diff --git a/lib/config/bloc_config.dart b/lib/config/bloc_config.dart index 94508aa4..29b65450 100644 --- a/lib/config/bloc_config.dart +++ b/lib/config/bloc_config.dart @@ -10,7 +10,7 @@ import 'package:selfprivacy/logic/cubit/jobs/jobs_cubit.dart'; import 'package:selfprivacy/logic/cubit/providers/providers_cubit.dart'; import 'package:selfprivacy/logic/cubit/services/services_cubit.dart'; import 'package:selfprivacy/logic/cubit/users/users_cubit.dart'; -import 'package:selfprivacy/logic/cubit/volumes/volumes_cubit.dart'; +import 'package:selfprivacy/logic/cubit/provider_volumes/provider_volume_cubit.dart'; class BlocAndProviderConfig extends StatelessWidget { const BlocAndProviderConfig({final super.key, this.child}); @@ -27,7 +27,7 @@ class BlocAndProviderConfig extends StatelessWidget { final dnsRecordsCubit = DnsRecordsCubit(serverInstallationCubit); final recoveryKeyCubit = RecoveryKeyCubit(serverInstallationCubit); final apiDevicesCubit = ApiDevicesCubit(serverInstallationCubit); - final apiVolumesCubit = ApiVolumesCubit(serverInstallationCubit); + final apiVolumesCubit = ApiProviderVolumeCubit(serverInstallationCubit); return MultiProvider( providers: [ BlocProvider( diff --git a/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart b/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart new file mode 100644 index 00000000..12e1a034 --- /dev/null +++ b/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart @@ -0,0 +1,108 @@ +import 'package:selfprivacy/config/get_it_config.dart'; +import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/server.dart'; +import 'package:selfprivacy/logic/api_maps/rest_maps/api_factory_creator.dart'; +import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/server_provider_factory.dart'; +import 'package:selfprivacy/logic/common_enum/common_enum.dart'; +import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart'; +import 'package:selfprivacy/logic/models/hive/server_details.dart'; +import 'package:selfprivacy/logic/models/json/server_disk_volume.dart'; +import 'package:selfprivacy/ui/pages/server_storage/disk_status.dart'; + +part 'provider_volume_state.dart'; + +class ApiProviderVolumeCubit + extends ServerInstallationDependendCubit { + ApiProviderVolumeCubit(final ServerInstallationCubit serverInstallationCubit) + : super(serverInstallationCubit, const ApiProviderVolumeState.initial()) { + final serverDetails = getIt().serverDetails; + providerApi = serverDetails == null + ? null + : VolumeApiFactoryCreator.createVolumeProviderApiFactory( + getIt().serverDetails!.provider, + ); + } + + VolumeProviderApiFactory? providerApi; + + @override + Future load() async { + if (serverInstallationCubit.state is ServerInstallationFinished) { + _refetch(); + } + } + + Future getPricePerGb() async => + providerApi!.getVolumeProvider().getPricePerGb(); + + Future refresh() async { + emit(const ApiProviderVolumeState([], LoadingStatus.refreshing)); + _refetch(); + } + + Future _refetch() async { + if (providerApi == null) { + return emit(const ApiProviderVolumeState([], LoadingStatus.error)); + } + + final List volumes = + await providerApi!.getVolumeProvider().getVolumes(); + + if (volumes.isEmpty) { + return emit(const ApiProviderVolumeState([], LoadingStatus.error)); + } + + emit(ApiProviderVolumeState(volumes, LoadingStatus.success)); + } + + Future attachVolume(final ServerVolume volume) async { + final ServerHostingDetails server = getIt().serverDetails!; + await providerApi!.getVolumeProvider().attachVolume(volume.id, server.id); + refresh(); + } + + Future detachVolume(final ServerVolume volume) async { + await providerApi!.getVolumeProvider().detachVolume(volume.id); + refresh(); + } + + Future resizeVolume( + final DiskVolume volume, + final int newSizeGb, + ) async { + final bool resized = await providerApi!.getVolumeProvider().resizeVolume( + volume.providerVolume!.id, + newSizeGb, + ); + + if (!resized) { + return false; + } + + await ServerApi().resizeVolume(volume.name); + refresh(); + return true; + } + + Future createVolume() async { + final ServerVolume? volume = + await providerApi!.getVolumeProvider().createVolume(); + await attachVolume(volume!); + + await Future.delayed(const Duration(seconds: 10)); + + await ServerApi().mountVolume(volume.name); + refresh(); + } + + Future deleteVolume(final DiskVolume volume) async { + await providerApi! + .getVolumeProvider() + .deleteVolume(volume.providerVolume!.id); + refresh(); + } + + @override + void clear() { + emit(const ApiProviderVolumeState.initial()); + } +} diff --git a/lib/logic/cubit/provider_volumes/provider_volume_state.dart b/lib/logic/cubit/provider_volumes/provider_volume_state.dart new file mode 100644 index 00000000..16a3f177 --- /dev/null +++ b/lib/logic/cubit/provider_volumes/provider_volume_state.dart @@ -0,0 +1,24 @@ +part of 'provider_volume_cubit.dart'; + +class ApiProviderVolumeState extends ServerInstallationDependendState { + const ApiProviderVolumeState(this._volumes, this.status); + + const ApiProviderVolumeState.initial() + : this(const [], LoadingStatus.uninitialized); + final List _volumes; + final LoadingStatus status; + + List get volumes => _volumes; + + ApiProviderVolumeState copyWith({ + final List? volumes, + final LoadingStatus? status, + }) => + ApiProviderVolumeState( + volumes ?? _volumes, + status ?? this.status, + ); + + @override + List get props => [_volumes]; +} diff --git a/lib/logic/cubit/server_installation/server_installation_cubit.dart b/lib/logic/cubit/server_installation/server_installation_cubit.dart index a1b15e32..571eb8ac 100644 --- a/lib/logic/cubit/server_installation/server_installation_cubit.dart +++ b/lib/logic/cubit/server_installation/server_installation_cubit.dart @@ -554,9 +554,6 @@ class ServerInstallationCubit extends Cubit { ); } - Future> getServerDiskVolumes() async => - ServerApi().getServerDiskVolumes(); - Future setAndValidateCloudflareToken(final String token) async { final ServerInstallationRecovery dataState = state as ServerInstallationRecovery; diff --git a/lib/logic/cubit/server_volumes/server_volume_cubit.dart b/lib/logic/cubit/server_volumes/server_volume_cubit.dart new file mode 100644 index 00000000..fbd49a99 --- /dev/null +++ b/lib/logic/cubit/server_volumes/server_volume_cubit.dart @@ -0,0 +1,36 @@ +import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/server.dart'; +import 'package:selfprivacy/logic/common_enum/common_enum.dart'; +import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart'; +import 'package:selfprivacy/logic/models/json/server_disk_volume.dart'; + +part 'server_volume_state.dart'; + +class ApiServerVolumeCubit + extends ServerInstallationDependendCubit { + ApiServerVolumeCubit(final ServerInstallationCubit serverInstallationCubit) + : super(serverInstallationCubit, const ApiServerVolumeState.initial()); + + final ServerApi serverApi = ServerApi(); + + @override + Future load() async { + if (serverInstallationCubit.state is ServerInstallationFinished) { + _refetch(); + } + } + + Future _refetch() async { + final List volumes = + await serverApi.getServerDiskVolumes(); + if (volumes.isNotEmpty) { + emit(ApiServerVolumeState(volumes, LoadingStatus.success)); + } else { + emit(const ApiServerVolumeState([], LoadingStatus.error)); + } + } + + @override + void clear() { + emit(const ApiServerVolumeState.initial()); + } +} diff --git a/lib/logic/cubit/server_volumes/server_volume_state.dart b/lib/logic/cubit/server_volumes/server_volume_state.dart new file mode 100644 index 00000000..3c267710 --- /dev/null +++ b/lib/logic/cubit/server_volumes/server_volume_state.dart @@ -0,0 +1,24 @@ +part of 'server_volume_cubit.dart'; + +class ApiServerVolumeState extends ServerInstallationDependendState { + const ApiServerVolumeState(this._volumes, this.status); + + const ApiServerVolumeState.initial() + : this(const [], LoadingStatus.uninitialized); + final List _volumes; + final LoadingStatus status; + + List get volumes => _volumes; + + ApiServerVolumeState copyWith({ + final List? volumes, + final LoadingStatus? status, + }) => + ApiServerVolumeState( + volumes ?? _volumes, + status ?? this.status, + ); + + @override + List get props => [_volumes]; +} diff --git a/lib/logic/cubit/volumes/volumes_cubit.dart b/lib/logic/cubit/volumes/volumes_cubit.dart deleted file mode 100644 index c6c522a9..00000000 --- a/lib/logic/cubit/volumes/volumes_cubit.dart +++ /dev/null @@ -1,160 +0,0 @@ -import 'package:selfprivacy/config/get_it_config.dart'; -import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/server.dart'; -import 'package:selfprivacy/logic/api_maps/rest_maps/api_factory_creator.dart'; -import 'package:selfprivacy/logic/api_maps/rest_maps/server_providers/server_provider_factory.dart'; -import 'package:selfprivacy/logic/common_enum/common_enum.dart'; -import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart'; -import 'package:selfprivacy/logic/models/hive/server_details.dart'; -import 'package:selfprivacy/logic/models/json/server_disk_volume.dart'; - -part 'volumes_state.dart'; - -class ApiVolumesCubit - extends ServerInstallationDependendCubit { - ApiVolumesCubit(final ServerInstallationCubit serverInstallationCubit) - : super(serverInstallationCubit, const ApiVolumesState.initial()) { - final serverDetails = getIt().serverDetails; - providerApi = serverDetails == null - ? null - : VolumeApiFactoryCreator.createVolumeProviderApiFactory( - getIt().serverDetails!.provider, - ); - } - - VolumeProviderApiFactory? providerApi; - - @override - Future load() async { - if (serverInstallationCubit.state is ServerInstallationFinished) { - _refetch(); - } - } - - Future> getVolumes() async { - if (providerApi == null) { - return []; - } - - return providerApi!.getVolumeProvider().getVolumes(); - } - - Future getPricePerGb() async => - providerApi!.getVolumeProvider().getPricePerGb(); - - Future refresh() async { - emit(const ApiVolumesState([], LoadingStatus.refreshing)); - _refetch(); - } - - Future _refetch() async { - final List volumes = await getVolumes(); - if (volumes.isNotEmpty) { - emit(ApiVolumesState(volumes, LoadingStatus.success)); - } else { - emit(const ApiVolumesState([], LoadingStatus.error)); - } - } - - Future attachVolume(final ServerVolume volume) async { - final ServerHostingDetails server = getIt().serverDetails!; - await providerApi!.getVolumeProvider().attachVolume(volume.id, server.id); - refresh(); - } - - Future detachVolume(final ServerVolume volume) async { - await providerApi!.getVolumeProvider().detachVolume(volume.id); - refresh(); - } - - Future resizeVolume( - final ServerDiskVolume volume, - final int newSizeGb, - ) async { - final ServerVolume? providerVolume = await fetchProdiverVolume(volume); - final bool resized = await providerApi!.getVolumeProvider().resizeVolume( - providerVolume!.id, - newSizeGb, - ); - - if (!resized) { - return false; - } - - await ServerApi().resizeVolume(volume.name); - refresh(); - return true; - } - - Future createVolume() async { - final ServerVolume? volume = - await providerApi!.getVolumeProvider().createVolume(); - await attachVolume(volume!); - - await Future.delayed(const Duration(seconds: 10)); - - final ServerDiskVolume? diskVolume = await fetchServerDiskVolume(volume); - await ServerApi().mountVolume(diskVolume!.name); - refresh(); - } - - Future deleteVolume(final ServerDiskVolume volume) async { - final ServerVolume? providerVolume = await fetchProdiverVolume(volume); - await providerApi!.getVolumeProvider().deleteVolume(providerVolume!.id); - refresh(); - } - - @override - void clear() { - emit(const ApiVolumesState.initial()); - } - - Future fetchProdiverVolume( - final ServerDiskVolume volume, - ) async { - ServerVolume? fetchedVolume; - final List volumes = - await providerApi!.getVolumeProvider().getVolumes(); - - for (final ServerVolume providerVolume in volumes) { - if (providerVolume.linuxDevice == null) { - continue; - } - - final String deviceId = providerVolume.linuxDevice!.split('/').last; - if (deviceId.contains(volume.model!) && - deviceId.contains(volume.serial!)) { - fetchedVolume = providerVolume; - break; - } - } - - return fetchedVolume; - } - - Future fetchServerDiskVolume( - final ServerVolume volume, - ) async { - ServerDiskVolume? fetchedVolume; - if (volume.linuxDevice == null) { - return fetchedVolume; - } - - final List volumes = - await ServerApi().getServerDiskVolumes(); - - for (final ServerDiskVolume serverDiskVolumes in volumes) { - if (serverDiskVolumes.model == null || serverDiskVolumes.serial == null) { - continue; - } - - final String deviceId = volume.linuxDevice!.split('/').last; - if (deviceId.contains(serverDiskVolumes.model!) && - deviceId.contains(serverDiskVolumes.serial!)) { - fetchedVolume = serverDiskVolumes; - break; - } - } - - return fetchedVolume; - } -} diff --git a/lib/logic/cubit/volumes/volumes_state.dart b/lib/logic/cubit/volumes/volumes_state.dart deleted file mode 100644 index cdc14272..00000000 --- a/lib/logic/cubit/volumes/volumes_state.dart +++ /dev/null @@ -1,23 +0,0 @@ -part of 'volumes_cubit.dart'; - -class ApiVolumesState extends ServerInstallationDependendState { - const ApiVolumesState(this._volumes, this.status); - - const ApiVolumesState.initial() : this(const [], LoadingStatus.uninitialized); - final List _volumes; - final LoadingStatus status; - - List get volumes => _volumes; - - ApiVolumesState copyWith({ - final List? volumes, - final LoadingStatus? status, - }) => - ApiVolumesState( - volumes ?? _volumes, - status ?? this.status, - ); - - @override - List get props => [_volumes]; -} diff --git a/lib/ui/pages/providers/providers.dart b/lib/ui/pages/providers/providers.dart index 4e99fab5..2696bf85 100644 --- a/lib/ui/pages/providers/providers.dart +++ b/lib/ui/pages/providers/providers.dart @@ -5,7 +5,8 @@ import 'package:selfprivacy/logic/cubit/backups/backups_cubit.dart'; import 'package:selfprivacy/logic/cubit/dns_records/dns_records_cubit.dart'; import 'package:selfprivacy/logic/cubit/providers/providers_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; -import 'package:selfprivacy/logic/cubit/volumes/volumes_cubit.dart'; +import 'package:selfprivacy/logic/cubit/provider_volumes/provider_volume_cubit.dart'; +import 'package:selfprivacy/logic/cubit/server_volumes/server_volume_cubit.dart'; import 'package:selfprivacy/logic/models/disk_size.dart'; import 'package:selfprivacy/logic/models/hive/server_details.dart'; import 'package:selfprivacy/logic/models/json/server_disk_volume.dart'; @@ -76,22 +77,10 @@ class _ProvidersPageState extends State { cards.add( Padding( padding: const EdgeInsets.only(bottom: 30), - child: FutureBuilder( - future: Future.wait([ - context.read().getServerDiskVolumes(), - context.read().getVolumes(), - ]), - builder: ( - final BuildContext context, - final AsyncSnapshot> snapshot, - ) => - StorageCard( - diskStatus: snapshot.hasData - ? toDiskStatus( - snapshot.data![0] as List, - snapshot.data![1] as List, - ) - : DiskStatus(), + child: StorageCard( + diskStatus: toDiskStatus( + context.read().state.volumes, + context.read().state.volumes, ), ), ), diff --git a/lib/ui/pages/server_storage/extending_volume.dart b/lib/ui/pages/server_storage/extending_volume.dart index e232bac4..651de4d0 100644 --- a/lib/ui/pages/server_storage/extending_volume.dart +++ b/lib/ui/pages/server_storage/extending_volume.dart @@ -1,7 +1,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart'; -import 'package:selfprivacy/logic/cubit/volumes/volumes_cubit.dart'; +import 'package:selfprivacy/logic/cubit/provider_volumes/provider_volume_cubit.dart'; import 'package:selfprivacy/logic/models/disk_size.dart'; import 'package:selfprivacy/ui/components/brand_button/filled_button.dart'; import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart'; @@ -39,7 +39,7 @@ class _ExtendingVolumePageState extends State { @override Widget build(final BuildContext context) => FutureBuilder( - future: context.read().getPricePerGb(), + future: context.read().getPricePerGb(), builder: ( final BuildContext context, final AsyncSnapshot snapshot, diff --git a/lib/ui/pages/server_storage/service_storage_consumption_list_item.dart b/lib/ui/pages/server_storage/service_storage_consumption_list_item.dart new file mode 100644 index 00000000..9396c1fc --- /dev/null +++ b/lib/ui/pages/server_storage/service_storage_consumption_list_item.dart @@ -0,0 +1,56 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:selfprivacy/ui/components/brand_linear_indicator/brand_linear_indicator.dart'; + +class ServiceStorageConsumptionListItem extends StatelessWidget { + const ServiceStorageConsumptionListItem({ + required this.title, + required this.percentage, + required this.storageConsumptionText, + required this.color, + required this.icon, + final super.key, + }); + + final String title; + final double percentage; + final String storageConsumptionText; + final Color color; + final IconData icon; + @override + Widget build(final BuildContext context) => Row( + children: [ + Icon( + icon, + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + title, + style: Theme.of(context).textTheme.titleMedium, + ), + Text( + storageConsumptionText, + style: Theme.of(context).textTheme.bodyMedium, + ), + ], + ), + const SizedBox(height: 4), + BrandLinearIndicator( + value: percentage, + color: color, + backgroundColor: Theme.of(context).colorScheme.surfaceVariant, + height: 7.0, + ), + ], + ), + ), + ], + ); +}