From fa21bdf034827512a4c77c8177fbf0beb2ed9e4f Mon Sep 17 00:00:00 2001 From: Inex Code Date: Fri, 26 Jan 2024 14:43:44 +0400 Subject: [PATCH 01/34] refactor: Remove unused timer singleton --- lib/config/get_it_config.dart | 3 --- lib/logic/get_it/timer.dart | 12 ------------ 2 files changed, 15 deletions(-) delete mode 100644 lib/logic/get_it/timer.dart diff --git a/lib/config/get_it_config.dart b/lib/config/get_it_config.dart index 6961ea94..659b4347 100644 --- a/lib/config/get_it_config.dart +++ b/lib/config/get_it_config.dart @@ -2,12 +2,10 @@ import 'package:get_it/get_it.dart'; import 'package:selfprivacy/logic/get_it/api_config.dart'; import 'package:selfprivacy/logic/get_it/console.dart'; import 'package:selfprivacy/logic/get_it/navigation.dart'; -import 'package:selfprivacy/logic/get_it/timer.dart'; export 'package:selfprivacy/logic/get_it/api_config.dart'; export 'package:selfprivacy/logic/get_it/console.dart'; export 'package:selfprivacy/logic/get_it/navigation.dart'; -export 'package:selfprivacy/logic/get_it/timer.dart'; final GetIt getIt = GetIt.instance; @@ -15,7 +13,6 @@ Future getItSetup() async { getIt.registerSingleton(NavigationService()); getIt.registerSingleton(ConsoleModel()); - getIt.registerSingleton(TimerModel()); getIt.registerSingleton(ApiConfigModel()..init()); await getIt.allReady(); diff --git a/lib/logic/get_it/timer.dart b/lib/logic/get_it/timer.dart deleted file mode 100644 index e91d3a20..00000000 --- a/lib/logic/get_it/timer.dart +++ /dev/null @@ -1,12 +0,0 @@ -import 'package:flutter/material.dart'; - -class TimerModel extends ChangeNotifier { - DateTime _time = DateTime.now(); - - DateTime get time => _time; - - void restart() { - _time = DateTime.now(); - notifyListeners(); - } -} From 24e5c8baeefa0160aa4b56e6643413be2a207829 Mon Sep 17 00:00:00 2001 From: Inex Code Date: Fri, 26 Jan 2024 14:49:36 +0400 Subject: [PATCH 02/34] refactor: Remove unused providers cubit --- lib/config/bloc_config.dart | 5 +-- .../cubit/providers/providers_cubit.dart | 19 -------- .../cubit/providers/providers_state.dart | 44 ------------------- lib/ui/pages/providers/providers.dart | 2 +- lib/ui/pages/server_storage/storage_card.dart | 2 +- 5 files changed, 3 insertions(+), 69 deletions(-) delete mode 100644 lib/logic/cubit/providers/providers_cubit.dart delete mode 100644 lib/logic/cubit/providers/providers_state.dart diff --git a/lib/config/bloc_config.dart b/lib/config/bloc_config.dart index 381261fe..3cd6055c 100644 --- a/lib/config/bloc_config.dart +++ b/lib/config/bloc_config.dart @@ -1,5 +1,6 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:selfprivacy/logic/cubit/connection_status/connection_status_bloc.dart'; import 'package:selfprivacy/logic/cubit/devices/devices_cubit.dart'; import 'package:selfprivacy/logic/cubit/recovery_key/recovery_key_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_detailed_info/server_detailed_info_cubit.dart'; @@ -8,7 +9,6 @@ import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart'; 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/client_jobs/client_jobs_cubit.dart'; -import 'package:selfprivacy/logic/cubit/providers/providers_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_volumes/server_volume_cubit.dart'; import 'package:selfprivacy/logic/cubit/services/services_cubit.dart'; @@ -55,9 +55,6 @@ class BlocAndProviderConfig extends StatelessWidget { create: (final _) => serverInstallationCubit, lazy: false, ), - BlocProvider( - create: (final _) => ProvidersCubit(), - ), BlocProvider( create: (final _) => usersCubit..load(), lazy: false, diff --git a/lib/logic/cubit/providers/providers_cubit.dart b/lib/logic/cubit/providers/providers_cubit.dart deleted file mode 100644 index d3ce60b9..00000000 --- a/lib/logic/cubit/providers/providers_cubit.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:equatable/equatable.dart'; -import 'package:selfprivacy/logic/models/provider.dart'; -import 'package:selfprivacy/logic/models/state_types.dart'; - -export 'package:selfprivacy/logic/models/state_types.dart'; -export 'package:provider/provider.dart'; - -part 'providers_state.dart'; - -class ProvidersCubit extends Cubit { - ProvidersCubit() : super(InitialProviderState()); - - void connect(final ProviderModel provider) { - final ProvidersState newState = - state.updateElement(provider, StateType.stable); - emit(newState); - } -} diff --git a/lib/logic/cubit/providers/providers_state.dart b/lib/logic/cubit/providers/providers_state.dart deleted file mode 100644 index 04146b5d..00000000 --- a/lib/logic/cubit/providers/providers_state.dart +++ /dev/null @@ -1,44 +0,0 @@ -part of 'providers_cubit.dart'; - -class ProvidersState extends Equatable { - const ProvidersState(this.all); - - final List all; - - ProvidersState updateElement( - final ProviderModel provider, - final StateType newState, - ) { - final List newList = [...all]; - final int index = newList.indexOf(provider); - newList[index] = provider.updateState(newState); - return ProvidersState(newList); - } - - List get connected => all - .where((final service) => service.state != StateType.uninitialized) - .toList(); - - List get uninitialized => all - .where((final service) => service.state == StateType.uninitialized) - .toList(); - - bool get isFullyInitialized => uninitialized.isEmpty; - - @override - List get props => all; -} - -class InitialProviderState extends ProvidersState { - InitialProviderState() - : super( - ProviderType.values - .map( - (final type) => ProviderModel( - state: StateType.uninitialized, - type: type, - ), - ) - .toList(), - ); -} diff --git a/lib/ui/pages/providers/providers.dart b/lib/ui/pages/providers/providers.dart index 5b2285f3..5d9d4fcc 100644 --- a/lib/ui/pages/providers/providers.dart +++ b/lib/ui/pages/providers/providers.dart @@ -4,9 +4,9 @@ import 'package:flutter/material.dart'; import 'package:selfprivacy/config/brand_theme.dart'; 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/server_volumes/server_volume_cubit.dart'; +import 'package:selfprivacy/logic/models/state_types.dart'; import 'package:selfprivacy/ui/components/brand_header/brand_header.dart'; import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart'; import 'package:selfprivacy/ui/components/icon_status_mask/icon_status_mask.dart'; diff --git a/lib/ui/pages/server_storage/storage_card.dart b/lib/ui/pages/server_storage/storage_card.dart index fbda9bee..2c6d334f 100644 --- a/lib/ui/pages/server_storage/storage_card.dart +++ b/lib/ui/pages/server_storage/storage_card.dart @@ -2,7 +2,7 @@ import 'package:auto_route/auto_route.dart'; 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/providers/providers_cubit.dart'; +import 'package:selfprivacy/logic/models/state_types.dart'; import 'package:selfprivacy/ui/components/icon_status_mask/icon_status_mask.dart'; import 'package:selfprivacy/logic/models/disk_status.dart'; import 'package:selfprivacy/ui/components/storage_list_items/server_storage_list_item.dart'; From 332e31b6552bc5b9c74ece68ea9ee9443a5caccb Mon Sep 17 00:00:00 2001 From: Inex Code Date: Fri, 26 Jan 2024 14:58:59 +0400 Subject: [PATCH 03/34] refactor: Remove binds migration --- lib/ui/pages/more/more.dart | 30 ------- .../migration_process_page.dart | 79 ------------------- .../binds_migration/services_migration.dart | 22 ++---- lib/ui/pages/services/service_page.dart | 1 - lib/ui/router/router.gr.dart | 8 +- 5 files changed, 8 insertions(+), 132 deletions(-) delete mode 100644 lib/ui/pages/server_storage/binds_migration/migration_process_page.dart diff --git a/lib/ui/pages/more/more.dart b/lib/ui/pages/more/more.dart index 99821373..189446db 100644 --- a/lib/ui/pages/more/more.dart +++ b/lib/ui/pages/more/more.dart @@ -4,8 +4,6 @@ import 'package:flutter/material.dart'; import 'package:ionicons/ionicons.dart'; import 'package:selfprivacy/config/brand_theme.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; -import 'package:selfprivacy/logic/cubit/server_volumes/server_volume_cubit.dart'; -import 'package:selfprivacy/logic/cubit/services/services_cubit.dart'; import 'package:selfprivacy/ui/components/cards/filled_card.dart'; import 'package:selfprivacy/ui/components/brand_header/brand_header.dart'; import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart'; @@ -21,9 +19,6 @@ class MorePage extends StatelessWidget { final bool isReady = context.watch().state is ServerInstallationFinished; - final bool? usesBinds = - context.watch().state.usesBinds; - return Scaffold( appBar: Breakpoints.small.isActive(context) ? PreferredSize( @@ -39,31 +34,6 @@ class MorePage extends StatelessWidget { padding: paddingH15V0, child: Column( children: [ - if (isReady && usesBinds != null && !usesBinds) - _MoreMenuItem( - title: 'storage.start_migration_button'.tr(), - iconData: Icons.drive_file_move_outline, - goTo: () => ServicesMigrationRoute( - diskStatus: - context.read().state.diskStatus, - services: context - .read() - .state - .services - .where( - (final service) => - service.id == 'bitwarden' || - service.id == 'gitea' || - service.id == 'pleroma' || - service.id == 'mailserver' || - service.id == 'nextcloud', - ) - .toList(), - isMigration: true, - ), - subtitle: 'storage.data_migration_notice'.tr(), - accent: true, - ), if (!isReady) _MoreMenuItem( title: 'more_page.configuration_wizard'.tr(), diff --git a/lib/ui/pages/server_storage/binds_migration/migration_process_page.dart b/lib/ui/pages/server_storage/binds_migration/migration_process_page.dart deleted file mode 100644 index 8d42cb6f..00000000 --- a/lib/ui/pages/server_storage/binds_migration/migration_process_page.dart +++ /dev/null @@ -1,79 +0,0 @@ -import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/material.dart'; -import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_cubit.dart'; -import 'package:selfprivacy/logic/models/json/server_job.dart'; -import 'package:selfprivacy/ui/components/buttons/brand_button.dart'; -import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; -import 'package:selfprivacy/ui/components/brand_linear_indicator/brand_linear_indicator.dart'; -import 'package:selfprivacy/ui/pages/root_route.dart'; -import 'package:selfprivacy/utils/route_transitions/basic.dart'; - -class MigrationProcessPage extends StatefulWidget { - const MigrationProcessPage({super.key}); - - @override - State createState() => _MigrationProcessPageState(); -} - -class _MigrationProcessPageState extends State { - @override - void initState() { - super.initState(); - } - - @override - Widget build(final BuildContext context) { - ServerJob? job; - String? subtitle = ''; - double value = 0.0; - List children = []; - - final serverJobsState = context.watch().state; - if (serverJobsState.migrationJobUid != null) { - job = context.read().getServerJobByUid( - serverJobsState.migrationJobUid!, - ); - } - - if (job == null) { - subtitle = 'basis.loading'.tr(); - } else { - value = job.progress == null ? 0.0 : job.progress! / 100; - subtitle = job.statusText; - children = [ - ...children, - const SizedBox(height: 16), - if (job.finishedAt != null) - Text( - job.result!, - style: Theme.of(context).textTheme.titleMedium, - ), - if (job.finishedAt != null) const SizedBox(height: 16), - if (job.finishedAt != null) - BrandButton.filled( - child: Text('storage.migration_done'.tr()), - onPressed: () { - Navigator.of(context).pushAndRemoveUntil( - materialRoute(const RootPage()), - (final predicate) => false, - ); - }, - ), - ]; - } - return BrandHeroScreen( - hasBackButton: false, - heroTitle: 'storage.migration_process'.tr(), - heroSubtitle: subtitle, - children: [ - BrandLinearIndicator( - value: value, - color: Theme.of(context).colorScheme.primary, - backgroundColor: Theme.of(context).colorScheme.surfaceVariant, - height: 4.0, - ), - ...children, - ], - ); - } -} diff --git a/lib/ui/pages/server_storage/binds_migration/services_migration.dart b/lib/ui/pages/server_storage/binds_migration/services_migration.dart index edb7474e..e981cb03 100644 --- a/lib/ui/pages/server_storage/binds_migration/services_migration.dart +++ b/lib/ui/pages/server_storage/binds_migration/services_migration.dart @@ -18,13 +18,11 @@ class ServicesMigrationPage extends StatefulWidget { const ServicesMigrationPage({ required this.services, required this.diskStatus, - required this.isMigration, super.key, }); final DiskStatus diskStatus; final List services; - final bool isMigration; @override State createState() => _ServicesMigrationPageState(); @@ -171,22 +169,16 @@ class _ServicesMigrationPageState extends State { ), ), const SizedBox(height: 16), - if (widget.isMigration || (!widget.isMigration && isVolumePicked)) + if (isVolumePicked) BrandButton.filled( child: Text('storage.start_migration_button'.tr()), onPressed: () { - if (widget.isMigration) { - context.read().migrateToBinds( - serviceToDisk, - ); - } else { - for (final service in widget.services) { - if (serviceToDisk[service.id] != null) { - context.read().moveService( - service.id, - serviceToDisk[service.id]!, - ); - } + for (final service in widget.services) { + if (serviceToDisk[service.id] != null) { + context.read().moveService( + service.id, + serviceToDisk[service.id]!, + ); } } context.router.popUntilRoot(); diff --git a/lib/ui/pages/services/service_page.dart b/lib/ui/pages/services/service_page.dart index 37f9515c..96560db0 100644 --- a/lib/ui/pages/services/service_page.dart +++ b/lib/ui/pages/services/service_page.dart @@ -118,7 +118,6 @@ class _ServicePageState extends State { services: [service], diskStatus: context.read().state.diskStatus, - isMigration: false, ), ), leading: const Icon(Icons.drive_file_move_outlined), diff --git a/lib/ui/router/router.gr.dart b/lib/ui/router/router.gr.dart index 98f4453a..1ff6d09c 100644 --- a/lib/ui/router/router.gr.dart +++ b/lib/ui/router/router.gr.dart @@ -159,7 +159,6 @@ abstract class _$RootRouter extends RootStackRouter { child: ServicesMigrationPage( services: args.services, diskStatus: args.diskStatus, - isMigration: args.isMigration, key: args.key, ), ); @@ -576,7 +575,6 @@ class ServicesMigrationRoute extends PageRouteInfo { ServicesMigrationRoute({ required List services, required DiskStatus diskStatus, - required bool isMigration, Key? key, List? children, }) : super( @@ -584,7 +582,6 @@ class ServicesMigrationRoute extends PageRouteInfo { args: ServicesMigrationRouteArgs( services: services, diskStatus: diskStatus, - isMigration: isMigration, key: key, ), initialChildren: children, @@ -600,7 +597,6 @@ class ServicesMigrationRouteArgs { const ServicesMigrationRouteArgs({ required this.services, required this.diskStatus, - required this.isMigration, this.key, }); @@ -608,13 +604,11 @@ class ServicesMigrationRouteArgs { final DiskStatus diskStatus; - final bool isMigration; - final Key? key; @override String toString() { - return 'ServicesMigrationRouteArgs{services: $services, diskStatus: $diskStatus, isMigration: $isMigration, key: $key}'; + return 'ServicesMigrationRouteArgs{services: $services, diskStatus: $diskStatus, key: $key}'; } } From b1be3f24d6b566266686b69aa228f2900872b15c Mon Sep 17 00:00:00 2001 From: Inex Code Date: Fri, 26 Jan 2024 18:46:09 +0400 Subject: [PATCH 04/34] refactor: Rewire cubit from depending on server_installation_cubit to the new connection manager --- lib/config/bloc_config.dart | 12 +- lib/config/get_it_config.dart | 5 + .../authentication_dependend_cubit.dart | 40 ----- lib/logic/cubit/backups/backups_cubit.dart | 47 +++-- .../connection_status_bloc.dart | 29 ++++ .../connection_status_event.dart | 14 ++ .../connection_status_state.dart | 12 ++ lib/logic/cubit/devices/devices_cubit.dart | 7 +- .../cubit/dns_records/dns_records_cubit.dart | 68 ++++---- .../provider_volume_cubit.dart | 10 +- .../recovery_key/recovery_key_cubit.dart | 6 +- .../server_connection_dependent_cubit.dart | 51 ++++++ .../server_connection_dependent_state.dart} | 2 +- .../server_detailed_info_cubit.dart | 6 +- .../server_installation_cubit.dart | 2 + .../cubit/server_jobs/server_jobs_bloc.dart | 87 ++++++++++ .../cubit/server_jobs/server_jobs_cubit.dart | 123 ------------- .../cubit/server_jobs/server_jobs_event.dart | 28 +++ .../cubit/server_jobs/server_jobs_state.dart | 44 +++-- .../server_volumes/server_volume_cubit.dart | 11 +- lib/logic/cubit/services/services_cubit.dart | 24 ++- lib/logic/cubit/users/users_cubit.dart | 12 +- .../get_it/api_connection_repository.dart | 164 ++++++++++++++++++ lib/logic/models/json/server_job.dart | 21 ++- .../components/jobs_content/jobs_content.dart | 14 +- lib/ui/pages/backups/backup_details.dart | 4 +- lib/ui/pages/backups/change_period_modal.dart | 2 +- .../backups/change_rotation_quotas_modal.dart | 2 +- .../backups/copy_encryption_key_modal.dart | 2 +- .../pages/backups/create_backups_modal.dart | 6 +- lib/ui/pages/backups/snapshot_modal.dart | 4 +- .../more/app_settings/developer_settings.dart | 10 ++ .../binds_migration/services_migration.dart | 2 +- .../server_storage/extending_volume.dart | 2 +- lib/ui/pages/server_storage/storage_card.dart | 2 +- .../broken_domain_outlined_card.dart | 2 +- .../initializing/dns_provider_picker.dart | 2 +- .../initializing/server_provider_picker.dart | 2 +- .../initializing/server_type_picker.dart | 2 +- .../recovering/recovery_confirm_server.dart | 2 +- 40 files changed, 570 insertions(+), 315 deletions(-) delete mode 100644 lib/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart create mode 100644 lib/logic/cubit/connection_status/connection_status_bloc.dart create mode 100644 lib/logic/cubit/connection_status/connection_status_event.dart create mode 100644 lib/logic/cubit/connection_status/connection_status_state.dart create mode 100644 lib/logic/cubit/server_connection_dependent/server_connection_dependent_cubit.dart rename lib/logic/cubit/{app_config_dependent/authentication_dependend_state.dart => server_connection_dependent/server_connection_dependent_state.dart} (69%) create mode 100644 lib/logic/cubit/server_jobs/server_jobs_bloc.dart delete mode 100644 lib/logic/cubit/server_jobs/server_jobs_cubit.dart create mode 100644 lib/logic/cubit/server_jobs/server_jobs_event.dart create mode 100644 lib/logic/get_it/api_connection_repository.dart diff --git a/lib/config/bloc_config.dart b/lib/config/bloc_config.dart index 3cd6055c..34b880dc 100644 --- a/lib/config/bloc_config.dart +++ b/lib/config/bloc_config.dart @@ -9,7 +9,7 @@ import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart'; 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/client_jobs/client_jobs_cubit.dart'; -import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_cubit.dart'; +import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_bloc.dart'; import 'package:selfprivacy/logic/cubit/server_volumes/server_volume_cubit.dart'; import 'package:selfprivacy/logic/cubit/services/services_cubit.dart'; import 'package:selfprivacy/logic/cubit/support_system/support_system_cubit.dart'; @@ -36,7 +36,8 @@ class BlocAndProviderConfig extends StatelessWidget { final apiVolumesCubit = ApiProviderVolumeCubit(serverInstallationCubit); final apiServerVolumesCubit = ApiServerVolumeCubit(serverInstallationCubit, apiVolumesCubit); - final serverJobsCubit = ServerJobsCubit(serverInstallationCubit); + final serverJobsBloc = ServerJobsBloc(); + final connectionStatusBloc = ConnectionStatusBloc(); final serverDetailsCubit = ServerDetailsCubit(serverInstallationCubit); return MultiProvider( @@ -64,11 +65,11 @@ class BlocAndProviderConfig extends StatelessWidget { lazy: false, ), BlocProvider( - create: (final _) => backupsCubit..load(), + create: (final _) => backupsCubit, lazy: false, ), BlocProvider( - create: (final _) => dnsRecordsCubit..load(), + create: (final _) => dnsRecordsCubit, ), BlocProvider( create: (final _) => recoveryKeyCubit..load(), @@ -83,8 +84,9 @@ class BlocAndProviderConfig extends StatelessWidget { create: (final _) => apiServerVolumesCubit..load(), ), BlocProvider( - create: (final _) => serverJobsCubit..load(), + create: (final _) => serverJobsBloc, ), + BlocProvider(create: (final _) => connectionStatusBloc), BlocProvider( create: (final _) => serverDetailsCubit..load(), ), diff --git a/lib/config/get_it_config.dart b/lib/config/get_it_config.dart index 659b4347..c67c1e32 100644 --- a/lib/config/get_it_config.dart +++ b/lib/config/get_it_config.dart @@ -1,5 +1,6 @@ import 'package:get_it/get_it.dart'; import 'package:selfprivacy/logic/get_it/api_config.dart'; +import 'package:selfprivacy/logic/get_it/api_connection_repository.dart'; import 'package:selfprivacy/logic/get_it/console.dart'; import 'package:selfprivacy/logic/get_it/navigation.dart'; @@ -15,5 +16,9 @@ Future getItSetup() async { getIt.registerSingleton(ConsoleModel()); getIt.registerSingleton(ApiConfigModel()..init()); + getIt.registerSingleton( + ApiConnectionRepository()..init(), + ); + await getIt.allReady(); } diff --git a/lib/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart b/lib/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart deleted file mode 100644 index 096a4d4f..00000000 --- a/lib/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart +++ /dev/null @@ -1,40 +0,0 @@ -import 'dart:async'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:equatable/equatable.dart'; -import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; - -export 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; - -part 'authentication_dependend_state.dart'; - -abstract class ServerInstallationDependendCubit< - T extends ServerInstallationDependendState> extends Cubit { - ServerInstallationDependendCubit( - this.serverInstallationCubit, - final T initState, - ) : super(initState) { - authCubitSubscription = - serverInstallationCubit.stream.listen(checkAuthStatus); - checkAuthStatus(serverInstallationCubit.state); - } - - void checkAuthStatus(final ServerInstallationState state) { - if (state is ServerInstallationFinished) { - load(); - } else if (state is ServerInstallationEmpty) { - clear(); - } - } - - late StreamSubscription authCubitSubscription; - final ServerInstallationCubit serverInstallationCubit; - - void load(); - void clear(); - - @override - Future close() { - authCubitSubscription.cancel(); - return super.close(); - } -} diff --git a/lib/logic/cubit/backups/backups_cubit.dart b/lib/logic/cubit/backups/backups_cubit.dart index 0c3bd893..1b358f4b 100644 --- a/lib/logic/cubit/backups/backups_cubit.dart +++ b/lib/logic/cubit/backups/backups_cubit.dart @@ -4,7 +4,8 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/backblaze.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart'; -import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart'; +import 'package:selfprivacy/logic/cubit/server_connection_dependent/server_connection_dependent_cubit.dart'; +import 'package:selfprivacy/logic/get_it/api_connection_repository.dart'; import 'package:selfprivacy/logic/models/hive/backblaze_bucket.dart'; import 'package:selfprivacy/logic/models/backup.dart'; import 'package:selfprivacy/logic/models/hive/backups_credential.dart'; @@ -13,10 +14,9 @@ import 'package:selfprivacy/logic/models/service.dart'; part 'backups_state.dart'; -class BackupsCubit extends ServerInstallationDependendCubit { +class BackupsCubit extends ServerConnectionDependentCubit { BackupsCubit(final ServerInstallationCubit serverInstallationCubit) : super( - serverInstallationCubit, const BackupsState(preventActions: true), ); @@ -25,24 +25,22 @@ class BackupsCubit extends ServerInstallationDependendCubit { @override Future load() async { - if (serverInstallationCubit.state is ServerInstallationFinished) { - final BackblazeBucket? bucket = getIt().backblazeBucket; - final BackupConfiguration? backupConfig = - await api.getBackupsConfiguration(); - final List backups = await api.getBackups(); - backups.sort((final a, final b) => b.time.compareTo(a.time)); - emit( - state.copyWith( - backblazeBucket: bucket, - isInitialized: backupConfig?.isInitialized, - autobackupPeriod: backupConfig?.autobackupPeriod ?? Duration.zero, - autobackupQuotas: backupConfig?.autobackupQuotas, - backups: backups, - preventActions: false, - refreshing: false, - ), - ); - } + final BackblazeBucket? bucket = getIt().backblazeBucket; + final BackupConfiguration? backupConfig = + await api.getBackupsConfiguration(); + final List backups = await api.getBackups(); + backups.sort((final a, final b) => b.time.compareTo(a.time)); + emit( + state.copyWith( + backblazeBucket: bucket, + isInitialized: backupConfig?.isInitialized, + autobackupPeriod: backupConfig?.autobackupPeriod ?? Duration.zero, + autobackupQuotas: backupConfig?.autobackupQuotas, + backups: backups, + preventActions: false, + refreshing: false, + ), + ); } Future initializeBackups() async { @@ -59,10 +57,11 @@ class BackupsCubit extends ServerInstallationDependendCubit { final BackblazeBucket bucket; if (state.backblazeBucket == null) { - final String domain = serverInstallationCubit - .state.serverDomain!.domainName + final String domain = getIt() + .serverDomain! + .domainName .replaceAll(RegExp(r'[^a-zA-Z0-9]'), '-'); - final int serverId = serverInstallationCubit.state.serverDetails!.id; + final int serverId = getIt().serverDetails!.id; String bucketName = '${DateTime.now().millisecondsSinceEpoch}-$serverId-$domain'; if (bucketName.length > 49) { diff --git a/lib/logic/cubit/connection_status/connection_status_bloc.dart b/lib/logic/cubit/connection_status/connection_status_bloc.dart new file mode 100644 index 00000000..df3ebf97 --- /dev/null +++ b/lib/logic/cubit/connection_status/connection_status_bloc.dart @@ -0,0 +1,29 @@ +import 'package:equatable/equatable.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:selfprivacy/config/get_it_config.dart'; +import 'package:selfprivacy/logic/get_it/api_connection_repository.dart'; + +part 'connection_status_event.dart'; +part 'connection_status_state.dart'; + +class ConnectionStatusBloc + extends Bloc { + ConnectionStatusBloc() + : super( + const ConnectionStatusState( + connectionStatus: ConnectionStatus.nonexistent, + ), + ) { + final apiConnectionRepository = getIt(); + apiConnectionRepository.connectionStatusStream.listen( + (final ConnectionStatus connectionStatus) { + add( + ConnectionStatusChanged(connectionStatus), + ); + }, + ); + on((final event, final emit) { + emit(ConnectionStatusState(connectionStatus: event.connectionStatus)); + }); + } +} diff --git a/lib/logic/cubit/connection_status/connection_status_event.dart b/lib/logic/cubit/connection_status/connection_status_event.dart new file mode 100644 index 00000000..0fc6e72f --- /dev/null +++ b/lib/logic/cubit/connection_status/connection_status_event.dart @@ -0,0 +1,14 @@ +part of 'connection_status_bloc.dart'; + +sealed class ConnectionStatusEvent extends Equatable { + const ConnectionStatusEvent(); +} + +class ConnectionStatusChanged extends ConnectionStatusEvent { + const ConnectionStatusChanged(this.connectionStatus); + + final ConnectionStatus connectionStatus; + + @override + List get props => [connectionStatus]; +} diff --git a/lib/logic/cubit/connection_status/connection_status_state.dart b/lib/logic/cubit/connection_status/connection_status_state.dart new file mode 100644 index 00000000..258765c1 --- /dev/null +++ b/lib/logic/cubit/connection_status/connection_status_state.dart @@ -0,0 +1,12 @@ +part of 'connection_status_bloc.dart'; + +class ConnectionStatusState extends Equatable { + const ConnectionStatusState({ + required this.connectionStatus, + }); + + final ConnectionStatus connectionStatus; + + @override + List get props => [connectionStatus]; +} diff --git a/lib/logic/cubit/devices/devices_cubit.dart b/lib/logic/cubit/devices/devices_cubit.dart index 7713254b..1e3852cd 100644 --- a/lib/logic/cubit/devices/devices_cubit.dart +++ b/lib/logic/cubit/devices/devices_cubit.dart @@ -1,15 +1,14 @@ import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.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/cubit/server_connection_dependent/server_connection_dependent_cubit.dart'; import 'package:selfprivacy/logic/models/json/api_token.dart'; part 'devices_state.dart'; -class ApiDevicesCubit - extends ServerInstallationDependendCubit { +class ApiDevicesCubit extends ServerConnectionDependentCubit { ApiDevicesCubit(final ServerInstallationCubit serverInstallationCubit) - : super(serverInstallationCubit, const ApiDevicesState.initial()); + : super(const ApiDevicesState.initial()); final ServerApi api = ServerApi(); diff --git a/lib/logic/cubit/dns_records/dns_records_cubit.dart b/lib/logic/cubit/dns_records/dns_records_cubit.dart index 539f36a1..0f14a8cc 100644 --- a/lib/logic/cubit/dns_records/dns_records_cubit.dart +++ b/lib/logic/cubit/dns_records/dns_records_cubit.dart @@ -1,6 +1,8 @@ import 'package:cubit_form/cubit_form.dart'; +import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/desired_dns_record.dart'; -import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart'; +import 'package:selfprivacy/logic/cubit/server_connection_dependent/server_connection_dependent_cubit.dart'; +import 'package:selfprivacy/logic/get_it/api_connection_repository.dart'; import 'package:selfprivacy/logic/models/hive/server_domain.dart'; import 'package:selfprivacy/logic/models/json/dns_records.dart'; @@ -10,11 +12,9 @@ import 'package:selfprivacy/utils/network_utils.dart'; part 'dns_records_state.dart'; -class DnsRecordsCubit - extends ServerInstallationDependendCubit { +class DnsRecordsCubit extends ServerConnectionDependentCubit { DnsRecordsCubit(final ServerInstallationCubit serverInstallationCubit) : super( - serverInstallationCubit, const DnsRecordsState(dnsState: DnsRecordsStatus.refreshing), ); @@ -29,38 +29,36 @@ class DnsRecordsCubit ), ); - if (serverInstallationCubit.state is ServerInstallationFinished) { - final ServerDomain? domain = serverInstallationCubit.state.serverDomain; - final String? ipAddress = - serverInstallationCubit.state.serverDetails?.ip4; + final ServerDomain? domain = getIt().serverDomain; + final String? ipAddress = + getIt().serverDetails?.ip4; - if (domain == null || ipAddress == null) { - emit(const DnsRecordsState()); - return; - } - - final List allDnsRecords = await api.getDnsRecords(); - allDnsRecords.removeWhere((final record) => record.type == 'AAAA'); - final foundRecords = await validateDnsRecords( - domain, - extractDkimRecord(allDnsRecords)?.content ?? '', - allDnsRecords, - ); - - if (!foundRecords.success || foundRecords.data.isEmpty) { - emit(const DnsRecordsState()); - return; - } - - emit( - DnsRecordsState( - dnsRecords: foundRecords.data, - dnsState: foundRecords.data.any((final r) => r.isSatisfied == false) - ? DnsRecordsStatus.error - : DnsRecordsStatus.good, - ), - ); + if (domain == null || ipAddress == null) { + emit(const DnsRecordsState()); + return; } + + final List allDnsRecords = await api.getDnsRecords(); + allDnsRecords.removeWhere((final record) => record.type == 'AAAA'); + final foundRecords = await validateDnsRecords( + domain, + extractDkimRecord(allDnsRecords)?.content ?? '', + allDnsRecords, + ); + + if (!foundRecords.success || foundRecords.data.isEmpty) { + emit(const DnsRecordsState()); + return; + } + + emit( + DnsRecordsState( + dnsRecords: foundRecords.data, + dnsState: foundRecords.data.any((final r) => r.isSatisfied == false) + ? DnsRecordsStatus.error + : DnsRecordsStatus.good, + ), + ); } /// Tries to check whether all known DNS records on the domain by ip4 @@ -171,7 +169,7 @@ class DnsRecordsCubit final List records = await api.getDnsRecords(); /// TODO: Error handling? - final ServerDomain? domain = serverInstallationCubit.state.serverDomain; + final ServerDomain? domain = getIt().serverDomain; await ProvidersController.currentDnsProvider!.removeDomainRecords( records: records, domain: domain!, diff --git a/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart b/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart index fed7b083..0f705d1e 100644 --- a/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart +++ b/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart @@ -4,7 +4,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.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/cubit/server_connection_dependent/server_connection_dependent_cubit.dart'; import 'package:selfprivacy/logic/models/disk_size.dart'; import 'package:selfprivacy/logic/models/hive/server_details.dart'; import 'package:selfprivacy/logic/models/disk_status.dart'; @@ -14,16 +14,14 @@ import 'package:selfprivacy/logic/providers/providers_controller.dart'; part 'provider_volume_state.dart'; class ApiProviderVolumeCubit - extends ServerInstallationDependendCubit { + extends ServerConnectionDependentCubit { ApiProviderVolumeCubit(final ServerInstallationCubit serverInstallationCubit) - : super(serverInstallationCubit, const ApiProviderVolumeState.initial()); + : super(const ApiProviderVolumeState.initial()); final ServerApi serverApi = ServerApi(); @override Future load() async { - if (serverInstallationCubit.state is ServerInstallationFinished) { - unawaited(_refetch()); - } + unawaited(_refetch()); } Future getPricePerGb() async { diff --git a/lib/logic/cubit/recovery_key/recovery_key_cubit.dart b/lib/logic/cubit/recovery_key/recovery_key_cubit.dart index c5f68d57..f49f1b24 100644 --- a/lib/logic/cubit/recovery_key/recovery_key_cubit.dart +++ b/lib/logic/cubit/recovery_key/recovery_key_cubit.dart @@ -2,15 +2,15 @@ import 'dart:async'; import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.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/cubit/server_connection_dependent/server_connection_dependent_cubit.dart'; import 'package:selfprivacy/logic/models/json/recovery_token_status.dart'; part 'recovery_key_state.dart'; class RecoveryKeyCubit - extends ServerInstallationDependendCubit { + extends ServerConnectionDependentCubit { RecoveryKeyCubit(final ServerInstallationCubit serverInstallationCubit) - : super(serverInstallationCubit, const RecoveryKeyState.initial()); + : super(const RecoveryKeyState.initial()); final ServerApi api = ServerApi(); diff --git a/lib/logic/cubit/server_connection_dependent/server_connection_dependent_cubit.dart b/lib/logic/cubit/server_connection_dependent/server_connection_dependent_cubit.dart new file mode 100644 index 00000000..61afa203 --- /dev/null +++ b/lib/logic/cubit/server_connection_dependent/server_connection_dependent_cubit.dart @@ -0,0 +1,51 @@ +import 'dart:async'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:equatable/equatable.dart'; +import 'package:selfprivacy/config/get_it_config.dart'; +import 'package:selfprivacy/logic/get_it/api_connection_repository.dart'; + +export 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; + +part 'server_connection_dependent_state.dart'; + +abstract class ServerConnectionDependentCubit< + T extends ServerInstallationDependendState> extends Cubit { + ServerConnectionDependentCubit( + super.initState, + ) { + final connectionRepository = getIt(); + + apiStatusSubscription = + connectionRepository.connectionStatusStream.listen(checkAuthStatus); + checkAuthStatus(connectionRepository.connectionStatus); + } + + void checkAuthStatus(final ConnectionStatus state) { + switch (state) { + case ConnectionStatus.nonexistent: + clear(); + isLoaded = false; + break; + case ConnectionStatus.connected: + if (!isLoaded) { + load(); + isLoaded = true; + } + break; + default: + break; + } + } + + late StreamSubscription apiStatusSubscription; + bool isLoaded = false; + + void load(); + void clear(); + + @override + Future close() { + apiStatusSubscription.cancel(); + return super.close(); + } +} diff --git a/lib/logic/cubit/app_config_dependent/authentication_dependend_state.dart b/lib/logic/cubit/server_connection_dependent/server_connection_dependent_state.dart similarity index 69% rename from lib/logic/cubit/app_config_dependent/authentication_dependend_state.dart rename to lib/logic/cubit/server_connection_dependent/server_connection_dependent_state.dart index 668d63d0..fe5a4782 100644 --- a/lib/logic/cubit/app_config_dependent/authentication_dependend_state.dart +++ b/lib/logic/cubit/server_connection_dependent/server_connection_dependent_state.dart @@ -1,4 +1,4 @@ -part of 'authentication_dependend_cubit.dart'; +part of 'server_connection_dependent_cubit.dart'; abstract class ServerInstallationDependendState extends Equatable { const ServerInstallationDependendState(); diff --git a/lib/logic/cubit/server_detailed_info/server_detailed_info_cubit.dart b/lib/logic/cubit/server_detailed_info/server_detailed_info_cubit.dart index b6a39733..9e760ebb 100644 --- a/lib/logic/cubit/server_detailed_info/server_detailed_info_cubit.dart +++ b/lib/logic/cubit/server_detailed_info/server_detailed_info_cubit.dart @@ -1,5 +1,5 @@ import 'package:selfprivacy/config/get_it_config.dart'; -import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart'; +import 'package:selfprivacy/logic/cubit/server_connection_dependent/server_connection_dependent_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_detailed_info/server_detailed_info_repository.dart'; import 'package:selfprivacy/logic/models/auto_upgrade_settings.dart'; import 'package:selfprivacy/logic/models/server_metadata.dart'; @@ -8,9 +8,9 @@ import 'package:selfprivacy/logic/models/timezone_settings.dart'; part 'server_detailed_info_state.dart'; class ServerDetailsCubit - extends ServerInstallationDependendCubit { + extends ServerConnectionDependentCubit { ServerDetailsCubit(final ServerInstallationCubit serverInstallationCubit) - : super(serverInstallationCubit, ServerDetailsInitial()); + : super(ServerDetailsInitial()); ServerDetailsRepository repository = ServerDetailsRepository(); diff --git a/lib/logic/cubit/server_installation/server_installation_cubit.dart b/lib/logic/cubit/server_installation/server_installation_cubit.dart index 95726fb4..709bc0b4 100644 --- a/lib/logic/cubit/server_installation/server_installation_cubit.dart +++ b/lib/logic/cubit/server_installation/server_installation_cubit.dart @@ -7,6 +7,7 @@ import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/backblaze.dart'; import 'package:selfprivacy/logic/api_maps/tls_options.dart'; +import 'package:selfprivacy/logic/get_it/api_connection_repository.dart'; import 'package:selfprivacy/logic/models/disk_size.dart'; import 'package:selfprivacy/logic/models/hive/backblaze_bucket.dart'; import 'package:selfprivacy/logic/models/hive/backups_credential.dart'; @@ -484,6 +485,7 @@ class ServerInstallationCubit extends Cubit { if (dkimCreated) { await repository.saveHasFinalChecked(true); emit(dataState.finish()); + getIt().init(); } else { runDelayed( finishCheckIfServerIsOkay, diff --git a/lib/logic/cubit/server_jobs/server_jobs_bloc.dart b/lib/logic/cubit/server_jobs/server_jobs_bloc.dart new file mode 100644 index 00000000..3b82ddd8 --- /dev/null +++ b/lib/logic/cubit/server_jobs/server_jobs_bloc.dart @@ -0,0 +1,87 @@ +import 'dart:async'; + +import 'package:equatable/equatable.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:selfprivacy/config/get_it_config.dart'; +import 'package:selfprivacy/logic/get_it/api_connection_repository.dart'; +import 'package:selfprivacy/logic/models/json/server_job.dart'; + +export 'package:provider/provider.dart'; + +part 'server_jobs_state.dart'; +part 'server_jobs_event.dart'; + +class ServerJobsBloc extends Bloc { + ServerJobsBloc() + : super( + ServerJobsInitialState(), + ) { + final apiConnectionRepository = getIt(); + _apiDataSubscription = apiConnectionRepository.dataStream.listen( + (final ApiData apiData) { + add( + ServerJobsListChanged([...apiData.serverJobs.data ?? []]), + ); + }, + ); + + on( + _mapServerJobsListChangedToState, + ); + on( + _mapRemoveServerJobToState, + ); + on( + _mapRemoveAllFinishedJobsToState, + ); + } + + StreamSubscription? _apiDataSubscription; + + Future _mapServerJobsListChangedToState( + final ServerJobsListChanged event, + final Emitter emit, + ) async { + if (event.serverJobList.isEmpty) { + emit(ServerJobsListEmptyState()); + return; + } + final newState = + ServerJobsListWithJobsState(serverJobList: event.serverJobList); + emit(newState); + } + + Future _mapRemoveServerJobToState( + final RemoveServerJob event, + final Emitter emit, + ) async { + await getIt().removeServerJob(event.uid); + } + + Future _mapRemoveAllFinishedJobsToState( + final RemoveAllFinishedJobs event, + final Emitter emit, + ) async { + final List finishedJobs = state.serverJobList + .where( + (final ServerJob job) => + job.status == JobStatusEnum.finished || + job.status == JobStatusEnum.error, + ) + .toList(); + for (final ServerJob job in finishedJobs) { + await getIt().removeServerJob(job.uid); + } + } + + @override + void onChange(final Change change) { + super.onChange(change); + } + + @override + Future close() { + _apiDataSubscription?.cancel(); + return super.close(); + } +} diff --git a/lib/logic/cubit/server_jobs/server_jobs_cubit.dart b/lib/logic/cubit/server_jobs/server_jobs_cubit.dart deleted file mode 100644 index 09d77b14..00000000 --- a/lib/logic/cubit/server_jobs/server_jobs_cubit.dart +++ /dev/null @@ -1,123 +0,0 @@ -import 'dart:async'; - -import 'package:easy_localization/easy_localization.dart'; -import 'package:selfprivacy/config/get_it_config.dart'; -import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart'; -import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart'; -import 'package:selfprivacy/logic/models/json/server_job.dart'; - -export 'package:provider/provider.dart'; - -part 'server_jobs_state.dart'; - -class ServerJobsCubit - extends ServerInstallationDependendCubit { - ServerJobsCubit(final ServerInstallationCubit serverInstallationCubit) - : super( - serverInstallationCubit, - ServerJobsState(), - ); - - Timer? timer; - final ServerApi api = ServerApi(); - - @override - void clear() async { - emit( - ServerJobsState(), - ); - if (timer != null && timer!.isActive) { - timer!.cancel(); - timer = null; - } - } - - @override - void load() async { - if (serverInstallationCubit.state is ServerInstallationFinished) { - final List jobs = await api.getServerJobs(); - emit( - ServerJobsState( - serverJobList: jobs, - ), - ); - timer = Timer(const Duration(seconds: 5), () => reload(useTimer: true)); - } - } - - Future migrateToBinds(final Map serviceToDisk) async { - final result = await api.migrateToBinds(serviceToDisk); - if (result.data == null) { - getIt().showSnackBar(result.message!); - return; - } - - emit( - ServerJobsState( - migrationJobUid: result.data, - ), - ); - } - - ServerJob? getServerJobByUid(final String uid) { - ServerJob? job; - - try { - job = state.serverJobList.firstWhere( - (final ServerJob job) => job.uid == uid, - ); - } catch (e) { - print(e); - } - - return job; - } - - Future removeServerJob(final String uid) async { - final result = await api.removeApiJob(uid); - if (!result.success) { - getIt().showSnackBar('jobs.generic_error'.tr()); - return; - } - - if (!result.data) { - getIt() - .showSnackBar(result.message ?? 'jobs.generic_error'.tr()); - return; - } - - emit( - ServerJobsState( - serverJobList: [ - for (final ServerJob job in state.serverJobList) - if (job.uid != uid) job, - ], - ), - ); - } - - Future removeAllFinishedJobs() async { - final List finishedJobs = state.serverJobList - .where( - (final ServerJob job) => - job.status == JobStatusEnum.finished || - job.status == JobStatusEnum.error, - ) - .toList(); - for (final ServerJob job in finishedJobs) { - await removeServerJob(job.uid); - } - } - - Future reload({final bool useTimer = false}) async { - final List jobs = await api.getServerJobs(); - emit( - ServerJobsState( - serverJobList: jobs, - ), - ); - if (useTimer) { - timer = Timer(const Duration(seconds: 5), () => reload(useTimer: true)); - } - } -} diff --git a/lib/logic/cubit/server_jobs/server_jobs_event.dart b/lib/logic/cubit/server_jobs/server_jobs_event.dart new file mode 100644 index 00000000..368be822 --- /dev/null +++ b/lib/logic/cubit/server_jobs/server_jobs_event.dart @@ -0,0 +1,28 @@ +part of 'server_jobs_bloc.dart'; + +sealed class ServerJobsEvent extends Equatable { + const ServerJobsEvent(); + + @override + List get props => []; +} + +class ServerJobsListChanged extends ServerJobsEvent { + const ServerJobsListChanged(this.serverJobList); + + final List serverJobList; + + @override + List get props => [serverJobList]; +} + +class RemoveServerJob extends ServerJobsEvent { + const RemoveServerJob(this.uid); + + final String uid; + + @override + List get props => [uid]; +} + +class RemoveAllFinishedJobs extends ServerJobsEvent {} diff --git a/lib/logic/cubit/server_jobs/server_jobs_state.dart b/lib/logic/cubit/server_jobs/server_jobs_state.dart index 9a18dd51..09f7e9ff 100644 --- a/lib/logic/cubit/server_jobs/server_jobs_state.dart +++ b/lib/logic/cubit/server_jobs/server_jobs_state.dart @@ -1,15 +1,16 @@ -part of 'server_jobs_cubit.dart'; +part of 'server_jobs_bloc.dart'; -class ServerJobsState extends ServerInstallationDependendState { +sealed class ServerJobsState extends Equatable { ServerJobsState({ - final serverJobList = const [], - this.migrationJobUid, - }) { - _serverJobList = serverJobList; - } + final int? hashCode, + }) : _hashCode = hashCode ?? Object.hashAll([]); - late final List _serverJobList; - final String? migrationJobUid; + final int? _hashCode; + + final apiConnectionRepository = getIt(); + + List get _serverJobList => + apiConnectionRepository.apiData.serverJobs.data ?? []; List get serverJobList { try { @@ -36,14 +37,19 @@ class ServerJobsState extends ServerInstallationDependendState { ); @override - List get props => [migrationJobUid, _serverJobList]; - - ServerJobsState copyWith({ - final List? serverJobList, - final String? migrationJobUid, - }) => - ServerJobsState( - serverJobList: serverJobList ?? _serverJobList, - migrationJobUid: migrationJobUid ?? this.migrationJobUid, - ); + List get props => [_hashCode]; +} + +class ServerJobsInitialState extends ServerJobsState { + ServerJobsInitialState() : super(hashCode: Object.hashAll([])); +} + +class ServerJobsListEmptyState extends ServerJobsState { + ServerJobsListEmptyState() : super(hashCode: Object.hashAll([])); +} + +class ServerJobsListWithJobsState extends ServerJobsState { + ServerJobsListWithJobsState({ + required final List serverJobList, + }) : super(hashCode: Object.hashAll([...serverJobList])); } diff --git a/lib/logic/cubit/server_volumes/server_volume_cubit.dart b/lib/logic/cubit/server_volumes/server_volume_cubit.dart index bf30c8c0..327b9623 100644 --- a/lib/logic/cubit/server_volumes/server_volume_cubit.dart +++ b/lib/logic/cubit/server_volumes/server_volume_cubit.dart @@ -2,7 +2,7 @@ import 'dart:async'; import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.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/cubit/server_connection_dependent/server_connection_dependent_cubit.dart'; import 'package:selfprivacy/logic/cubit/provider_volumes/provider_volume_cubit.dart'; import 'package:selfprivacy/logic/models/disk_status.dart'; import 'package:selfprivacy/logic/models/json/server_disk_volume.dart'; @@ -10,11 +10,12 @@ import 'package:selfprivacy/logic/models/json/server_disk_volume.dart'; part 'server_volume_state.dart'; class ApiServerVolumeCubit - extends ServerInstallationDependendCubit { + extends ServerConnectionDependentCubit { ApiServerVolumeCubit( final ServerInstallationCubit serverInstallationCubit, this.providerVolumeCubit, - ) : super(serverInstallationCubit, ApiServerVolumeState.initial()) { + ) : super(ApiServerVolumeState.initial()) { + // TODO: Remove this connection between cubits _providerVolumeSubscription = providerVolumeCubit.stream.listen(checkProviderVolumes); } @@ -23,9 +24,7 @@ class ApiServerVolumeCubit @override Future load() async { - if (serverInstallationCubit.state is ServerInstallationFinished) { - unawaited(reload()); - } + unawaited(reload()); } late StreamSubscription _providerVolumeSubscription; diff --git a/lib/logic/cubit/services/services_cubit.dart b/lib/logic/cubit/services/services_cubit.dart index 60476c2d..a9849700 100644 --- a/lib/logic/cubit/services/services_cubit.dart +++ b/lib/logic/cubit/services/services_cubit.dart @@ -3,28 +3,26 @@ import 'dart:async'; import 'package:easy_localization/easy_localization.dart'; import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart'; -import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart'; +import 'package:selfprivacy/logic/cubit/server_connection_dependent/server_connection_dependent_cubit.dart'; import 'package:selfprivacy/logic/models/service.dart'; part 'services_state.dart'; -class ServicesCubit extends ServerInstallationDependendCubit { +class ServicesCubit extends ServerConnectionDependentCubit { ServicesCubit(final ServerInstallationCubit serverInstallationCubit) - : super(serverInstallationCubit, const ServicesState.empty()); + : super(const ServicesState.empty()); final ServerApi api = ServerApi(); Timer? timer; @override Future load() async { - if (serverInstallationCubit.state is ServerInstallationFinished) { - final List services = await api.getAllServices(); - emit( - ServicesState( - services: services, - lockedServices: const [], - ), - ); - timer = Timer(const Duration(seconds: 10), () => reload(useTimer: true)); - } + final List services = await api.getAllServices(); + emit( + ServicesState( + services: services, + lockedServices: const [], + ), + ); + timer = Timer(const Duration(seconds: 10), () => reload(useTimer: true)); } Future reload({final bool useTimer = false}) async { diff --git a/lib/logic/cubit/users/users_cubit.dart b/lib/logic/cubit/users/users_cubit.dart index cb717441..037b7c39 100644 --- a/lib/logic/cubit/users/users_cubit.dart +++ b/lib/logic/cubit/users/users_cubit.dart @@ -5,17 +5,17 @@ import 'package:hive/hive.dart'; import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/config/hive_config.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart'; -import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart'; +import 'package:selfprivacy/logic/cubit/server_connection_dependent/server_connection_dependent_cubit.dart'; +import 'package:selfprivacy/logic/get_it/api_connection_repository.dart'; import 'package:selfprivacy/logic/models/hive/user.dart'; export 'package:provider/provider.dart'; part 'users_state.dart'; -class UsersCubit extends ServerInstallationDependendCubit { +class UsersCubit extends ServerConnectionDependentCubit { UsersCubit(final ServerInstallationCubit serverInstallationCubit) : super( - serverInstallationCubit, const UsersState( [], false, @@ -28,9 +28,6 @@ class UsersCubit extends ServerInstallationDependendCubit { @override Future load() async { - if (serverInstallationCubit.state is! ServerInstallationFinished) { - return; - } final List loadedUsers = box.values.toList(); if (loadedUsers.isNotEmpty) { emit( @@ -45,7 +42,8 @@ class UsersCubit extends ServerInstallationDependendCubit { } Future refresh() async { - if (serverInstallationCubit.state is! ServerInstallationFinished) { + if (getIt().connectionStatus != + ConnectionStatus.nonexistent) { return; } emit(state.copyWith(isLoading: true)); diff --git a/lib/logic/get_it/api_connection_repository.dart b/lib/logic/get_it/api_connection_repository.dart new file mode 100644 index 00000000..d46f0010 --- /dev/null +++ b/lib/logic/get_it/api_connection_repository.dart @@ -0,0 +1,164 @@ +import 'dart:async'; + +import 'package:hive/hive.dart'; +import 'package:pub_semver/pub_semver.dart'; +import 'package:selfprivacy/config/get_it_config.dart'; +import 'package:selfprivacy/config/hive_config.dart'; +import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart'; +import 'package:selfprivacy/logic/models/hive/server_details.dart'; +import 'package:selfprivacy/logic/models/hive/server_domain.dart'; +import 'package:selfprivacy/logic/models/json/server_job.dart'; + +/// Repository for all API calls +/// Stores the current state of all data from API and exposes it to Blocs. +class ApiConnectionRepository { + Box box = Hive.box(BNames.serverInstallationBox); + final ServerApi api = ServerApi(); + + final ApiData _apiData = ApiData(); + + ApiData get apiData => _apiData; + + ConnectionStatus connectionStatus = ConnectionStatus.nonexistent; + + final _dataStream = StreamController.broadcast(); + final _connectionStatusStream = + StreamController.broadcast(); + + Stream get dataStream => _dataStream.stream; + Stream get connectionStatusStream => + _connectionStatusStream.stream; + + ConnectionStatus get currentConnectionStatus => connectionStatus; + + Timer? _timer; + + Future removeServerJob(final String uid) async { + await api.removeApiJob(uid); + _apiData.serverJobs.data + ?.removeWhere((final ServerJob element) => element.uid == uid); + _dataStream.add(_apiData); + } + + void dispose() { + _dataStream.close(); + _connectionStatusStream.close(); + _timer?.cancel(); + } + + ServerHostingDetails? get serverDetails => + getIt().serverDetails; + ServerDomain? get serverDomain => getIt().serverDomain; + + void init() async { + final serverDetails = getIt().serverDetails; + final hasFinalChecked = + box.get(BNames.hasFinalChecked, defaultValue: false); + if (serverDetails == null || !hasFinalChecked) { + return; + } + connectionStatus = ConnectionStatus.reconnecting; + _connectionStatusStream.add(connectionStatus); + + final String? apiVersion = await api.getApiVersion(); + if (apiVersion == null) { + connectionStatus = ConnectionStatus.offline; + _connectionStatusStream.add(connectionStatus); + return; + } else { + connectionStatus = ConnectionStatus.connected; + _connectionStatusStream.add(connectionStatus); + _apiData.apiVersion.data = apiVersion; + _dataStream.add(_apiData); + } + + _apiData.serverJobs.data = await api.getServerJobs(); + _dataStream.add(_apiData); + + // Use timer to periodically check for new jobs + _timer = Timer.periodic( + const Duration(seconds: 10), + reload, + ); + } + + void reload(final Timer timer) async { + final serverDetails = getIt().serverDetails; + if (serverDetails == null) { + return; + } + + final String? apiVersion = await api.getApiVersion(); + if (apiVersion == null) { + connectionStatus = ConnectionStatus.offline; + _connectionStatusStream.add(connectionStatus); + return; + } else { + connectionStatus = ConnectionStatus.connected; + _connectionStatusStream.add(connectionStatus); + _apiData.apiVersion.data = apiVersion; + } + + if (VersionConstraint.parse(_apiData.apiVersion.requiredApiVersion) + .allows(Version.parse(apiVersion))) { + final jobs = await api.getServerJobs(); + if (Object.hashAll(_apiData.serverJobs.data ?? []) != + Object.hashAll(jobs)) { + _apiData.serverJobs.data = [...jobs]; + _dataStream.add(_apiData); + } + } + } +} + +class ApiData { + ApiData() + : serverJobs = ApiDataElement>(null), + apiVersion = ApiDataElement(null); + + ApiDataElement> serverJobs; + ApiDataElement apiVersion; +} + +enum ConnectionStatus { + nonexistent, + connected, + reconnecting, + offline, + unauthorized, +} + +class ApiDataElement { + ApiDataElement( + final T? data, { + this.requiredApiVersion = '>=2.3.0', + this.ttl = 60, + }) : _data = data, + _lastUpdated = DateTime.now(); + + T? _data; + final String requiredApiVersion; + + /// TTL of the data in seconds + final int ttl; + + /// Timestamp of when the data was last updated + DateTime _lastUpdated; + + bool get isExpired { + final now = DateTime.now(); + final difference = now.difference(_lastUpdated); + return difference.inSeconds > ttl; + } + + T? get data => _data; + + /// Sets the data and updates the lastUpdated timestamp + set data(final T? data) { + _data = data; + _lastUpdated = DateTime.now(); + } + + /// Returns the last time the data was updated + DateTime get lastUpdated => _lastUpdated; +} diff --git a/lib/logic/models/json/server_job.dart b/lib/logic/models/json/server_job.dart index 70d6d103..6b60492e 100644 --- a/lib/logic/models/json/server_job.dart +++ b/lib/logic/models/json/server_job.dart @@ -1,13 +1,14 @@ +import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/server_api.graphql.dart'; part 'server_job.g.dart'; @JsonSerializable() -class ServerJob { +class ServerJob extends Equatable { factory ServerJob.fromJson(final Map json) => _$ServerJobFromJson(json); - ServerJob({ + const ServerJob({ required this.name, required this.description, required this.status, @@ -50,6 +51,22 @@ class ServerJob { final String? result; final String? statusText; final DateTime? finishedAt; + + @override + List get props => [ + name, + description, + status, + uid, + typeId, + updatedAt, + createdAt, + error, + progress, + result, + statusText, + finishedAt, + ]; } enum JobStatusEnum { diff --git a/lib/ui/components/jobs_content/jobs_content.dart b/lib/ui/components/jobs_content/jobs_content.dart index 8db853ac..d71636ee 100644 --- a/lib/ui/components/jobs_content/jobs_content.dart +++ b/lib/ui/components/jobs_content/jobs_content.dart @@ -4,7 +4,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:selfprivacy/config/brand_theme.dart'; import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; -import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_cubit.dart'; +import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_bloc.dart'; import 'package:selfprivacy/logic/models/json/server_job.dart'; import 'package:selfprivacy/ui/components/buttons/brand_button.dart'; import 'package:selfprivacy/ui/components/brand_loader/brand_loader.dart'; @@ -22,10 +22,10 @@ class JobsContent extends StatelessWidget { @override Widget build(final BuildContext context) { final List serverJobs = - context.watch().state.serverJobList; + context.watch().state.serverJobList; final bool hasRemovableJobs = - context.watch().state.hasRemovableJobs; + context.watch().state.hasRemovableJobs; return BlocBuilder( builder: (final context, final state) { @@ -152,8 +152,8 @@ class JobsContent extends StatelessWidget { IconButton( onPressed: hasRemovableJobs ? () => context - .read() - .removeAllFinishedJobs() + .read() + .add(RemoveAllFinishedJobs()) : null, icon: const Icon(Icons.clear_all), color: Theme.of(context).colorScheme.onBackground, @@ -172,7 +172,9 @@ class JobsContent extends StatelessWidget { serverJob: job, ), onDismissed: (final direction) { - context.read().removeServerJob(job.uid); + context.read().add( + RemoveServerJob(job.uid), + ); }, ), ), diff --git a/lib/ui/pages/backups/backup_details.dart b/lib/ui/pages/backups/backup_details.dart index fa2f3b61..c965a2cc 100644 --- a/lib/ui/pages/backups/backup_details.dart +++ b/lib/ui/pages/backups/backup_details.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/logic/cubit/backups/backups_cubit.dart'; -import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_cubit.dart'; +import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_bloc.dart'; import 'package:selfprivacy/logic/cubit/services/services_cubit.dart'; import 'package:selfprivacy/logic/models/backup.dart'; import 'package:selfprivacy/logic/models/json/server_job.dart'; @@ -43,7 +43,7 @@ class BackupDetailsPage extends StatelessWidget { context.watch().state.servicesThatCanBeBackedUp; final Duration? autobackupPeriod = backupsState.autobackupPeriod; final List backupJobs = context - .watch() + .watch() .state .backupJobList .where((final job) => job.status != JobStatusEnum.finished) diff --git a/lib/ui/pages/backups/change_period_modal.dart b/lib/ui/pages/backups/change_period_modal.dart index dbee981e..a02b0c85 100644 --- a/lib/ui/pages/backups/change_period_modal.dart +++ b/lib/ui/pages/backups/change_period_modal.dart @@ -2,7 +2,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/logic/cubit/backups/backups_cubit.dart'; -import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_cubit.dart'; +import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_bloc.dart'; import 'package:selfprivacy/utils/extensions/duration.dart'; class ChangeAutobackupsPeriodModal extends StatefulWidget { diff --git a/lib/ui/pages/backups/change_rotation_quotas_modal.dart b/lib/ui/pages/backups/change_rotation_quotas_modal.dart index 0ce97eee..dab54d02 100644 --- a/lib/ui/pages/backups/change_rotation_quotas_modal.dart +++ b/lib/ui/pages/backups/change_rotation_quotas_modal.dart @@ -2,7 +2,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/logic/cubit/backups/backups_cubit.dart'; -import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_cubit.dart'; +import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_bloc.dart'; import 'package:selfprivacy/logic/models/backup.dart'; class ChangeRotationQuotasModal extends StatefulWidget { diff --git a/lib/ui/pages/backups/copy_encryption_key_modal.dart b/lib/ui/pages/backups/copy_encryption_key_modal.dart index fc16c356..96d64ce1 100644 --- a/lib/ui/pages/backups/copy_encryption_key_modal.dart +++ b/lib/ui/pages/backups/copy_encryption_key_modal.dart @@ -4,7 +4,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/logic/cubit/backups/backups_cubit.dart'; -import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_cubit.dart'; +import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_bloc.dart'; import 'package:selfprivacy/ui/components/info_box/info_box.dart'; import 'package:selfprivacy/utils/platform_adapter.dart'; diff --git a/lib/ui/pages/backups/create_backups_modal.dart b/lib/ui/pages/backups/create_backups_modal.dart index 3f461da1..891f26cf 100644 --- a/lib/ui/pages/backups/create_backups_modal.dart +++ b/lib/ui/pages/backups/create_backups_modal.dart @@ -2,7 +2,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:selfprivacy/logic/cubit/backups/backups_cubit.dart'; -import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_cubit.dart'; +import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_bloc.dart'; import 'package:selfprivacy/logic/models/json/server_job.dart'; import 'package:selfprivacy/logic/models/service.dart'; @@ -29,7 +29,7 @@ class _CreateBackupsModalState extends State { void initState() { super.initState(); final List busyServices = context - .read() + .read() .state .backupJobList .where( @@ -48,7 +48,7 @@ class _CreateBackupsModalState extends State { @override Widget build(final BuildContext context) { final List busyServices = context - .watch() + .watch() .state .backupJobList .where( diff --git a/lib/ui/pages/backups/snapshot_modal.dart b/lib/ui/pages/backups/snapshot_modal.dart index 37b10a17..411da72a 100644 --- a/lib/ui/pages/backups/snapshot_modal.dart +++ b/lib/ui/pages/backups/snapshot_modal.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/logic/cubit/backups/backups_cubit.dart'; -import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_cubit.dart'; +import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_bloc.dart'; import 'package:selfprivacy/logic/cubit/services/services_cubit.dart'; import 'package:selfprivacy/logic/models/backup.dart'; import 'package:selfprivacy/logic/models/json/server_job.dart'; @@ -34,7 +34,7 @@ class _SnapshotModalState extends State { @override Widget build(final BuildContext context) { final List busyServices = context - .watch() + .watch() .state .backupJobList .where( diff --git a/lib/ui/pages/more/app_settings/developer_settings.dart b/lib/ui/pages/more/app_settings/developer_settings.dart index aa5b2368..e7881510 100644 --- a/lib/ui/pages/more/app_settings/developer_settings.dart +++ b/lib/ui/pages/more/app_settings/developer_settings.dart @@ -1,9 +1,11 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; +import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/logic/api_maps/tls_options.dart'; import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart'; import 'package:selfprivacy/logic/cubit/devices/devices_cubit.dart'; import 'package:selfprivacy/logic/cubit/recovery_key/recovery_key_cubit.dart'; +import 'package:selfprivacy/logic/get_it/api_connection_repository.dart'; import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; import 'package:easy_localization/easy_localization.dart'; @@ -100,6 +102,14 @@ class _DeveloperSettingsPageState extends State { context.watch().state.loadingStatus.toString(), ), ), + ListTile( + title: const Text('ApiConnectionRepository status'), + subtitle: Text( + getIt() + .currentConnectionStatus + .toString(), + ), + ), ], ); } diff --git a/lib/ui/pages/server_storage/binds_migration/services_migration.dart b/lib/ui/pages/server_storage/binds_migration/services_migration.dart index e981cb03..3d8acbc6 100644 --- a/lib/ui/pages/server_storage/binds_migration/services_migration.dart +++ b/lib/ui/pages/server_storage/binds_migration/services_migration.dart @@ -1,7 +1,7 @@ import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_cubit.dart'; +import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_bloc.dart'; import 'package:selfprivacy/logic/cubit/services/services_cubit.dart'; import 'package:selfprivacy/logic/models/disk_size.dart'; import 'package:selfprivacy/logic/models/service.dart'; diff --git a/lib/ui/pages/server_storage/extending_volume.dart b/lib/ui/pages/server_storage/extending_volume.dart index fd8213b1..0293ab4a 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:auto_route/auto_route.dart'; 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:flutter_bloc/flutter_bloc.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'; diff --git a/lib/ui/pages/server_storage/storage_card.dart b/lib/ui/pages/server_storage/storage_card.dart index 2c6d334f..240c99c9 100644 --- a/lib/ui/pages/server_storage/storage_card.dart +++ b/lib/ui/pages/server_storage/storage_card.dart @@ -1,7 +1,7 @@ import 'package:auto_route/auto_route.dart'; 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/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/logic/models/state_types.dart'; import 'package:selfprivacy/ui/components/icon_status_mask/icon_status_mask.dart'; import 'package:selfprivacy/logic/models/disk_status.dart'; diff --git a/lib/ui/pages/setup/initializing/broken_domain_outlined_card.dart b/lib/ui/pages/setup/initializing/broken_domain_outlined_card.dart index 97e18bfe..19d7a175 100644 --- a/lib/ui/pages/setup/initializing/broken_domain_outlined_card.dart +++ b/lib/ui/pages/setup/initializing/broken_domain_outlined_card.dart @@ -1,6 +1,6 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:selfprivacy/logic/cubit/providers/providers_cubit.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:selfprivacy/logic/cubit/support_system/support_system_cubit.dart'; import 'package:selfprivacy/logic/providers/dns_providers/dns_provider.dart'; import 'package:selfprivacy/ui/components/cards/filled_card.dart'; diff --git a/lib/ui/pages/setup/initializing/dns_provider_picker.dart b/lib/ui/pages/setup/initializing/dns_provider_picker.dart index 69560f5c..5c3f6244 100644 --- a/lib/ui/pages/setup/initializing/dns_provider_picker.dart +++ b/lib/ui/pages/setup/initializing/dns_provider_picker.dart @@ -2,8 +2,8 @@ import 'package:cubit_form/cubit_form.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart'; import 'package:selfprivacy/logic/cubit/forms/setup/initializing/dns_provider_form_cubit.dart'; +import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/logic/cubit/support_system/support_system_cubit.dart'; import 'package:selfprivacy/logic/models/hive/server_domain.dart'; import 'package:selfprivacy/ui/components/buttons/brand_button.dart'; diff --git a/lib/ui/pages/setup/initializing/server_provider_picker.dart b/lib/ui/pages/setup/initializing/server_provider_picker.dart index 41c4c9ea..5a842be1 100644 --- a/lib/ui/pages/setup/initializing/server_provider_picker.dart +++ b/lib/ui/pages/setup/initializing/server_provider_picker.dart @@ -2,8 +2,8 @@ import 'package:cubit_form/cubit_form.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart'; import 'package:selfprivacy/logic/cubit/forms/setup/initializing/server_provider_form_cubit.dart'; +import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/logic/cubit/support_system/support_system_cubit.dart'; import 'package:selfprivacy/logic/models/hive/server_details.dart'; import 'package:selfprivacy/ui/components/buttons/brand_button.dart'; diff --git a/lib/ui/pages/setup/initializing/server_type_picker.dart b/lib/ui/pages/setup/initializing/server_type_picker.dart index 932cbd93..067746f6 100644 --- a/lib/ui/pages/setup/initializing/server_type_picker.dart +++ b/lib/ui/pages/setup/initializing/server_type_picker.dart @@ -1,8 +1,8 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:selfprivacy/illustrations/stray_deer.dart'; -import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart'; import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart'; +import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/logic/models/price.dart'; import 'package:selfprivacy/logic/models/server_provider_location.dart'; import 'package:selfprivacy/logic/models/server_type.dart'; diff --git a/lib/ui/pages/setup/recovering/recovery_confirm_server.dart b/lib/ui/pages/setup/recovering/recovery_confirm_server.dart index 3c0ade69..8e0ff022 100644 --- a/lib/ui/pages/setup/recovering/recovery_confirm_server.dart +++ b/lib/ui/pages/setup/recovering/recovery_confirm_server.dart @@ -1,6 +1,6 @@ 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/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/logic/models/server_basic_info.dart'; import 'package:selfprivacy/ui/components/buttons/brand_button.dart'; import 'package:selfprivacy/ui/components/cards/filled_card.dart'; From a5e7725733b2ea488b68da9b393ce75fb8efaa46 Mon Sep 17 00:00:00 2001 From: Inex Code Date: Mon, 29 Jan 2024 17:54:09 +0400 Subject: [PATCH 05/34] refactor: Rewrite backups cubit to bloc, using ApiRepo streams --- lib/config/bloc_config.dart | 9 +- lib/config/get_it_config.dart | 1 + lib/logic/bloc/backups/backups_bloc.dart | 396 ++++++++++++++++++ lib/logic/bloc/backups/backups_event.dart | 89 ++++ lib/logic/bloc/backups/backups_state.dart | 168 ++++++++ .../server_jobs/server_jobs_bloc.dart | 1 - .../server_jobs/server_jobs_event.dart | 0 .../server_jobs/server_jobs_state.dart | 0 lib/logic/cubit/backups/backups_cubit.dart | 278 ------------ lib/logic/cubit/backups/backups_state.dart | 61 --- .../connection_status_bloc.dart | 1 - .../cubit/dns_records/dns_records_cubit.dart | 1 - .../server_connection_dependent_cubit.dart | 1 - .../server_installation_cubit.dart | 3 +- .../server_installation_repository.dart | 16 +- lib/logic/cubit/users/users_cubit.dart | 1 - lib/logic/get_it/api_config.dart | 18 +- .../get_it/api_connection_repository.dart | 88 +++- lib/logic/models/backup.dart | 48 ++- lib/logic/models/hive/backblaze_bucket.dart | 15 + .../components/jobs_content/jobs_content.dart | 2 +- lib/ui/pages/backups/backup_details.dart | 43 +- lib/ui/pages/backups/backups_list.dart | 14 +- lib/ui/pages/backups/change_period_modal.dart | 12 +- .../backups/change_rotation_quotas_modal.dart | 14 +- .../backups/copy_encryption_key_modal.dart | 6 +- .../pages/backups/create_backups_modal.dart | 8 +- lib/ui/pages/backups/snapshot_modal.dart | 10 +- .../more/app_settings/developer_settings.dart | 1 - lib/ui/pages/providers/providers.dart | 4 +- .../binds_migration/services_migration.dart | 2 +- 31 files changed, 859 insertions(+), 452 deletions(-) create mode 100644 lib/logic/bloc/backups/backups_bloc.dart create mode 100644 lib/logic/bloc/backups/backups_event.dart create mode 100644 lib/logic/bloc/backups/backups_state.dart rename lib/logic/{cubit => bloc}/server_jobs/server_jobs_bloc.dart (96%) rename lib/logic/{cubit => bloc}/server_jobs/server_jobs_event.dart (100%) rename lib/logic/{cubit => bloc}/server_jobs/server_jobs_state.dart (100%) delete mode 100644 lib/logic/cubit/backups/backups_cubit.dart delete mode 100644 lib/logic/cubit/backups/backups_state.dart diff --git a/lib/config/bloc_config.dart b/lib/config/bloc_config.dart index 34b880dc..b7d87f2e 100644 --- a/lib/config/bloc_config.dart +++ b/lib/config/bloc_config.dart @@ -1,15 +1,15 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart'; import 'package:selfprivacy/logic/cubit/connection_status/connection_status_bloc.dart'; import 'package:selfprivacy/logic/cubit/devices/devices_cubit.dart'; import 'package:selfprivacy/logic/cubit/recovery_key/recovery_key_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_detailed_info/server_detailed_info_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart'; -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/client_jobs/client_jobs_cubit.dart'; -import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_bloc.dart'; +import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart'; import 'package:selfprivacy/logic/cubit/server_volumes/server_volume_cubit.dart'; import 'package:selfprivacy/logic/cubit/services/services_cubit.dart'; import 'package:selfprivacy/logic/cubit/support_system/support_system_cubit.dart'; @@ -29,7 +29,7 @@ class BlocAndProviderConfig extends StatelessWidget { final supportSystemCubit = SupportSystemCubit(); final usersCubit = UsersCubit(serverInstallationCubit); final servicesCubit = ServicesCubit(serverInstallationCubit); - final backupsCubit = BackupsCubit(serverInstallationCubit); + final backupsBloc = BackupsBloc(); final dnsRecordsCubit = DnsRecordsCubit(serverInstallationCubit); final recoveryKeyCubit = RecoveryKeyCubit(serverInstallationCubit); final apiDevicesCubit = ApiDevicesCubit(serverInstallationCubit); @@ -65,8 +65,7 @@ class BlocAndProviderConfig extends StatelessWidget { lazy: false, ), BlocProvider( - create: (final _) => backupsCubit, - lazy: false, + create: (final _) => backupsBloc, ), BlocProvider( create: (final _) => dnsRecordsCubit, diff --git a/lib/config/get_it_config.dart b/lib/config/get_it_config.dart index c67c1e32..b8ac1866 100644 --- a/lib/config/get_it_config.dart +++ b/lib/config/get_it_config.dart @@ -7,6 +7,7 @@ import 'package:selfprivacy/logic/get_it/navigation.dart'; export 'package:selfprivacy/logic/get_it/api_config.dart'; export 'package:selfprivacy/logic/get_it/console.dart'; export 'package:selfprivacy/logic/get_it/navigation.dart'; +export 'package:selfprivacy/logic/get_it/api_connection_repository.dart'; final GetIt getIt = GetIt.instance; diff --git a/lib/logic/bloc/backups/backups_bloc.dart b/lib/logic/bloc/backups/backups_bloc.dart new file mode 100644 index 00000000..55d4753a --- /dev/null +++ b/lib/logic/bloc/backups/backups_bloc.dart @@ -0,0 +1,396 @@ +import 'dart:async'; + +import 'package:easy_localization/easy_localization.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:selfprivacy/config/get_it_config.dart'; +import 'package:selfprivacy/logic/api_maps/rest_maps/backblaze.dart'; +import 'package:selfprivacy/logic/models/backup.dart'; +import 'package:selfprivacy/logic/models/hive/backblaze_bucket.dart'; +import 'package:selfprivacy/logic/models/hive/backups_credential.dart'; +import 'package:selfprivacy/logic/models/initialize_repository_input.dart'; +import 'package:selfprivacy/logic/models/json/server_job.dart'; +import 'package:selfprivacy/logic/models/service.dart'; + +part 'backups_event.dart'; +part 'backups_state.dart'; + +class BackupsBloc extends Bloc { + BackupsBloc() : super(BackupsInitial()) { + final connectionRepository = getIt(); + + _apiStatusSubscription = connectionRepository.connectionStatusStream + .listen((final ConnectionStatus connectionStatus) { + switch (connectionStatus) { + case ConnectionStatus.nonexistent: + add(const BackupsServerReset()); + isLoaded = false; + break; + case ConnectionStatus.connected: + if (!isLoaded) { + add(const BackupsServerLoaded()); + isLoaded = true; + } + break; + default: + break; + } + }); + + _apiDataSubscription = connectionRepository.dataStream.listen( + (final ApiData apiData) { + if (apiData.backups.data == null || apiData.backupConfig.data == null) { + add(const BackupsServerReset()); + isLoaded = false; + } else { + add( + BackupsStateChanged( + apiData.backups.data!, + apiData.backupConfig.data, + ), + ); + isLoaded = true; + } + }, + ); + + if (connectionRepository.connectionStatus == ConnectionStatus.connected) { + add(const BackupsServerLoaded()); + isLoaded = true; + } + + on( + _loadState, + ); + on( + _resetState, + ); + on( + _updateState, + ); + on( + _initializeRepository, + ); + on( + _forceSnapshotListUpdate, + ); + on( + _createBackups, + ); + on( + _restoreBackup, + ); + on( + _setAutobackupPeriod, + ); + on( + _setAutobackupQuotas, + ); + on( + _forgetSnapshot, + ); + } + + final BackblazeApi backblaze = BackblazeApi(); + + Future _loadState( + final BackupsServerLoaded event, + final Emitter emit, + ) async { + BackblazeBucket? bucket = getIt().backblazeBucket; + final backups = getIt().apiData.backups; + final backupConfig = getIt().apiData.backupConfig; + if (backupConfig.data == null || backups.data == null) { + emit(BackupsLoading()); + return; + } + if (bucket != null && + backupConfig.data!.encryptionKey != bucket.encryptionKey) { + bucket = bucket.copyWith( + encryptionKey: backupConfig.data!.encryptionKey, + ); + await getIt().setBackblazeBucket(bucket); + } + if (backupConfig.data!.isInitialized) { + emit( + BackupsInitialized( + backblazeBucket: bucket, + backupConfig: backupConfig.data, + backups: backups.data ?? [], + ), + ); + } else { + emit(BackupsUnititialized()); + } + } + + Future _resetState( + final BackupsServerReset event, + final Emitter emit, + ) async { + emit(BackupsInitial()); + } + + Future _initializeRepository( + final InitializeBackupsRepository event, + final Emitter emit, + ) async { + if (state is! BackupsUnititialized) { + return; + } + emit(BackupsInitializing()); + final String? encryptionKey = getIt() + .apiData + .backupConfig + .data + ?.encryptionKey; + if (encryptionKey == null) { + emit(BackupsUnititialized()); + getIt() + .showSnackBar("Couldn't get encryption key from your server."); + return; + } + + final BackblazeBucket bucket; + + if (state.backblazeBucket == null) { + final String domain = getIt() + .serverDomain! + .domainName + .replaceAll(RegExp(r'[^a-zA-Z0-9]'), '-'); + final int serverId = getIt().serverDetails!.id; + String bucketName = + '${DateTime.now().millisecondsSinceEpoch}-$serverId-$domain'; + if (bucketName.length > 49) { + bucketName = bucketName.substring(0, 49); + } + final String bucketId = await backblaze.createBucket(bucketName); + + final BackblazeApplicationKey key = await backblaze.createKey(bucketId); + bucket = BackblazeBucket( + bucketId: bucketId, + bucketName: bucketName, + applicationKey: key.applicationKey, + applicationKeyId: key.applicationKeyId, + encryptionKey: encryptionKey, + ); + + await getIt().setBackblazeBucket(bucket); + emit(state.copyWith(backblazeBucket: bucket)); + } else { + bucket = state.backblazeBucket!; + } + + final GenericResult result = + await getIt().api.initializeRepository( + InitializeRepositoryInput( + provider: BackupsProviderType.backblaze, + locationId: bucket.bucketId, + locationName: bucket.bucketName, + login: bucket.applicationKeyId, + password: bucket.applicationKey, + ), + ); + if (result.success == false) { + getIt().showSnackBar( + result.message ?? "Couldn't initialize repository on your server."); + emit(BackupsUnititialized()); + return; + } + getIt().apiData.backupConfig.invalidate(); + getIt().apiData.backups.invalidate(); + await getIt().reload(null); + + getIt().showSnackBar( + 'Backups repository is now initializing. It may take a while.', + ); + } + + Future _updateState( + final BackupsStateChanged event, + final Emitter emit, + ) async { + if (event.backupConfiguration == null || + event.backupConfiguration!.isInitialized == false) { + emit(BackupsUnititialized()); + return; + } + final BackblazeBucket? bucket = getIt().backblazeBucket; + emit( + BackupsInitialized( + backblazeBucket: bucket, + backupConfig: event.backupConfiguration, + backups: event.backups, + ), + ); + } + + Future _forceSnapshotListUpdate( + final ForceSnapshotListUpdate event, + final Emitter emit, + ) async { + final currentState = state; + if (currentState is BackupsInitialized) { + emit(BackupsBusy.fromState(currentState)); + getIt().showSnackBar('backup.refetching_list'.tr()); + await getIt().api.forceBackupListReload(); + getIt().apiData.backups.invalidate(); + emit(currentState); + } + } + + Future _createBackups( + final CreateBackups event, + final Emitter emit, + ) async { + final currentState = state; + if (currentState is BackupsInitialized) { + emit(BackupsBusy.fromState(currentState)); + for (final service in event.services) { + final GenericResult result = + await getIt().api.startBackup( + service.id, + ); + if (result.success == false) { + getIt() + .showSnackBar(result.message ?? 'Unknown error'); + } + if (result.data != null) { + getIt() + .apiData + .serverJobs + .data + ?.add(result.data!); + } + } + emit(currentState); + getIt().emitData(); + } + } + + Future _restoreBackup( + final RestoreBackup event, + final Emitter emit, + ) async { + final currentState = state; + if (currentState is BackupsInitialized) { + emit(BackupsBusy.fromState(currentState)); + final GenericResult result = + await getIt().api.restoreBackup( + event.backupId, + event.restoreStrategy, + ); + if (result.success == false) { + getIt() + .showSnackBar(result.message ?? 'Unknown error'); + } + emit(currentState); + } + } + + Future _setAutobackupPeriod( + final SetAutobackupPeriod event, + final Emitter emit, + ) async { + final currentState = state; + if (currentState is BackupsInitialized) { + emit(BackupsBusy.fromState(currentState)); + final GenericResult result = + await getIt().api.setAutobackupPeriod( + period: event.period?.inMinutes, + ); + if (result.success == false) { + getIt() + .showSnackBar(result.message ?? 'Unknown error'); + } + if (result.success == true) { + getIt().apiData.backupConfig.data = + getIt() + .apiData + .backupConfig + .data + ?.copyWith( + autobackupPeriod: event.period, + ); + } + emit(currentState); + getIt().emitData(); + } + } + + Future _setAutobackupQuotas( + final SetAutobackupQuotas event, + final Emitter emit, + ) async { + final currentState = state; + if (currentState is BackupsInitialized) { + emit(BackupsBusy.fromState(currentState)); + final GenericResult result = + await getIt().api.setAutobackupQuotas( + event.quotas, + ); + if (result.success == false) { + getIt() + .showSnackBar(result.message ?? 'Unknown error'); + } + if (result.success == true) { + getIt().apiData.backupConfig.data = + getIt() + .apiData + .backupConfig + .data + ?.copyWith( + autobackupQuotas: event.quotas, + ); + } + emit(currentState); + getIt().emitData(); + } + } + + Future _forgetSnapshot( + final ForgetSnapshot event, + final Emitter emit, + ) async { + final currentState = state; + if (currentState is BackupsInitialized) { + emit(BackupsBusy.fromState(currentState)); + final GenericResult result = + await getIt().api.forgetSnapshot( + event.backupId, + ); + if (result.success == false) { + getIt() + .showSnackBar(result.message ?? 'jobs.generic_error'.tr()); + } else if (result.data == false) { + getIt() + .showSnackBar('backup.forget_snapshot_error'.tr()); + } else { + final backups = getIt().apiData.backups.data; + if (backups != null) { + getIt().apiData.backups.data = backups + .where((final Backup backup) => backup.id != event.backupId) + .toList(); + } + } + emit(currentState); + getIt().emitData(); + } + } + + @override + Future close() { + _apiStatusSubscription.cancel(); + _apiDataSubscription.cancel(); + return super.close(); + } + + @override + void onChange(final Change change) { + super.onChange(change); + } + + late StreamSubscription _apiStatusSubscription; + late StreamSubscription _apiDataSubscription; + bool isLoaded = false; +} diff --git a/lib/logic/bloc/backups/backups_event.dart b/lib/logic/bloc/backups/backups_event.dart new file mode 100644 index 00000000..f2625fd8 --- /dev/null +++ b/lib/logic/bloc/backups/backups_event.dart @@ -0,0 +1,89 @@ +part of 'backups_bloc.dart'; + +sealed class BackupsEvent extends Equatable { + const BackupsEvent(); +} + +class BackupsServerLoaded extends BackupsEvent { + const BackupsServerLoaded(); + + @override + List get props => []; +} + +class BackupsServerReset extends BackupsEvent { + const BackupsServerReset(); + + @override + List get props => []; +} + +class InitializeBackupsRepository extends BackupsEvent { + const InitializeBackupsRepository(); + + @override + List get props => []; +} + +class BackupsStateChanged extends BackupsEvent { + const BackupsStateChanged(this.backups, this.backupConfiguration); + + final List backups; + final BackupConfiguration? backupConfiguration; + + @override + List get props => [backups, backupConfiguration]; +} + +class ForceSnapshotListUpdate extends BackupsEvent { + const ForceSnapshotListUpdate(); + + @override + List get props => []; +} + +class CreateBackups extends BackupsEvent { + const CreateBackups(this.services); + + final List services; + + @override + List get props => [services]; +} + +class RestoreBackup extends BackupsEvent { + const RestoreBackup(this.backupId, this.restoreStrategy); + + final String backupId; + final BackupRestoreStrategy restoreStrategy; + + @override + List get props => [backupId, restoreStrategy]; +} + +class SetAutobackupPeriod extends BackupsEvent { + const SetAutobackupPeriod(this.period); + + final Duration? period; + + @override + List get props => [period]; +} + +class SetAutobackupQuotas extends BackupsEvent { + const SetAutobackupQuotas(this.quotas); + + final AutobackupQuotas quotas; + + @override + List get props => [quotas]; +} + +class ForgetSnapshot extends BackupsEvent { + const ForgetSnapshot(this.backupId); + + final String backupId; + + @override + List get props => [backupId]; +} diff --git a/lib/logic/bloc/backups/backups_state.dart b/lib/logic/bloc/backups/backups_state.dart new file mode 100644 index 00000000..153b1e4e --- /dev/null +++ b/lib/logic/bloc/backups/backups_state.dart @@ -0,0 +1,168 @@ +part of 'backups_bloc.dart'; + +sealed class BackupsState extends Equatable { + BackupsState({ + this.backblazeBucket, + }); + final apiConnectionRepository = getIt(); + final BackblazeBucket? backblazeBucket; + + @Deprecated('Infer the initializations status from state') + bool get isInitialized => false; + + @Deprecated('Infer the loading status from state') + bool get refreshing => false; + + @Deprecated('Infer the prevent actions status from state') + bool get preventActions => true; + + List get backups => []; + + List serviceBackups(final String serviceId) => []; + + Duration? get autobackupPeriod => null; + + AutobackupQuotas? get autobackupQuotas => null; + + BackupsState copyWith({required final BackblazeBucket backblazeBucket}); +} + +class BackupsInitial extends BackupsState { + BackupsInitial({ + super.backblazeBucket, + }); + @override + List get props => []; + + @override + BackupsInitial copyWith({ + final BackblazeBucket? backblazeBucket, + }) => + BackupsInitial(backblazeBucket: backblazeBucket ?? this.backblazeBucket); +} + +class BackupsLoading extends BackupsState { + BackupsLoading({ + super.backblazeBucket, + }); + @override + List get props => []; + + @override + @Deprecated('Infer the loading status from state') + bool get refreshing => true; + + @override + BackupsLoading copyWith({ + final BackblazeBucket? backblazeBucket, + }) => + BackupsLoading(backblazeBucket: backblazeBucket ?? this.backblazeBucket); +} + +class BackupsUnititialized extends BackupsState { + BackupsUnititialized({ + super.backblazeBucket, + }); + @override + List get props => []; + + @override + BackupsUnititialized copyWith({ + final BackblazeBucket? backblazeBucket, + }) => + BackupsUnititialized( + backblazeBucket: backblazeBucket ?? this.backblazeBucket); +} + +class BackupsInitializing extends BackupsState { + BackupsInitializing({ + super.backblazeBucket, + }); + @override + List get props => []; + + @override + BackupsInitializing copyWith({ + final BackblazeBucket? backblazeBucket, + }) => + BackupsInitializing( + backblazeBucket: backblazeBucket ?? this.backblazeBucket); +} + +class BackupsInitialized extends BackupsState { + BackupsInitialized({ + final List backups = const [], + final BackupConfiguration? backupConfig, + super.backblazeBucket, + }) : _backupsHashCode = Object.hashAll(backups), + _backupConfigHashCode = Object.hashAll([backupConfig]); + + final int _backupsHashCode; + final int _backupConfigHashCode; + + List get _backupList => + apiConnectionRepository.apiData.backups.data ?? []; + + BackupConfiguration? get _backupConfig => + apiConnectionRepository.apiData.backupConfig.data; + + @override + AutobackupQuotas? get autobackupQuotas => _backupConfig?.autobackupQuotas; + + @override + Duration? get autobackupPeriod => + _backupConfig?.autobackupPeriod?.inMinutes == 0 + ? null + : _backupConfig?.autobackupPeriod; + + @override + @Deprecated('Infer the initializations status from state') + bool get isInitialized => true; + + @override + @Deprecated('Infer the prevent actions status from state') + bool get preventActions => false; + + @override + List get backups { + try { + final List list = _backupList; + list.sort((final a, final b) => b.time.compareTo(a.time)); + return list; + } on UnsupportedError { + return _backupList; + } + } + + @override + List serviceBackups(final String serviceId) => backups + .where((final backup) => backup.serviceId == serviceId) + .toList(growable: false); + + @override + List get props => [_backupsHashCode, _backupConfigHashCode]; + + @override + BackupsState copyWith({required final BackblazeBucket backblazeBucket}) => + BackupsInitialized( + backups: backups, + backupConfig: _backupConfig, + backblazeBucket: backblazeBucket, + ); +} + +class BackupsBusy extends BackupsInitialized { + BackupsBusy.fromState(final BackupsInitialized state) + : super( + backups: state.backups, + backupConfig: state._backupConfig, + backblazeBucket: state.backblazeBucket, + ); + + @override + @Deprecated('Infer the prevent actions status from state') + bool get preventActions => true; + + @override + List get props => []; +} diff --git a/lib/logic/cubit/server_jobs/server_jobs_bloc.dart b/lib/logic/bloc/server_jobs/server_jobs_bloc.dart similarity index 96% rename from lib/logic/cubit/server_jobs/server_jobs_bloc.dart rename to lib/logic/bloc/server_jobs/server_jobs_bloc.dart index 3b82ddd8..bf428d23 100644 --- a/lib/logic/cubit/server_jobs/server_jobs_bloc.dart +++ b/lib/logic/bloc/server_jobs/server_jobs_bloc.dart @@ -3,7 +3,6 @@ import 'dart:async'; import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:selfprivacy/config/get_it_config.dart'; -import 'package:selfprivacy/logic/get_it/api_connection_repository.dart'; import 'package:selfprivacy/logic/models/json/server_job.dart'; export 'package:provider/provider.dart'; diff --git a/lib/logic/cubit/server_jobs/server_jobs_event.dart b/lib/logic/bloc/server_jobs/server_jobs_event.dart similarity index 100% rename from lib/logic/cubit/server_jobs/server_jobs_event.dart rename to lib/logic/bloc/server_jobs/server_jobs_event.dart diff --git a/lib/logic/cubit/server_jobs/server_jobs_state.dart b/lib/logic/bloc/server_jobs/server_jobs_state.dart similarity index 100% rename from lib/logic/cubit/server_jobs/server_jobs_state.dart rename to lib/logic/bloc/server_jobs/server_jobs_state.dart diff --git a/lib/logic/cubit/backups/backups_cubit.dart b/lib/logic/cubit/backups/backups_cubit.dart deleted file mode 100644 index 1b358f4b..00000000 --- a/lib/logic/cubit/backups/backups_cubit.dart +++ /dev/null @@ -1,278 +0,0 @@ -import 'dart:async'; - -import 'package:easy_localization/easy_localization.dart'; -import 'package:selfprivacy/config/get_it_config.dart'; -import 'package:selfprivacy/logic/api_maps/rest_maps/backblaze.dart'; -import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart'; -import 'package:selfprivacy/logic/cubit/server_connection_dependent/server_connection_dependent_cubit.dart'; -import 'package:selfprivacy/logic/get_it/api_connection_repository.dart'; -import 'package:selfprivacy/logic/models/hive/backblaze_bucket.dart'; -import 'package:selfprivacy/logic/models/backup.dart'; -import 'package:selfprivacy/logic/models/hive/backups_credential.dart'; -import 'package:selfprivacy/logic/models/initialize_repository_input.dart'; -import 'package:selfprivacy/logic/models/service.dart'; - -part 'backups_state.dart'; - -class BackupsCubit extends ServerConnectionDependentCubit { - BackupsCubit(final ServerInstallationCubit serverInstallationCubit) - : super( - const BackupsState(preventActions: true), - ); - - final ServerApi api = ServerApi(); - final BackblazeApi backblaze = BackblazeApi(); - - @override - Future load() async { - final BackblazeBucket? bucket = getIt().backblazeBucket; - final BackupConfiguration? backupConfig = - await api.getBackupsConfiguration(); - final List backups = await api.getBackups(); - backups.sort((final a, final b) => b.time.compareTo(a.time)); - emit( - state.copyWith( - backblazeBucket: bucket, - isInitialized: backupConfig?.isInitialized, - autobackupPeriod: backupConfig?.autobackupPeriod ?? Duration.zero, - autobackupQuotas: backupConfig?.autobackupQuotas, - backups: backups, - preventActions: false, - refreshing: false, - ), - ); - } - - Future initializeBackups() async { - emit(state.copyWith(preventActions: true)); - final String? encryptionKey = - (await api.getBackupsConfiguration())?.encryptionKey; - if (encryptionKey == null) { - getIt() - .showSnackBar("Couldn't get encryption key from your server."); - emit(state.copyWith(preventActions: false)); - return; - } - - final BackblazeBucket bucket; - - if (state.backblazeBucket == null) { - final String domain = getIt() - .serverDomain! - .domainName - .replaceAll(RegExp(r'[^a-zA-Z0-9]'), '-'); - final int serverId = getIt().serverDetails!.id; - String bucketName = - '${DateTime.now().millisecondsSinceEpoch}-$serverId-$domain'; - if (bucketName.length > 49) { - bucketName = bucketName.substring(0, 49); - } - final String bucketId = await backblaze.createBucket(bucketName); - - final BackblazeApplicationKey key = await backblaze.createKey(bucketId); - bucket = BackblazeBucket( - bucketId: bucketId, - bucketName: bucketName, - applicationKey: key.applicationKey, - applicationKeyId: key.applicationKeyId, - encryptionKey: encryptionKey, - ); - - await getIt().storeBackblazeBucket(bucket); - emit(state.copyWith(backblazeBucket: bucket)); - } else { - bucket = state.backblazeBucket!; - } - - final GenericResult result = await api.initializeRepository( - InitializeRepositoryInput( - provider: BackupsProviderType.backblaze, - locationId: bucket.bucketId, - locationName: bucket.bucketName, - login: bucket.applicationKeyId, - password: bucket.applicationKey, - ), - ); - if (result.success == false) { - getIt() - .showSnackBar(result.message ?? 'Unknown error'); - emit(state.copyWith(preventActions: false)); - return; - } - await updateBackups(); - getIt().showSnackBar( - 'Backups repository is now initializing. It may take a while.', - ); - - emit(state.copyWith(preventActions: false)); - } - - Future reuploadKey() async { - emit(state.copyWith(preventActions: true)); - BackblazeBucket? bucket = getIt().backblazeBucket; - if (bucket == null) { - emit(state.copyWith(isInitialized: false)); - } else { - String login = bucket.applicationKeyId; - String password = bucket.applicationKey; - if (login.isEmpty || password.isEmpty) { - final BackblazeApplicationKey key = - await backblaze.createKey(bucket.bucketId); - login = key.applicationKeyId; - password = key.applicationKey; - bucket = BackblazeBucket( - bucketId: bucket.bucketId, - bucketName: bucket.bucketName, - encryptionKey: bucket.encryptionKey, - applicationKey: password, - applicationKeyId: login, - ); - await getIt().storeBackblazeBucket(bucket); - emit(state.copyWith(backblazeBucket: bucket)); - } - final GenericResult result = await api.initializeRepository( - InitializeRepositoryInput( - provider: BackupsProviderType.backblaze, - locationId: bucket.bucketId, - locationName: bucket.bucketName, - login: login, - password: password, - ), - ); - if (result.success == false) { - getIt() - .showSnackBar(result.message ?? 'Unknown error'); - emit(state.copyWith(preventActions: false)); - return; - } else { - emit(state.copyWith(preventActions: false)); - getIt().showSnackBar('backup.reuploaded_key'.tr()); - await updateBackups(); - } - } - } - - @Deprecated("we don't have states") - Duration refreshTimeFromState() => const Duration(seconds: 60); - - Future updateBackups({final bool useTimer = false}) async { - emit(state.copyWith(refreshing: true)); - final backups = await api.getBackups(); - backups.sort((final a, final b) => b.time.compareTo(a.time)); - final backupConfig = await api.getBackupsConfiguration(); - - emit( - state.copyWith( - backups: backups, - refreshTimer: refreshTimeFromState(), - refreshing: false, - isInitialized: backupConfig?.isInitialized ?? false, - autobackupPeriod: backupConfig?.autobackupPeriod, - autobackupQuotas: backupConfig?.autobackupQuotas, - ), - ); - if (useTimer) { - Timer(state.refreshTimer, () => updateBackups(useTimer: true)); - } - } - - Future forceUpdateBackups() async { - emit(state.copyWith(preventActions: true)); - getIt().showSnackBar('backup.refetching_list'.tr()); - await api.forceBackupListReload(); - emit(state.copyWith(preventActions: false)); - } - - Future createMultipleBackups(final List services) async { - emit(state.copyWith(preventActions: true)); - for (final service in services) { - await api.startBackup(service.id); - } - await updateBackups(); - emit(state.copyWith(preventActions: false)); - } - - Future createBackup(final String serviceId) async { - emit(state.copyWith(preventActions: true)); - await api.startBackup(serviceId); - await updateBackups(); - emit(state.copyWith(preventActions: false)); - } - - Future restoreBackup( - final String backupId, - final BackupRestoreStrategy strategy, - ) async { - emit(state.copyWith(preventActions: true)); - await api.restoreBackup(backupId, strategy); - emit(state.copyWith(preventActions: false)); - } - - Future setAutobackupPeriod(final Duration? period) async { - emit(state.copyWith(preventActions: true)); - final result = await api.setAutobackupPeriod(period: period?.inMinutes); - if (result.success == false) { - getIt() - .showSnackBar(result.message ?? 'Unknown error'); - emit(state.copyWith(preventActions: false)); - } else { - getIt() - .showSnackBar('backup.autobackup_period_set'.tr()); - emit( - state.copyWith( - preventActions: false, - autobackupPeriod: period ?? Duration.zero, - ), - ); - } - await updateBackups(); - } - - Future setAutobackupQuotas(final AutobackupQuotas quotas) async { - emit(state.copyWith(preventActions: true)); - final result = await api.setAutobackupQuotas(quotas); - if (result.success == false) { - getIt() - .showSnackBar(result.message ?? 'Unknown error'); - emit(state.copyWith(preventActions: false)); - } else { - getIt().showSnackBar('backup.quotas_set'.tr()); - emit( - state.copyWith( - preventActions: false, - autobackupQuotas: quotas, - ), - ); - } - await updateBackups(); - } - - Future forgetSnapshot(final String snapshotId) async { - final result = await api.forgetSnapshot(snapshotId); - if (!result.success) { - getIt().showSnackBar('jobs.generic_error'.tr()); - return; - } - - if (result.data == false) { - getIt() - .showSnackBar('backup.forget_snapshot_error'.tr()); - } - - // Optimistic update - final backups = state.backups; - final index = - backups.indexWhere((final snapshot) => snapshot.id == snapshotId); - if (index != -1) { - backups.removeAt(index); - emit(state.copyWith(backups: backups)); - } - - await updateBackups(); - } - - @override - void clear() async { - emit(const BackupsState()); - } -} diff --git a/lib/logic/cubit/backups/backups_state.dart b/lib/logic/cubit/backups/backups_state.dart deleted file mode 100644 index 887396d7..00000000 --- a/lib/logic/cubit/backups/backups_state.dart +++ /dev/null @@ -1,61 +0,0 @@ -part of 'backups_cubit.dart'; - -class BackupsState extends ServerInstallationDependendState { - const BackupsState({ - this.isInitialized = false, - this.backups = const [], - this.preventActions = true, - this.refreshTimer = const Duration(seconds: 60), - this.refreshing = true, - this.autobackupPeriod, - this.backblazeBucket, - this.autobackupQuotas, - }); - - final bool isInitialized; - final List backups; - final bool preventActions; - final Duration refreshTimer; - final bool refreshing; - final Duration? autobackupPeriod; - final BackblazeBucket? backblazeBucket; - final AutobackupQuotas? autobackupQuotas; - - List serviceBackups(final String serviceId) => backups - .where((final backup) => backup.serviceId == serviceId) - .toList(growable: false); - - @override - List get props => [ - isInitialized, - backups, - preventActions, - refreshTimer, - refreshing, - ]; - - BackupsState copyWith({ - final bool? isInitialized, - final List? backups, - final bool? preventActions, - final Duration? refreshTimer, - final bool? refreshing, - final Duration? autobackupPeriod, - final BackblazeBucket? backblazeBucket, - final AutobackupQuotas? autobackupQuotas, - }) => - BackupsState( - isInitialized: isInitialized ?? this.isInitialized, - backups: backups ?? this.backups, - preventActions: preventActions ?? this.preventActions, - refreshTimer: refreshTimer ?? this.refreshTimer, - refreshing: refreshing ?? this.refreshing, - // The autobackupPeriod might be null, so if the duration is set to 0, we - // set it to null. - autobackupPeriod: autobackupPeriod?.inSeconds == 0 - ? null - : autobackupPeriod ?? this.autobackupPeriod, - backblazeBucket: backblazeBucket ?? this.backblazeBucket, - autobackupQuotas: autobackupQuotas ?? this.autobackupQuotas, - ); -} diff --git a/lib/logic/cubit/connection_status/connection_status_bloc.dart b/lib/logic/cubit/connection_status/connection_status_bloc.dart index df3ebf97..f44288ed 100644 --- a/lib/logic/cubit/connection_status/connection_status_bloc.dart +++ b/lib/logic/cubit/connection_status/connection_status_bloc.dart @@ -1,7 +1,6 @@ import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:selfprivacy/config/get_it_config.dart'; -import 'package:selfprivacy/logic/get_it/api_connection_repository.dart'; part 'connection_status_event.dart'; part 'connection_status_state.dart'; diff --git a/lib/logic/cubit/dns_records/dns_records_cubit.dart b/lib/logic/cubit/dns_records/dns_records_cubit.dart index 0f14a8cc..6e928b63 100644 --- a/lib/logic/cubit/dns_records/dns_records_cubit.dart +++ b/lib/logic/cubit/dns_records/dns_records_cubit.dart @@ -2,7 +2,6 @@ import 'package:cubit_form/cubit_form.dart'; import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/desired_dns_record.dart'; import 'package:selfprivacy/logic/cubit/server_connection_dependent/server_connection_dependent_cubit.dart'; -import 'package:selfprivacy/logic/get_it/api_connection_repository.dart'; import 'package:selfprivacy/logic/models/hive/server_domain.dart'; import 'package:selfprivacy/logic/models/json/dns_records.dart'; diff --git a/lib/logic/cubit/server_connection_dependent/server_connection_dependent_cubit.dart b/lib/logic/cubit/server_connection_dependent/server_connection_dependent_cubit.dart index 61afa203..f46c7ef9 100644 --- a/lib/logic/cubit/server_connection_dependent/server_connection_dependent_cubit.dart +++ b/lib/logic/cubit/server_connection_dependent/server_connection_dependent_cubit.dart @@ -2,7 +2,6 @@ import 'dart:async'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:equatable/equatable.dart'; import 'package:selfprivacy/config/get_it_config.dart'; -import 'package:selfprivacy/logic/get_it/api_connection_repository.dart'; export 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; diff --git a/lib/logic/cubit/server_installation/server_installation_cubit.dart b/lib/logic/cubit/server_installation/server_installation_cubit.dart index 709bc0b4..01f46aa4 100644 --- a/lib/logic/cubit/server_installation/server_installation_cubit.dart +++ b/lib/logic/cubit/server_installation/server_installation_cubit.dart @@ -7,7 +7,6 @@ import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/backblaze.dart'; import 'package:selfprivacy/logic/api_maps/tls_options.dart'; -import 'package:selfprivacy/logic/get_it/api_connection_repository.dart'; import 'package:selfprivacy/logic/models/disk_size.dart'; import 'package:selfprivacy/logic/models/hive/backblaze_bucket.dart'; import 'package:selfprivacy/logic/models/hive/backups_credential.dart'; @@ -234,7 +233,7 @@ class ServerInstallationCubit extends Cubit { try { bucket = await BackblazeApi() .fetchBucket(backblazeCredential, configuration); - await getIt().storeBackblazeBucket(bucket!); + await getIt().setBackblazeBucket(bucket!); } catch (e) { print(e); } diff --git a/lib/logic/cubit/server_installation/server_installation_repository.dart b/lib/logic/cubit/server_installation/server_installation_repository.dart index 36cb8743..0e6c0dcd 100644 --- a/lib/logic/cubit/server_installation/server_installation_repository.dart +++ b/lib/logic/cubit/server_installation/server_installation_repository.dart @@ -470,7 +470,7 @@ class ServerInstallationRepository { Future saveServerDetails( final ServerHostingDetails serverDetails, ) async { - await getIt().storeServerDetails(serverDetails); + await getIt().setServerDetails(serverDetails); } Future deleteServerDetails() async { @@ -483,18 +483,18 @@ class ServerInstallationRepository { } Future saveDnsProviderType(final DnsProviderType type) async { - await getIt().storeDnsProviderType(type); + await getIt().setDnsProviderType(type); } Future saveServerProviderKey(final String key) async { - await getIt().storeServerProviderKey(key); + await getIt().setServerProviderKey(key); } Future saveServerType(final ServerType serverType) async { - await getIt().storeServerTypeIdentifier( + await getIt().setServerTypeIdentifier( serverType.identifier, ); - await getIt().storeServerLocation( + await getIt().setServerLocation( serverType.location.identifier, ); } @@ -507,7 +507,7 @@ class ServerInstallationRepository { Future saveBackblazeKey( final BackupsCredential backblazeCredential, ) async { - await getIt().storeBackblazeCredential(backblazeCredential); + await getIt().setBackblazeCredential(backblazeCredential); } Future deleteBackblazeKey() async { @@ -516,7 +516,7 @@ class ServerInstallationRepository { } Future setDnsApiToken(final String key) async { - await getIt().storeDnsProviderKey(key); + await getIt().setDnsProviderKey(key); } Future deleteDnsProviderKey() async { @@ -525,7 +525,7 @@ class ServerInstallationRepository { } Future saveDomain(final ServerDomain serverDomain) async { - await getIt().storeServerDomain(serverDomain); + await getIt().setServerDomain(serverDomain); } Future deleteDomain() async { diff --git a/lib/logic/cubit/users/users_cubit.dart b/lib/logic/cubit/users/users_cubit.dart index 037b7c39..858a7b4f 100644 --- a/lib/logic/cubit/users/users_cubit.dart +++ b/lib/logic/cubit/users/users_cubit.dart @@ -6,7 +6,6 @@ import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/config/hive_config.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart'; import 'package:selfprivacy/logic/cubit/server_connection_dependent/server_connection_dependent_cubit.dart'; -import 'package:selfprivacy/logic/get_it/api_connection_repository.dart'; import 'package:selfprivacy/logic/models/hive/user.dart'; export 'package:provider/provider.dart'; diff --git a/lib/logic/get_it/api_config.dart b/lib/logic/get_it/api_config.dart index ac889dcc..b32cd995 100644 --- a/lib/logic/get_it/api_config.dart +++ b/lib/logic/get_it/api_config.dart @@ -42,47 +42,47 @@ class ApiConfigModel { _serverProvider = value; } - Future storeDnsProviderType(final DnsProviderType value) async { + Future setDnsProviderType(final DnsProviderType value) async { await _box.put(BNames.dnsProvider, value); _dnsProvider = value; } - Future storeServerProviderKey(final String value) async { + Future setServerProviderKey(final String value) async { await _box.put(BNames.hetznerKey, value); _serverProviderKey = value; } - Future storeDnsProviderKey(final String value) async { + Future setDnsProviderKey(final String value) async { await _box.put(BNames.cloudFlareKey, value); _dnsProviderKey = value; } - Future storeServerTypeIdentifier(final String typeIdentifier) async { + Future setServerTypeIdentifier(final String typeIdentifier) async { await _box.put(BNames.serverTypeIdentifier, typeIdentifier); _serverType = typeIdentifier; } - Future storeServerLocation(final String serverLocation) async { + Future setServerLocation(final String serverLocation) async { await _box.put(BNames.serverLocation, serverLocation); _serverLocation = serverLocation; } - Future storeBackblazeCredential(final BackupsCredential value) async { + Future setBackblazeCredential(final BackupsCredential value) async { await _box.put(BNames.backblazeCredential, value); _backblazeCredential = value; } - Future storeServerDomain(final ServerDomain value) async { + Future setServerDomain(final ServerDomain value) async { await _box.put(BNames.serverDomain, value); _serverDomain = value; } - Future storeServerDetails(final ServerHostingDetails value) async { + Future setServerDetails(final ServerHostingDetails value) async { await _box.put(BNames.serverDetails, value); _serverDetails = value; } - Future storeBackblazeBucket(final BackblazeBucket value) async { + Future setBackblazeBucket(final BackblazeBucket value) async { await _box.put(BNames.backblazeBucket, value); _backblazeBucket = value; } diff --git a/lib/logic/get_it/api_connection_repository.dart b/lib/logic/get_it/api_connection_repository.dart index d46f0010..72e9fb65 100644 --- a/lib/logic/get_it/api_connection_repository.dart +++ b/lib/logic/get_it/api_connection_repository.dart @@ -5,6 +5,7 @@ import 'package:pub_semver/pub_semver.dart'; import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/config/hive_config.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart'; +import 'package:selfprivacy/logic/models/backup.dart'; import 'package:selfprivacy/logic/models/hive/server_details.dart'; import 'package:selfprivacy/logic/models/hive/server_domain.dart'; import 'package:selfprivacy/logic/models/json/server_job.dart'; @@ -15,7 +16,7 @@ class ApiConnectionRepository { Box box = Hive.box(BNames.serverInstallationBox); final ServerApi api = ServerApi(); - final ApiData _apiData = ApiData(); + final ApiData _apiData = ApiData(ServerApi()); ApiData get apiData => _apiData; @@ -66,15 +67,18 @@ class ApiConnectionRepository { _connectionStatusStream.add(connectionStatus); return; } else { - connectionStatus = ConnectionStatus.connected; - _connectionStatusStream.add(connectionStatus); _apiData.apiVersion.data = apiVersion; _dataStream.add(_apiData); } _apiData.serverJobs.data = await api.getServerJobs(); + _apiData.backupConfig.data = await api.getBackupsConfiguration(); + _apiData.backups.data = await api.getBackups(); _dataStream.add(_apiData); + connectionStatus = ConnectionStatus.connected; + _connectionStatusStream.add(connectionStatus); + // Use timer to periodically check for new jobs _timer = Timer.periodic( const Duration(seconds: 10), @@ -82,7 +86,7 @@ class ApiConnectionRepository { ); } - void reload(final Timer timer) async { + Future reload(final Timer? timer) async { final serverDetails = getIt().serverDetails; if (serverDetails == null) { return; @@ -98,26 +102,42 @@ class ApiConnectionRepository { _connectionStatusStream.add(connectionStatus); _apiData.apiVersion.data = apiVersion; } + final Version version = Version.parse(apiVersion); + await _apiData.serverJobs + .refetchData(version, () => _dataStream.add(_apiData)); + await _apiData.backups + .refetchData(version, () => _dataStream.add(_apiData)); + await _apiData.backupConfig + .refetchData(version, () => _dataStream.add(_apiData)); + } - if (VersionConstraint.parse(_apiData.apiVersion.requiredApiVersion) - .allows(Version.parse(apiVersion))) { - final jobs = await api.getServerJobs(); - if (Object.hashAll(_apiData.serverJobs.data ?? []) != - Object.hashAll(jobs)) { - _apiData.serverJobs.data = [...jobs]; - _dataStream.add(_apiData); - } - } + void emitData() { + _dataStream.add(_apiData); } } class ApiData { - ApiData() - : serverJobs = ApiDataElement>(null), - apiVersion = ApiDataElement(null); + ApiData(final ServerApi api) + : apiVersion = ApiDataElement( + fetchData: () async => api.getApiVersion(), + ), + serverJobs = ApiDataElement>( + fetchData: () async => api.getServerJobs(), + ttl: 10, + ), + backupConfig = ApiDataElement( + fetchData: () async => api.getBackupsConfiguration(), + requiredApiVersion: '>=2.4.2', + ), + backups = ApiDataElement>( + fetchData: () async => api.getBackups(), + requiredApiVersion: '>=2.4.2', + ); ApiDataElement> serverJobs; ApiDataElement apiVersion; + ApiDataElement backupConfig; + ApiDataElement> backups; } enum ConnectionStatus { @@ -129,8 +149,9 @@ enum ConnectionStatus { } class ApiDataElement { - ApiDataElement( - final T? data, { + ApiDataElement({ + required this.fetchData, + final T? data, this.requiredApiVersion = '>=2.3.0', this.ttl = 60, }) : _data = data, @@ -139,9 +160,40 @@ class ApiDataElement { T? _data; final String requiredApiVersion; + final Future Function() fetchData; + + Future refetchData( + final Version version, final Function callback) async { + if (VersionConstraint.parse(requiredApiVersion).allows(version)) { + print('Fetching data for $runtimeType'); + if (isExpired) { + print('Data is expired'); + final newData = await fetchData(); + print(newData); + if (T is List) { + if (Object.hashAll(newData as Iterable) != + Object.hashAll(_data as Iterable)) { + _data = [...newData] as T?; + } + } else { + if (newData.hashCode != _data.hashCode) { + _data = newData; + } + } + callback(); + } + } + } + /// TTL of the data in seconds final int ttl; + Type get type => T; + + void invalidate() { + _lastUpdated = DateTime.fromMillisecondsSinceEpoch(0); + } + /// Timestamp of when the data was last updated DateTime _lastUpdated; diff --git a/lib/logic/models/backup.dart b/lib/logic/models/backup.dart index 1dcf9129..43d50987 100644 --- a/lib/logic/models/backup.dart +++ b/lib/logic/models/backup.dart @@ -1,3 +1,4 @@ +import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/backups.graphql.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/schema.graphql.dart'; @@ -40,7 +41,7 @@ extension BackupReasonExtension on Enum$BackupReason { }; } -class BackupConfiguration { +class BackupConfiguration extends Equatable { BackupConfiguration.fromGraphQL( final Query$BackupConfiguration$backup$configuration configuration, ) : this( @@ -58,7 +59,7 @@ class BackupConfiguration { ), ); - BackupConfiguration({ + const BackupConfiguration({ required this.autobackupPeriod, required this.encryptionKey, required this.isInitialized, @@ -75,9 +76,39 @@ class BackupConfiguration { final String? locationName; final BackupsProviderType provider; final AutobackupQuotas autobackupQuotas; + + @override + List get props => [ + autobackupPeriod, + encryptionKey, + isInitialized, + locationId, + locationName, + provider, + autobackupQuotas, + ]; + + BackupConfiguration copyWith({ + final Duration? autobackupPeriod, + final String? encryptionKey, + final bool? isInitialized, + final String? locationId, + final String? locationName, + final BackupsProviderType? provider, + final AutobackupQuotas? autobackupQuotas, + }) => + BackupConfiguration( + autobackupPeriod: autobackupPeriod ?? this.autobackupPeriod, + encryptionKey: encryptionKey ?? this.encryptionKey, + isInitialized: isInitialized ?? this.isInitialized, + locationId: locationId ?? this.locationId, + locationName: locationName ?? this.locationName, + provider: provider ?? this.provider, + autobackupQuotas: autobackupQuotas ?? this.autobackupQuotas, + ); } -class AutobackupQuotas { +class AutobackupQuotas extends Equatable { AutobackupQuotas.fromGraphQL( final Query$BackupConfiguration$backup$configuration$autobackupQuotas autobackupQuotas, @@ -89,7 +120,7 @@ class AutobackupQuotas { yearly: autobackupQuotas.yearly, ); - AutobackupQuotas({ + const AutobackupQuotas({ required this.last, required this.daily, required this.weekly, @@ -117,6 +148,15 @@ class AutobackupQuotas { monthly: monthly ?? this.monthly, yearly: yearly ?? this.yearly, ); + + @override + List get props => [ + last, + daily, + weekly, + monthly, + yearly, + ]; } enum BackupRestoreStrategy { diff --git a/lib/logic/models/hive/backblaze_bucket.dart b/lib/logic/models/hive/backblaze_bucket.dart index 6c4bbeea..de898dff 100644 --- a/lib/logic/models/hive/backblaze_bucket.dart +++ b/lib/logic/models/hive/backblaze_bucket.dart @@ -29,4 +29,19 @@ class BackblazeBucket { @override String toString() => bucketName; + + BackblazeBucket copyWith({ + final String? bucketId, + final String? applicationKeyId, + final String? applicationKey, + final String? bucketName, + final String? encryptionKey, + }) => + BackblazeBucket( + bucketId: bucketId ?? this.bucketId, + applicationKeyId: applicationKeyId ?? this.applicationKeyId, + applicationKey: applicationKey ?? this.applicationKey, + bucketName: bucketName ?? this.bucketName, + encryptionKey: encryptionKey ?? this.encryptionKey, + ); } diff --git a/lib/ui/components/jobs_content/jobs_content.dart b/lib/ui/components/jobs_content/jobs_content.dart index d71636ee..62bdbc14 100644 --- a/lib/ui/components/jobs_content/jobs_content.dart +++ b/lib/ui/components/jobs_content/jobs_content.dart @@ -4,7 +4,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:selfprivacy/config/brand_theme.dart'; import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; -import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_bloc.dart'; +import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart'; import 'package:selfprivacy/logic/models/json/server_job.dart'; import 'package:selfprivacy/ui/components/buttons/brand_button.dart'; import 'package:selfprivacy/ui/components/brand_loader/brand_loader.dart'; diff --git a/lib/ui/pages/backups/backup_details.dart b/lib/ui/pages/backups/backup_details.dart index c965a2cc..a99d6385 100644 --- a/lib/ui/pages/backups/backup_details.dart +++ b/lib/ui/pages/backups/backup_details.dart @@ -2,9 +2,9 @@ import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; +import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; -import 'package:selfprivacy/logic/cubit/backups/backups_cubit.dart'; -import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_bloc.dart'; +import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart'; import 'package:selfprivacy/logic/cubit/services/services_cubit.dart'; import 'package:selfprivacy/logic/models/backup.dart'; import 'package:selfprivacy/logic/models/json/server_job.dart'; @@ -31,14 +31,13 @@ class BackupDetailsPage extends StatelessWidget { Widget build(final BuildContext context) { final bool isReady = context.watch().state is ServerInstallationFinished; - final BackupsState backupsState = context.watch().state; + final BackupsState backupsState = context.watch().state; final bool isBackupInitialized = backupsState.isInitialized; final StateType providerState = isReady && isBackupInitialized ? StateType.stable : StateType.uninitialized; final bool preventActions = backupsState.preventActions; final List backups = backupsState.backups; - final bool refreshing = backupsState.refreshing; final List services = context.watch().state.servicesThatCanBeBackedUp; final Duration? autobackupPeriod = backupsState.autobackupPeriod; @@ -75,8 +74,10 @@ class BackupDetailsPage extends StatelessWidget { BrandButton.rised( onPressed: preventActions ? null - : () async { - await context.read().initializeBackups(); + : () { + context + .read() + .add(const InitializeBackupsRepository()); }, text: 'backup.initialize'.tr(), ), @@ -335,8 +336,10 @@ class BackupDetailsPage extends StatelessWidget { actionButtonTitle: 'backup.forget_snapshot'.tr(), actionButtonOnPressed: () => { - context.read().forgetSnapshot( - backup.id, + context.read().add( + ForgetSnapshot( + backup.id, + ), ), }, ); @@ -391,18 +394,6 @@ class BackupDetailsPage extends StatelessWidget { const SizedBox(height: 8), const Divider(), const SizedBox(height: 8), - ListTile( - title: Text( - 'backup.refresh'.tr(), - ), - onTap: refreshing - ? null - : () => {context.read().updateBackups()}, - enabled: !refreshing, - leading: const Icon( - Icons.refresh_outlined, - ), - ), if (providerState != StateType.uninitialized) Column( children: [ @@ -425,7 +416,11 @@ class BackupDetailsPage extends StatelessWidget { ), onTap: preventActions ? null - : () => {context.read().forceUpdateBackups()}, + : () => { + context + .read() + .add(const ForceSnapshotListUpdate()) + }, ), const SizedBox(height: 8), const Divider(), @@ -447,9 +442,9 @@ class BackupDetailsPage extends StatelessWidget { Icons.warning_amber_outlined, color: overrideColor, ), - onTap: preventActions - ? null - : () => {context.read().reuploadKey()}, + // onTap: preventActions + // ? null + // : () => {context.read().reuploadKey()}, ), ], ), diff --git a/lib/ui/pages/backups/backups_list.dart b/lib/ui/pages/backups/backups_list.dart index 128c8ed6..79eb7a95 100644 --- a/lib/ui/pages/backups/backups_list.dart +++ b/lib/ui/pages/backups/backups_list.dart @@ -3,7 +3,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:selfprivacy/logic/cubit/backups/backups_cubit.dart'; +import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart'; import 'package:selfprivacy/logic/cubit/services/services_cubit.dart'; import 'package:selfprivacy/logic/models/backup.dart'; import 'package:selfprivacy/logic/models/service.dart'; @@ -25,10 +25,10 @@ class BackupsListPage extends StatelessWidget { // If the service is null, get all backups from state. If not null, call the // serviceBackups(serviceId) on the backups state. final List backups = service == null - ? context.watch().state.backups - : context.watch().state.serviceBackups(service!.id); + ? context.watch().state.backups + : context.watch().state.serviceBackups(service!.id); final bool preventActions = - context.watch().state.preventActions; + context.watch().state.preventActions; return BrandHeroScreen( heroTitle: 'backup.snapshots_title'.tr(), hasFlashButton: true, @@ -76,9 +76,9 @@ class BackupsListPage extends StatelessWidget { description: 'backup.forget_snapshot_alert'.tr(), actionButtonTitle: 'backup.forget_snapshot'.tr(), actionButtonOnPressed: () => { - context.read().forgetSnapshot( - backup.id, - ), + context + .read() + .add(ForgetSnapshot(backup.id)), }, ); }, diff --git a/lib/ui/pages/backups/change_period_modal.dart b/lib/ui/pages/backups/change_period_modal.dart index a02b0c85..87c9fe2d 100644 --- a/lib/ui/pages/backups/change_period_modal.dart +++ b/lib/ui/pages/backups/change_period_modal.dart @@ -1,8 +1,8 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; -import 'package:selfprivacy/logic/cubit/backups/backups_cubit.dart'; -import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_bloc.dart'; +import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart'; import 'package:selfprivacy/utils/extensions/duration.dart'; class ChangeAutobackupsPeriodModal extends StatefulWidget { @@ -34,13 +34,13 @@ class _ChangeAutobackupsPeriodModalState @override void initState() { super.initState(); - selectedPeriod = context.read().state.autobackupPeriod; + selectedPeriod = context.read().state.autobackupPeriod; } @override Widget build(final BuildContext context) { final Duration? initialAutobackupPeriod = - context.watch().state.autobackupPeriod; + context.watch().state.autobackupPeriod; return ListView( controller: widget.scrollController, padding: const EdgeInsets.all(16), @@ -91,8 +91,8 @@ class _ChangeAutobackupsPeriodModalState ? null : () { context - .read() - .setAutobackupPeriod(selectedPeriod); + .read() + .add(SetAutobackupPeriod(selectedPeriod)); Navigator.of(context).pop(); }, child: Text( diff --git a/lib/ui/pages/backups/change_rotation_quotas_modal.dart b/lib/ui/pages/backups/change_rotation_quotas_modal.dart index dab54d02..f3972387 100644 --- a/lib/ui/pages/backups/change_rotation_quotas_modal.dart +++ b/lib/ui/pages/backups/change_rotation_quotas_modal.dart @@ -1,8 +1,8 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; -import 'package:selfprivacy/logic/cubit/backups/backups_cubit.dart'; -import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_bloc.dart'; +import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart'; import 'package:selfprivacy/logic/models/backup.dart'; class ChangeRotationQuotasModal extends StatefulWidget { @@ -27,7 +27,7 @@ enum QuotaUnits { } class _ChangeRotationQuotasModalState extends State { - AutobackupQuotas selectedQuotas = AutobackupQuotas( + AutobackupQuotas selectedQuotas = const AutobackupQuotas( last: 3, daily: 7, weekly: 4, @@ -40,7 +40,7 @@ class _ChangeRotationQuotasModalState extends State { void initState() { super.initState(); selectedQuotas = - context.read().state.autobackupQuotas ?? selectedQuotas; + context.read().state.autobackupQuotas ?? selectedQuotas; } String generateSubtitle(final int value, final QuotaUnits unit) { @@ -83,7 +83,7 @@ class _ChangeRotationQuotasModalState extends State { @override Widget build(final BuildContext context) { final AutobackupQuotas? initialAutobackupQuotas = - context.watch().state.autobackupQuotas; + context.watch().state.autobackupQuotas; return ListView( controller: widget.scrollController, padding: const EdgeInsets.all(16), @@ -190,8 +190,8 @@ class _ChangeRotationQuotasModalState extends State { ? null : () { context - .read() - .setAutobackupQuotas(selectedQuotas); + .read() + .add(SetAutobackupQuotas(selectedQuotas)); Navigator.of(context).pop(); }, child: Text( diff --git a/lib/ui/pages/backups/copy_encryption_key_modal.dart b/lib/ui/pages/backups/copy_encryption_key_modal.dart index 96d64ce1..19c98727 100644 --- a/lib/ui/pages/backups/copy_encryption_key_modal.dart +++ b/lib/ui/pages/backups/copy_encryption_key_modal.dart @@ -2,9 +2,9 @@ import 'dart:async'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; -import 'package:selfprivacy/logic/cubit/backups/backups_cubit.dart'; -import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_bloc.dart'; +import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart'; import 'package:selfprivacy/ui/components/info_box/info_box.dart'; import 'package:selfprivacy/utils/platform_adapter.dart'; @@ -34,7 +34,7 @@ class _CopyEncryptionKeyModalState extends State { @override Widget build(final BuildContext context) { final String? encryptionKey = - context.watch().state.backblazeBucket?.encryptionKey; + context.watch().state.backblazeBucket?.encryptionKey; if (encryptionKey == null) { return ListView( controller: widget.scrollController, diff --git a/lib/ui/pages/backups/create_backups_modal.dart b/lib/ui/pages/backups/create_backups_modal.dart index 891f26cf..acf6ac6f 100644 --- a/lib/ui/pages/backups/create_backups_modal.dart +++ b/lib/ui/pages/backups/create_backups_modal.dart @@ -1,8 +1,8 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; -import 'package:selfprivacy/logic/cubit/backups/backups_cubit.dart'; -import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_bloc.dart'; +import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart'; +import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart'; import 'package:selfprivacy/logic/models/json/server_job.dart'; import 'package:selfprivacy/logic/models/service.dart'; @@ -147,8 +147,8 @@ class _CreateBackupsModalState extends State { ? null : () { context - .read() - .createMultipleBackups(selectedServices); + .read() + .add(CreateBackups(selectedServices)); Navigator.of(context).pop(); }, child: Text( diff --git a/lib/ui/pages/backups/snapshot_modal.dart b/lib/ui/pages/backups/snapshot_modal.dart index 411da72a..3236ddc1 100644 --- a/lib/ui/pages/backups/snapshot_modal.dart +++ b/lib/ui/pages/backups/snapshot_modal.dart @@ -2,8 +2,8 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:selfprivacy/config/get_it_config.dart'; -import 'package:selfprivacy/logic/cubit/backups/backups_cubit.dart'; -import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_bloc.dart'; +import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart'; +import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart'; import 'package:selfprivacy/logic/cubit/services/services_cubit.dart'; import 'package:selfprivacy/logic/models/backup.dart'; import 'package:selfprivacy/logic/models/json/server_job.dart'; @@ -153,10 +153,8 @@ class _SnapshotModalState extends State { onPressed: isServiceBusy ? null : () { - context.read().restoreBackup( - widget.snapshot.id, - selectedStrategy, - ); + context.read().add(RestoreBackup( + widget.snapshot.id, selectedStrategy)); Navigator.of(context).pop(); getIt() .showSnackBar('backup.restore_started'.tr()); diff --git a/lib/ui/pages/more/app_settings/developer_settings.dart b/lib/ui/pages/more/app_settings/developer_settings.dart index e7881510..21f69665 100644 --- a/lib/ui/pages/more/app_settings/developer_settings.dart +++ b/lib/ui/pages/more/app_settings/developer_settings.dart @@ -5,7 +5,6 @@ import 'package:selfprivacy/logic/api_maps/tls_options.dart'; import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart'; import 'package:selfprivacy/logic/cubit/devices/devices_cubit.dart'; import 'package:selfprivacy/logic/cubit/recovery_key/recovery_key_cubit.dart'; -import 'package:selfprivacy/logic/get_it/api_connection_repository.dart'; import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; import 'package:easy_localization/easy_localization.dart'; diff --git a/lib/ui/pages/providers/providers.dart b/lib/ui/pages/providers/providers.dart index 5d9d4fcc..e65098d0 100644 --- a/lib/ui/pages/providers/providers.dart +++ b/lib/ui/pages/providers/providers.dart @@ -2,7 +2,7 @@ import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:selfprivacy/config/brand_theme.dart'; -import 'package:selfprivacy/logic/cubit/backups/backups_cubit.dart'; +import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart'; import 'package:selfprivacy/logic/cubit/dns_records/dns_records_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_volumes/server_volume_cubit.dart'; @@ -30,7 +30,7 @@ class _ProvidersPageState extends State { final bool isReady = context.watch().state is ServerInstallationFinished; final bool isBackupInitialized = - context.watch().state.isInitialized; + context.watch().state.isInitialized; final DnsRecordsStatus dnsStatus = context.watch().state.dnsState; diff --git a/lib/ui/pages/server_storage/binds_migration/services_migration.dart b/lib/ui/pages/server_storage/binds_migration/services_migration.dart index 3d8acbc6..453e7067 100644 --- a/lib/ui/pages/server_storage/binds_migration/services_migration.dart +++ b/lib/ui/pages/server_storage/binds_migration/services_migration.dart @@ -1,7 +1,7 @@ import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_bloc.dart'; +import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart'; import 'package:selfprivacy/logic/cubit/services/services_cubit.dart'; import 'package:selfprivacy/logic/models/disk_size.dart'; import 'package:selfprivacy/logic/models/service.dart'; From 831a0e95ebb26487671fcb165e999a2e6561552a Mon Sep 17 00:00:00 2001 From: Inex Code Date: Mon, 29 Jan 2024 19:57:22 +0400 Subject: [PATCH 06/34] refactor: Rewrite services cubit to bloc, using ApiRepo streams --- lib/config/bloc_config.dart | 9 +- lib/logic/bloc/backups/backups_bloc.dart | 3 +- lib/logic/bloc/backups/backups_state.dart | 6 +- lib/logic/bloc/services/services_bloc.dart | 144 ++++++++++++++++++ lib/logic/bloc/services/services_event.dart | 40 +++++ lib/logic/bloc/services/services_state.dart | 115 ++++++++++++++ .../cubit/client_jobs/client_jobs_cubit.dart | 8 +- lib/logic/cubit/services/services_cubit.dart | 81 ---------- lib/logic/cubit/services/services_state.dart | 49 ------ .../get_it/api_connection_repository.dart | 13 +- lib/logic/models/service.dart | 33 +++- lib/ui/pages/backups/backup_details.dart | 8 +- lib/ui/pages/backups/backups_list.dart | 4 +- lib/ui/pages/backups/snapshot_modal.dart | 12 +- .../binds_migration/services_migration.dart | 10 +- .../pages/server_storage/server_storage.dart | 5 +- lib/ui/pages/services/service_page.dart | 8 +- lib/ui/pages/services/services.dart | 7 +- 18 files changed, 382 insertions(+), 173 deletions(-) create mode 100644 lib/logic/bloc/services/services_bloc.dart create mode 100644 lib/logic/bloc/services/services_event.dart create mode 100644 lib/logic/bloc/services/services_state.dart delete mode 100644 lib/logic/cubit/services/services_cubit.dart delete mode 100644 lib/logic/cubit/services/services_state.dart diff --git a/lib/config/bloc_config.dart b/lib/config/bloc_config.dart index b7d87f2e..ff7a8236 100644 --- a/lib/config/bloc_config.dart +++ b/lib/config/bloc_config.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart'; +import 'package:selfprivacy/logic/bloc/services/services_bloc.dart'; import 'package:selfprivacy/logic/cubit/connection_status/connection_status_bloc.dart'; import 'package:selfprivacy/logic/cubit/devices/devices_cubit.dart'; import 'package:selfprivacy/logic/cubit/recovery_key/recovery_key_cubit.dart'; @@ -11,7 +12,6 @@ import 'package:selfprivacy/logic/cubit/dns_records/dns_records_cubit.dart'; import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart'; import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart'; import 'package:selfprivacy/logic/cubit/server_volumes/server_volume_cubit.dart'; -import 'package:selfprivacy/logic/cubit/services/services_cubit.dart'; import 'package:selfprivacy/logic/cubit/support_system/support_system_cubit.dart'; import 'package:selfprivacy/logic/cubit/users/users_cubit.dart'; import 'package:selfprivacy/logic/cubit/provider_volumes/provider_volume_cubit.dart'; @@ -28,7 +28,7 @@ class BlocAndProviderConfig extends StatelessWidget { final serverInstallationCubit = ServerInstallationCubit()..load(); final supportSystemCubit = SupportSystemCubit(); final usersCubit = UsersCubit(serverInstallationCubit); - final servicesCubit = ServicesCubit(serverInstallationCubit); + final servicesBloc = ServicesBloc(); final backupsBloc = BackupsBloc(); final dnsRecordsCubit = DnsRecordsCubit(serverInstallationCubit); final recoveryKeyCubit = RecoveryKeyCubit(serverInstallationCubit); @@ -61,8 +61,7 @@ class BlocAndProviderConfig extends StatelessWidget { lazy: false, ), BlocProvider( - create: (final _) => servicesCubit..load(), - lazy: false, + create: (final _) => servicesBloc, ), BlocProvider( create: (final _) => backupsBloc, @@ -92,7 +91,7 @@ class BlocAndProviderConfig extends StatelessWidget { BlocProvider( create: (final _) => JobsCubit( usersCubit: usersCubit, - servicesCubit: servicesCubit, + servicesBloc: servicesBloc, ), ), ], diff --git a/lib/logic/bloc/backups/backups_bloc.dart b/lib/logic/bloc/backups/backups_bloc.dart index 55d4753a..fcdedfeb 100644 --- a/lib/logic/bloc/backups/backups_bloc.dart +++ b/lib/logic/bloc/backups/backups_bloc.dart @@ -193,7 +193,8 @@ class BackupsBloc extends Bloc { ); if (result.success == false) { getIt().showSnackBar( - result.message ?? "Couldn't initialize repository on your server."); + result.message ?? "Couldn't initialize repository on your server.", + ); emit(BackupsUnititialized()); return; } diff --git a/lib/logic/bloc/backups/backups_state.dart b/lib/logic/bloc/backups/backups_state.dart index 153b1e4e..e41c3b74 100644 --- a/lib/logic/bloc/backups/backups_state.dart +++ b/lib/logic/bloc/backups/backups_state.dart @@ -71,7 +71,8 @@ class BackupsUnititialized extends BackupsState { final BackblazeBucket? backblazeBucket, }) => BackupsUnititialized( - backblazeBucket: backblazeBucket ?? this.backblazeBucket); + backblazeBucket: backblazeBucket ?? this.backblazeBucket, + ); } class BackupsInitializing extends BackupsState { @@ -86,7 +87,8 @@ class BackupsInitializing extends BackupsState { final BackblazeBucket? backblazeBucket, }) => BackupsInitializing( - backblazeBucket: backblazeBucket ?? this.backblazeBucket); + backblazeBucket: backblazeBucket ?? this.backblazeBucket, + ); } class BackupsInitialized extends BackupsState { diff --git a/lib/logic/bloc/services/services_bloc.dart b/lib/logic/bloc/services/services_bloc.dart new file mode 100644 index 00000000..1d5293fd --- /dev/null +++ b/lib/logic/bloc/services/services_bloc.dart @@ -0,0 +1,144 @@ +import 'dart:async'; + +import 'package:easy_localization/easy_localization.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:selfprivacy/config/get_it_config.dart'; + +import 'package:selfprivacy/logic/models/service.dart'; + +part 'services_event.dart'; +part 'services_state.dart'; + +class ServicesBloc extends Bloc { + ServicesBloc() : super(ServicesInitial()) { + final connectionRepository = getIt(); + + _apiDataSubscription = connectionRepository.dataStream.listen( + (final ApiData apiData) { + add( + ServicesListUpdate([...apiData.services.data ?? []]), + ); + }, + ); + + if (connectionRepository.connectionStatus == ConnectionStatus.connected) { + add( + ServicesListUpdate( + [...connectionRepository.apiData.services.data ?? []], + ), + ); + } + + on( + _updateList, + ); + on( + _reload, + ); + on( + _restart, + ); + on( + _move, + ); + } + + Future _updateList( + final ServicesListUpdate event, + final Emitter emit, + ) async { + if (event.services.isEmpty) { + emit(ServicesInitial()); + return; + } + final newState = ServicesLoaded( + services: event.services, + lockedServices: state._lockedServices, + ); + emit(newState); + } + + Future _reload( + final ServicesReload event, + final Emitter emit, + ) async { + final currentState = state; + if (currentState is ServicesLoaded) { + emit(ServicesReloading.fromState(currentState)); + getIt().apiData.services.invalidate(); + await getIt().reload(null); + } + } + + Future awaitReload() async { + final currentState = state; + if (currentState is ServicesLoaded) { + getIt().apiData.services.invalidate(); + await getIt().reload(null); + } + } + + Future _restart( + final ServiceRestart event, + final Emitter emit, + ) async { + emit( + state.copyWith( + lockedServices: [ + ...state._lockedServices, + ServiceLock( + serviceId: event.service.id, + lockDuration: const Duration(seconds: 15), + ), + ], + ), + ); + final result = await getIt() + .api + .restartService(event.service.id); + if (!result.success) { + getIt().showSnackBar('jobs.generic_error'.tr()); + return; + } + if (!result.data) { + getIt() + .showSnackBar(result.message ?? 'jobs.generic_error'.tr()); + return; + } + } + + Future _move( + final ServiceMove event, + final Emitter emit, + ) async { + final migrationJob = await getIt() + .api + .moveService(event.service.id, event.destination); + if (!migrationJob.success) { + getIt() + .showSnackBar(migrationJob.message ?? 'jobs.generic_error'.tr()); + } + if (migrationJob.data != null) { + getIt() + .apiData + .serverJobs + .data + ?.add(migrationJob.data!); + getIt().emitData(); + } + } + + late StreamSubscription _apiDataSubscription; + + @override + void onChange(final Change change) { + super.onChange(change); + } + + @override + Future close() { + _apiDataSubscription.cancel(); + return super.close(); + } +} diff --git a/lib/logic/bloc/services/services_event.dart b/lib/logic/bloc/services/services_event.dart new file mode 100644 index 00000000..c8239152 --- /dev/null +++ b/lib/logic/bloc/services/services_event.dart @@ -0,0 +1,40 @@ +part of 'services_bloc.dart'; + +abstract class ServicesEvent extends Equatable { + const ServicesEvent(); +} + +class ServicesListUpdate extends ServicesEvent { + const ServicesListUpdate(this.services); + + final List services; + + @override + List get props => [services]; +} + +class ServicesReload extends ServicesEvent { + const ServicesReload(); + + @override + List get props => []; +} + +class ServiceRestart extends ServicesEvent { + const ServiceRestart(this.service); + + final Service service; + + @override + List get props => [service]; +} + +class ServiceMove extends ServicesEvent { + const ServiceMove(this.service, this.destination); + + final Service service; + final String destination; + + @override + List get props => [service, destination]; +} diff --git a/lib/logic/bloc/services/services_state.dart b/lib/logic/bloc/services/services_state.dart new file mode 100644 index 00000000..05d6bc89 --- /dev/null +++ b/lib/logic/bloc/services/services_state.dart @@ -0,0 +1,115 @@ +part of 'services_bloc.dart'; + +abstract class ServicesState extends Equatable { + ServicesState({final List lockedServices = const []}) + : _lockedServices = + lockedServices.where((final lock) => lock.isLocked).toList(); + final List _lockedServices; + List get services; + List get lockedServices => _lockedServices + .where((final lock) => lock.isLocked) + .map((final lock) => lock.serviceId) + .toList(); + + List get servicesThatCanBeBackedUp => services + .where( + (final service) => service.canBeBackedUp, + ) + .toList(); + + bool isServiceLocked(final String serviceId) => + lockedServices.contains(serviceId); + + Service? getServiceById(final String id) { + final service = services.firstWhere( + (final service) => service.id == id, + orElse: () => Service.empty, + ); + if (service.id == 'empty') { + return null; + } + return service; + } + + ServicesState copyWith({ + final List? services, + final List? lockedServices, + }); +} + +class ServiceLock extends Equatable { + ServiceLock({ + required this.serviceId, + required this.lockDuration, + }) : lockTime = DateTime.now(); + + final String serviceId; + final Duration lockDuration; + final DateTime lockTime; + + bool get isLocked => DateTime.now().isBefore(lockTime.add(lockDuration)); + + @override + List get props => [serviceId, lockDuration, lockTime]; +} + +class ServicesInitial extends ServicesState { + @override + List get props => []; + + @override + List get services => []; + + @override + ServicesState copyWith({ + final List? services, + final List? lockedServices, + }) => + ServicesInitial(); +} + +class ServicesLoaded extends ServicesState { + ServicesLoaded({ + required final List services, + required super.lockedServices, + }) : _servicesHachCode = Object.hashAll([...services]); + + final int _servicesHachCode; + + final apiConnectionRepository = getIt(); + + List get _services => + apiConnectionRepository.apiData.services.data ?? []; + + @override + List get services => _services; + + @override + List get props => [_servicesHachCode, _lockedServices]; + + @override + ServicesLoaded copyWith({ + final List? services, + final List? lockedServices, + }) => + ServicesLoaded( + services: services ?? this.services, + lockedServices: lockedServices ?? _lockedServices, + ); +} + +class ServicesReloading extends ServicesLoaded { + ServicesReloading({ + required super.services, + required super.lockedServices, + }); + + ServicesReloading.fromState(final ServicesLoaded state) + : super( + services: state.services, + lockedServices: state._lockedServices, + ); + + @override + List get props => [services, lockedServices]; +} diff --git a/lib/logic/cubit/client_jobs/client_jobs_cubit.dart b/lib/logic/cubit/client_jobs/client_jobs_cubit.dart index 59a33673..f9188985 100644 --- a/lib/logic/cubit/client_jobs/client_jobs_cubit.dart +++ b/lib/logic/cubit/client_jobs/client_jobs_cubit.dart @@ -5,7 +5,7 @@ import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart'; -import 'package:selfprivacy/logic/cubit/services/services_cubit.dart'; +import 'package:selfprivacy/logic/bloc/services/services_bloc.dart'; import 'package:selfprivacy/logic/cubit/users/users_cubit.dart'; import 'package:selfprivacy/logic/models/job.dart'; @@ -16,12 +16,12 @@ part 'client_jobs_state.dart'; class JobsCubit extends Cubit { JobsCubit({ required this.usersCubit, - required this.servicesCubit, + required this.servicesBloc, }) : super(JobsStateEmpty()); final ServerApi api = ServerApi(); final UsersCubit usersCubit; - final ServicesCubit servicesCubit; + final ServicesBloc servicesBloc; void addJob(final ClientJob job) { final jobs = currentJobList; @@ -90,7 +90,7 @@ class JobsCubit extends Cubit { await api.pullConfigurationUpdate(); await api.apply(); - await servicesCubit.load(); + servicesBloc.add(const ServicesReload()); emit(JobsStateEmpty()); } diff --git a/lib/logic/cubit/services/services_cubit.dart b/lib/logic/cubit/services/services_cubit.dart deleted file mode 100644 index a9849700..00000000 --- a/lib/logic/cubit/services/services_cubit.dart +++ /dev/null @@ -1,81 +0,0 @@ -import 'dart:async'; - -import 'package:easy_localization/easy_localization.dart'; -import 'package:selfprivacy/config/get_it_config.dart'; -import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart'; -import 'package:selfprivacy/logic/cubit/server_connection_dependent/server_connection_dependent_cubit.dart'; -import 'package:selfprivacy/logic/models/service.dart'; - -part 'services_state.dart'; - -class ServicesCubit extends ServerConnectionDependentCubit { - ServicesCubit(final ServerInstallationCubit serverInstallationCubit) - : super(const ServicesState.empty()); - final ServerApi api = ServerApi(); - Timer? timer; - @override - Future load() async { - final List services = await api.getAllServices(); - emit( - ServicesState( - services: services, - lockedServices: const [], - ), - ); - timer = Timer(const Duration(seconds: 10), () => reload(useTimer: true)); - } - - Future reload({final bool useTimer = false}) async { - final List services = await api.getAllServices(); - emit( - state.copyWith( - services: services, - ), - ); - if (useTimer) { - timer = Timer(const Duration(seconds: 60), () => reload(useTimer: true)); - } - } - - Future restart(final String serviceId) async { - emit(state.copyWith(lockedServices: [...state.lockedServices, serviceId])); - final result = await api.restartService(serviceId); - if (!result.success) { - getIt().showSnackBar('jobs.generic_error'.tr()); - return; - } - if (!result.data) { - getIt() - .showSnackBar(result.message ?? 'jobs.generic_error'.tr()); - return; - } - - await Future.delayed(const Duration(seconds: 2)); - unawaited(reload()); - await Future.delayed(const Duration(seconds: 10)); - emit( - state.copyWith( - lockedServices: state.lockedServices - .where((final element) => element != serviceId) - .toList(), - ), - ); - unawaited(reload()); - } - - Future moveService( - final String serviceId, - final String destination, - ) async { - await api.moveService(serviceId, destination); - } - - @override - void clear() async { - emit(const ServicesState.empty()); - if (timer != null && timer!.isActive) { - timer!.cancel(); - timer = null; - } - } -} diff --git a/lib/logic/cubit/services/services_state.dart b/lib/logic/cubit/services/services_state.dart deleted file mode 100644 index 7a136b54..00000000 --- a/lib/logic/cubit/services/services_state.dart +++ /dev/null @@ -1,49 +0,0 @@ -part of 'services_cubit.dart'; - -class ServicesState extends ServerInstallationDependendState { - const ServicesState({ - required this.services, - required this.lockedServices, - }); - - const ServicesState.empty() - : this(services: const [], lockedServices: const []); - - final List services; - final List lockedServices; - - List get servicesThatCanBeBackedUp => services - .where( - (final service) => service.canBeBackedUp, - ) - .toList(); - - bool isServiceLocked(final String serviceId) => - lockedServices.contains(serviceId); - - Service? getServiceById(final String id) { - final service = services.firstWhere( - (final service) => service.id == id, - orElse: () => Service.empty, - ); - if (service.id == 'empty') { - return null; - } - return service; - } - - @override - List get props => [ - services, - lockedServices, - ]; - - ServicesState copyWith({ - final List? services, - final List? lockedServices, - }) => - ServicesState( - services: services ?? this.services, - lockedServices: lockedServices ?? this.lockedServices, - ); -} diff --git a/lib/logic/get_it/api_connection_repository.dart b/lib/logic/get_it/api_connection_repository.dart index 72e9fb65..8503acbc 100644 --- a/lib/logic/get_it/api_connection_repository.dart +++ b/lib/logic/get_it/api_connection_repository.dart @@ -9,6 +9,7 @@ import 'package:selfprivacy/logic/models/backup.dart'; import 'package:selfprivacy/logic/models/hive/server_details.dart'; import 'package:selfprivacy/logic/models/hive/server_domain.dart'; import 'package:selfprivacy/logic/models/json/server_job.dart'; +import 'package:selfprivacy/logic/models/service.dart'; /// Repository for all API calls /// Stores the current state of all data from API and exposes it to Blocs. @@ -74,6 +75,7 @@ class ApiConnectionRepository { _apiData.serverJobs.data = await api.getServerJobs(); _apiData.backupConfig.data = await api.getBackupsConfiguration(); _apiData.backups.data = await api.getBackups(); + _apiData.services.data = await api.getAllServices(); _dataStream.add(_apiData); connectionStatus = ConnectionStatus.connected; @@ -109,6 +111,8 @@ class ApiConnectionRepository { .refetchData(version, () => _dataStream.add(_apiData)); await _apiData.backupConfig .refetchData(version, () => _dataStream.add(_apiData)); + await _apiData.services + .refetchData(version, () => _dataStream.add(_apiData)); } void emitData() { @@ -132,12 +136,17 @@ class ApiData { backups = ApiDataElement>( fetchData: () async => api.getBackups(), requiredApiVersion: '>=2.4.2', + ), + services = ApiDataElement>( + fetchData: () async => api.getAllServices(), + requiredApiVersion: '>=2.4.3', ); ApiDataElement> serverJobs; ApiDataElement apiVersion; ApiDataElement backupConfig; ApiDataElement> backups; + ApiDataElement> services; } enum ConnectionStatus { @@ -163,7 +172,9 @@ class ApiDataElement { final Future Function() fetchData; Future refetchData( - final Version version, final Function callback) async { + final Version version, + final Function callback, + ) async { if (VersionConstraint.parse(requiredApiVersion).allows(version)) { print('Fetching data for $runtimeType'); if (isExpired) { diff --git a/lib/logic/models/service.dart b/lib/logic/models/service.dart index 702b94db..5f2364e9 100644 --- a/lib/logic/models/service.dart +++ b/lib/logic/models/service.dart @@ -1,6 +1,7 @@ import 'dart:convert'; import 'package:easy_localization/easy_localization.dart'; +import 'package:equatable/equatable.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/schema.graphql.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/services.graphql.dart'; import 'package:selfprivacy/logic/models/disk_size.dart'; @@ -8,7 +9,7 @@ import 'package:selfprivacy/logic/models/json/dns_records.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/server_settings.graphql.dart'; -class Service { +class Service extends Equatable { Service.fromGraphQL(final Query$AllServices$services$allServices service) : this( id: service.id, @@ -37,7 +38,7 @@ class Service { [], url: service.url, ); - Service({ + const Service({ required this.id, required this.displayName, required this.description, @@ -72,7 +73,7 @@ class Service { return ''; } - static Service empty = Service( + static Service empty = const Service( id: 'empty', displayName: '', description: '', @@ -83,7 +84,7 @@ class Service { backupDescription: '', status: ServiceStatus.off, storageUsage: ServiceStorageUsage( - used: const DiskSize(byte: 0), + used: DiskSize(byte: 0), volume: '', ), svgIcon: '', @@ -104,16 +105,36 @@ class Service { final String svgIcon; final String? url; final List dnsRecords; + + @override + List get props => [ + id, + displayName, + description, + isEnabled, + isRequired, + isMovable, + canBeBackedUp, + backupDescription, + status, + storageUsage, + svgIcon, + dnsRecords, + url, + ]; } -class ServiceStorageUsage { - ServiceStorageUsage({ +class ServiceStorageUsage extends Equatable { + const ServiceStorageUsage({ required this.used, required this.volume, }); final DiskSize used; final String? volume; + + @override + List get props => [used, volume]; } enum ServiceStatus { diff --git a/lib/ui/pages/backups/backup_details.dart b/lib/ui/pages/backups/backup_details.dart index a99d6385..d5dbe4e4 100644 --- a/lib/ui/pages/backups/backup_details.dart +++ b/lib/ui/pages/backups/backup_details.dart @@ -5,7 +5,7 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart'; -import 'package:selfprivacy/logic/cubit/services/services_cubit.dart'; +import 'package:selfprivacy/logic/bloc/services/services_bloc.dart'; import 'package:selfprivacy/logic/models/backup.dart'; import 'package:selfprivacy/logic/models/json/server_job.dart'; import 'package:selfprivacy/logic/models/service.dart'; @@ -39,7 +39,7 @@ class BackupDetailsPage extends StatelessWidget { final bool preventActions = backupsState.preventActions; final List backups = backupsState.backups; final List services = - context.watch().state.servicesThatCanBeBackedUp; + context.watch().state.servicesThatCanBeBackedUp; final Duration? autobackupPeriod = backupsState.autobackupPeriod; final List backupJobs = context .watch() @@ -298,7 +298,7 @@ class BackupDetailsPage extends StatelessWidget { children: backups.take(15).map( (final Backup backup) { final service = context - .read() + .read() .state .getServiceById(backup.serviceId); return ListTile( @@ -419,7 +419,7 @@ class BackupDetailsPage extends StatelessWidget { : () => { context .read() - .add(const ForceSnapshotListUpdate()) + .add(const ForceSnapshotListUpdate()), }, ), const SizedBox(height: 8), diff --git a/lib/ui/pages/backups/backups_list.dart b/lib/ui/pages/backups/backups_list.dart index 79eb7a95..fd2b541e 100644 --- a/lib/ui/pages/backups/backups_list.dart +++ b/lib/ui/pages/backups/backups_list.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:flutter_svg/svg.dart'; import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart'; -import 'package:selfprivacy/logic/cubit/services/services_cubit.dart'; +import 'package:selfprivacy/logic/bloc/services/services_bloc.dart'; import 'package:selfprivacy/logic/models/backup.dart'; import 'package:selfprivacy/logic/models/service.dart'; import 'package:selfprivacy/ui/helpers/modals.dart'; @@ -43,7 +43,7 @@ class BackupsListPage extends StatelessWidget { ...backups.map( (final Backup backup) { final service = context - .read() + .read() .state .getServiceById(backup.serviceId); return ListTile( diff --git a/lib/ui/pages/backups/snapshot_modal.dart b/lib/ui/pages/backups/snapshot_modal.dart index 3236ddc1..54dbaae4 100644 --- a/lib/ui/pages/backups/snapshot_modal.dart +++ b/lib/ui/pages/backups/snapshot_modal.dart @@ -4,7 +4,7 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart'; import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart'; -import 'package:selfprivacy/logic/cubit/services/services_cubit.dart'; +import 'package:selfprivacy/logic/bloc/services/services_bloc.dart'; import 'package:selfprivacy/logic/models/backup.dart'; import 'package:selfprivacy/logic/models/json/server_job.dart'; import 'package:selfprivacy/logic/models/service.dart'; @@ -48,7 +48,7 @@ class _SnapshotModalState extends State { final bool isServiceBusy = busyServices.contains(widget.snapshot.serviceId); final Service? service = context - .read() + .read() .state .getServiceById(widget.snapshot.serviceId); @@ -153,8 +153,12 @@ class _SnapshotModalState extends State { onPressed: isServiceBusy ? null : () { - context.read().add(RestoreBackup( - widget.snapshot.id, selectedStrategy)); + context.read().add( + RestoreBackup( + widget.snapshot.id, + selectedStrategy, + ), + ); Navigator.of(context).pop(); getIt() .showSnackBar('backup.restore_started'.tr()); diff --git a/lib/ui/pages/server_storage/binds_migration/services_migration.dart b/lib/ui/pages/server_storage/binds_migration/services_migration.dart index 453e7067..171fb53d 100644 --- a/lib/ui/pages/server_storage/binds_migration/services_migration.dart +++ b/lib/ui/pages/server_storage/binds_migration/services_migration.dart @@ -2,7 +2,7 @@ import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart'; -import 'package:selfprivacy/logic/cubit/services/services_cubit.dart'; +import 'package:selfprivacy/logic/bloc/services/services_bloc.dart'; import 'package:selfprivacy/logic/models/disk_size.dart'; import 'package:selfprivacy/logic/models/service.dart'; import 'package:selfprivacy/ui/components/buttons/brand_button.dart'; @@ -175,9 +175,11 @@ class _ServicesMigrationPageState extends State { onPressed: () { for (final service in widget.services) { if (serviceToDisk[service.id] != null) { - context.read().moveService( - service.id, - serviceToDisk[service.id]!, + context.read().add( + ServiceMove( + service, + serviceToDisk[service.id]!, + ), ); } } diff --git a/lib/ui/pages/server_storage/server_storage.dart b/lib/ui/pages/server_storage/server_storage.dart index eb7a586b..fb1602b9 100644 --- a/lib/ui/pages/server_storage/server_storage.dart +++ b/lib/ui/pages/server_storage/server_storage.dart @@ -3,7 +3,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; -import 'package:selfprivacy/logic/cubit/services/services_cubit.dart'; +import 'package:selfprivacy/logic/bloc/services/services_bloc.dart'; import 'package:selfprivacy/logic/models/service.dart'; import 'package:selfprivacy/ui/components/buttons/outlined_button.dart'; import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; @@ -30,8 +30,7 @@ class _ServerStoragePageState extends State { final bool isReady = context.watch().state is ServerInstallationFinished; - final List services = - context.watch().state.services; + final List services = context.watch().state.services; if (!isReady) { return BrandHeroScreen( diff --git a/lib/ui/pages/services/service_page.dart b/lib/ui/pages/services/service_page.dart index 96560db0..4291c6e2 100644 --- a/lib/ui/pages/services/service_page.dart +++ b/lib/ui/pages/services/service_page.dart @@ -4,7 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_volumes/server_volume_cubit.dart'; -import 'package:selfprivacy/logic/cubit/services/services_cubit.dart'; +import 'package:selfprivacy/logic/bloc/services/services_bloc.dart'; import 'package:selfprivacy/logic/models/job.dart'; import 'package:selfprivacy/logic/models/service.dart'; import 'package:selfprivacy/ui/components/cards/filled_card.dart'; @@ -26,7 +26,7 @@ class _ServicePageState extends State { @override Widget build(final BuildContext context) { final Service? service = - context.watch().state.getServiceById(widget.serviceId); + context.watch().state.getServiceById(widget.serviceId); if (service == null) { return const BrandHeroScreen( @@ -43,7 +43,7 @@ class _ServicePageState extends State { service.status == ServiceStatus.off; final bool serviceLocked = - context.watch().state.isServiceLocked(service.id); + context.watch().state.isServiceLocked(service.id); return BrandHeroScreen( hasBackButton: true, @@ -81,7 +81,7 @@ class _ServicePageState extends State { ListTile( iconColor: Theme.of(context).colorScheme.onBackground, onTap: () => { - context.read().restart(service.id), + context.read().add(ServiceRestart(service)), }, leading: const Icon(Icons.restart_alt_outlined), title: Text( diff --git a/lib/ui/pages/services/services.dart b/lib/ui/pages/services/services.dart index 2aa31d8b..3bc0de48 100644 --- a/lib/ui/pages/services/services.dart +++ b/lib/ui/pages/services/services.dart @@ -3,7 +3,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:selfprivacy/config/brand_theme.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; -import 'package:selfprivacy/logic/cubit/services/services_cubit.dart'; +import 'package:selfprivacy/logic/bloc/services/services_bloc.dart'; import 'package:selfprivacy/logic/models/service.dart'; import 'package:selfprivacy/logic/models/state_types.dart'; import 'package:selfprivacy/ui/components/brand_header/brand_header.dart'; @@ -30,7 +30,7 @@ class _ServicesPageState extends State { final isReady = context.watch().state is ServerInstallationFinished; - final services = [...context.watch().state.services]; + final services = [...context.watch().state.services]; services .sort((final a, final b) => a.status.index.compareTo(b.status.index)); @@ -52,7 +52,8 @@ class _ServicesPageState extends State { ) : RefreshIndicator( onRefresh: () async { - await context.read().reload(); + // Create a ServicesRelaod event and wait for the state to change. + await context.read().awaitReload(); }, child: ListView( padding: paddingH15V0, From bdd00683cd25415b20806bf8542cb2ded5f9610e Mon Sep 17 00:00:00 2001 From: Inex Code Date: Mon, 29 Jan 2024 20:14:12 +0400 Subject: [PATCH 07/34] refactor: Optimistic state update when removing all finished jobs --- .../bloc/server_jobs/server_jobs_bloc.dart | 11 +-------- .../get_it/api_connection_repository.dart | 23 +++++++++++++++++++ 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/lib/logic/bloc/server_jobs/server_jobs_bloc.dart b/lib/logic/bloc/server_jobs/server_jobs_bloc.dart index bf428d23..0104f3e5 100644 --- a/lib/logic/bloc/server_jobs/server_jobs_bloc.dart +++ b/lib/logic/bloc/server_jobs/server_jobs_bloc.dart @@ -61,16 +61,7 @@ class ServerJobsBloc extends Bloc { final RemoveAllFinishedJobs event, final Emitter emit, ) async { - final List finishedJobs = state.serverJobList - .where( - (final ServerJob job) => - job.status == JobStatusEnum.finished || - job.status == JobStatusEnum.error, - ) - .toList(); - for (final ServerJob job in finishedJobs) { - await getIt().removeServerJob(job.uid); - } + await getIt().removeAllFinishedServerJobs(); } @override diff --git a/lib/logic/get_it/api_connection_repository.dart b/lib/logic/get_it/api_connection_repository.dart index 8503acbc..8a64b09d 100644 --- a/lib/logic/get_it/api_connection_repository.dart +++ b/lib/logic/get_it/api_connection_repository.dart @@ -42,6 +42,29 @@ class ApiConnectionRepository { _dataStream.add(_apiData); } + Future removeAllFinishedServerJobs() async { + final List finishedJobs = _apiData.serverJobs.data + ?.where( + (final ServerJob element) => + element.status == JobStatusEnum.finished || + element.status == JobStatusEnum.error, + ) + .toList() ?? + []; + // Optimistically remove the jobs from the list + _apiData.serverJobs.data?.removeWhere( + (final ServerJob element) => + element.status == JobStatusEnum.finished || + element.status == JobStatusEnum.error, + ); + _dataStream.add(_apiData); + + await Future.forEach( + finishedJobs, + (final ServerJob job) async => removeServerJob(job.uid), + ); + } + void dispose() { _dataStream.close(); _connectionStatusStream.close(); From 9bfaf5d381c0ff64b868d6a40ad8bb2f7da8d5ec Mon Sep 17 00:00:00 2001 From: Inex Code Date: Mon, 29 Jan 2024 20:45:49 +0400 Subject: [PATCH 08/34] refactor: Remove usesBinds from ApiServerVolumeCubit --- lib/logic/cubit/server_volumes/server_volume_cubit.dart | 3 --- lib/logic/cubit/server_volumes/server_volume_state.dart | 8 ++------ 2 files changed, 2 insertions(+), 9 deletions(-) diff --git a/lib/logic/cubit/server_volumes/server_volume_cubit.dart b/lib/logic/cubit/server_volumes/server_volume_cubit.dart index 327b9623..41854f84 100644 --- a/lib/logic/cubit/server_volumes/server_volume_cubit.dart +++ b/lib/logic/cubit/server_volumes/server_volume_cubit.dart @@ -35,7 +35,6 @@ class ApiServerVolumeCubit ApiServerVolumeState( this.state._volumes, this.state.status, - this.state.usesBinds, DiskStatus.fromVolumes(this.state._volumes, state.volumes), ), ); @@ -44,7 +43,6 @@ class ApiServerVolumeCubit Future reload() async { final volumes = await serverApi.getServerDiskVolumes(); - final usesBinds = await serverApi.isUsingBinds(); var status = LoadingStatus.error; if (volumes.isNotEmpty) { @@ -55,7 +53,6 @@ class ApiServerVolumeCubit ApiServerVolumeState( volumes, status, - usesBinds, DiskStatus.fromVolumes( volumes, providerVolumeCubit.state.volumes, diff --git a/lib/logic/cubit/server_volumes/server_volume_state.dart b/lib/logic/cubit/server_volumes/server_volume_state.dart index d68daa58..3ea7aaa5 100644 --- a/lib/logic/cubit/server_volumes/server_volume_state.dart +++ b/lib/logic/cubit/server_volumes/server_volume_state.dart @@ -4,16 +4,14 @@ class ApiServerVolumeState extends ServerInstallationDependendState { const ApiServerVolumeState( this._volumes, this.status, - this.usesBinds, this._diskStatus, ); ApiServerVolumeState.initial() - : this(const [], LoadingStatus.uninitialized, null, DiskStatus()); + : this(const [], LoadingStatus.uninitialized, DiskStatus()); final List _volumes; final DiskStatus _diskStatus; - final bool? usesBinds; final LoadingStatus status; List get volumes => _diskStatus.diskVolumes; @@ -27,16 +25,14 @@ class ApiServerVolumeState extends ServerInstallationDependendState { ApiServerVolumeState copyWith({ final List? volumes, final LoadingStatus? status, - final bool? usesBinds, final DiskStatus? diskStatus, }) => ApiServerVolumeState( volumes ?? _volumes, status ?? this.status, - usesBinds ?? this.usesBinds, diskStatus ?? _diskStatus, ); @override - List get props => [_volumes, status, usesBinds]; + List get props => [_volumes, status]; } From 149969aed8d983996f9e81930c0c692e0fca7937 Mon Sep 17 00:00:00 2001 From: Inex Code Date: Mon, 29 Jan 2024 20:49:20 +0400 Subject: [PATCH 09/34] refactor: Rename ServerVolume model to reflect that it is tied to provider --- .../provider_volume_cubit.dart | 2 +- .../provider_volume_state.dart | 6 ++--- .../server_installation_cubit.dart | 2 +- .../server_installation_repository.dart | 8 +++--- lib/logic/models/disk_status.dart | 12 ++++----- lib/logic/models/hive/server_details.dart | 6 ++--- .../server_providers/digital_ocean.dart | 26 +++++++++---------- .../providers/server_providers/hetzner.dart | 22 ++++++++-------- .../server_providers/server_provider.dart | 24 ++++++++--------- 9 files changed, 54 insertions(+), 54 deletions(-) diff --git a/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart b/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart index 0f705d1e..9ca5213e 100644 --- a/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart +++ b/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart @@ -121,7 +121,7 @@ class ApiProviderVolumeCubit } Future createVolume(final DiskSize size) async { - final ServerVolume? volume = (await ProvidersController + final ServerProviderVolume? volume = (await ProvidersController .currentServerProvider! .createVolume(size.gibibyte.toInt())) .data; diff --git a/lib/logic/cubit/provider_volumes/provider_volume_state.dart b/lib/logic/cubit/provider_volumes/provider_volume_state.dart index 3858ef14..21faa52c 100644 --- a/lib/logic/cubit/provider_volumes/provider_volume_state.dart +++ b/lib/logic/cubit/provider_volumes/provider_volume_state.dart @@ -5,14 +5,14 @@ class ApiProviderVolumeState extends ServerInstallationDependendState { const ApiProviderVolumeState.initial() : this(const [], LoadingStatus.uninitialized, false); - final List _volumes; + final List _volumes; final LoadingStatus status; final bool isResizing; - List get volumes => _volumes; + List get volumes => _volumes; ApiProviderVolumeState copyWith({ - final List? volumes, + final List? volumes, final LoadingStatus? status, final bool? isResizing, }) => diff --git a/lib/logic/cubit/server_installation/server_installation_cubit.dart b/lib/logic/cubit/server_installation/server_installation_cubit.dart index 01f46aa4..f72ee055 100644 --- a/lib/logic/cubit/server_installation/server_installation_cubit.dart +++ b/lib/logic/cubit/server_installation/server_installation_cubit.dart @@ -725,7 +725,7 @@ class ServerInstallationCubit extends Cubit { ip4: server.ip, id: server.id, createTime: server.created, - volume: ServerVolume( + volume: ServerProviderVolume( id: 0, name: 'recovered_volume', sizeByte: 0, diff --git a/lib/logic/cubit/server_installation/server_installation_repository.dart b/lib/logic/cubit/server_installation/server_installation_repository.dart index 0e6c0dcd..a59cfd22 100644 --- a/lib/logic/cubit/server_installation/server_installation_repository.dart +++ b/lib/logic/cubit/server_installation/server_installation_repository.dart @@ -313,7 +313,7 @@ class ServerInstallationRepository { if (result.success) { return ServerHostingDetails( apiToken: result.data, - volume: ServerVolume( + volume: ServerProviderVolume( id: 0, name: '', sizeByte: 0, @@ -350,7 +350,7 @@ class ServerInstallationRepository { if (result.success) { return ServerHostingDetails( apiToken: result.data, - volume: ServerVolume( + volume: ServerProviderVolume( id: 0, name: '', sizeByte: 0, @@ -385,7 +385,7 @@ class ServerInstallationRepository { if (await serverApi.isHttpServerWorking()) { return ServerHostingDetails( apiToken: apiToken, - volume: ServerVolume( + volume: ServerProviderVolume( id: 0, name: '', serverId: 0, @@ -416,7 +416,7 @@ class ServerInstallationRepository { if (result.success) { return ServerHostingDetails( apiToken: result.data, - volume: ServerVolume( + volume: ServerProviderVolume( id: 0, name: '', sizeByte: 0, diff --git a/lib/logic/models/disk_status.dart b/lib/logic/models/disk_status.dart index 2a37ad77..033ed0c7 100644 --- a/lib/logic/models/disk_status.dart +++ b/lib/logic/models/disk_status.dart @@ -15,7 +15,7 @@ class DiskVolume { DiskVolume.fromServerDiscVolume( final ServerDiskVolume volume, - final ServerVolume? providerVolume, + final ServerProviderVolume? providerVolume, ) : this( name: volume.name, sizeTotal: DiskSize( @@ -52,7 +52,7 @@ class DiskVolume { bool root; bool isResizable; ServerDiskVolume? serverDiskVolume; - ServerVolume? providerVolume; + ServerProviderVolume? providerVolume; /// from 0.0 to 1.0 double get percentage => @@ -67,7 +67,7 @@ class DiskVolume { final bool? root, final bool? isResizable, final ServerDiskVolume? serverDiskVolume, - final ServerVolume? providerVolume, + final ServerProviderVolume? providerVolume, }) => DiskVolume( sizeUsed: sizeUsed ?? this.sizeUsed, @@ -83,14 +83,14 @@ class DiskVolume { class DiskStatus { DiskStatus.fromVolumes( final List serverVolumes, - final List providerVolumes, + final List providerVolumes, ) { diskVolumes = serverVolumes.map(( final ServerDiskVolume volume, ) { - ServerVolume? providerVolume; + ServerProviderVolume? providerVolume; - for (final ServerVolume iterableProviderVolume in providerVolumes) { + for (final ServerProviderVolume iterableProviderVolume in providerVolumes) { if (iterableProviderVolume.linuxDevice == null || volume.model == null || volume.serial == null) { diff --git a/lib/logic/models/hive/server_details.dart b/lib/logic/models/hive/server_details.dart index 0e9d825a..e332be66 100644 --- a/lib/logic/models/hive/server_details.dart +++ b/lib/logic/models/hive/server_details.dart @@ -28,7 +28,7 @@ class ServerHostingDetails { final DateTime? startTime; @HiveField(4) - final ServerVolume volume; + final ServerProviderVolume volume; @HiveField(5) final String apiToken; @@ -52,8 +52,8 @@ class ServerHostingDetails { } @HiveType(typeId: 5) -class ServerVolume { - ServerVolume({ +class ServerProviderVolume { + ServerProviderVolume({ required this.id, required this.name, required this.sizeByte, diff --git a/lib/logic/providers/server_providers/digital_ocean.dart b/lib/logic/providers/server_providers/digital_ocean.dart index 741e35ac..a1df7a70 100644 --- a/lib/logic/providers/server_providers/digital_ocean.dart +++ b/lib/logic/providers/server_providers/digital_ocean.dart @@ -336,7 +336,7 @@ class DigitalOceanServerProvider extends ServerProvider { } final volumes = await getVolumes(); - final ServerVolume volumeToRemove; + final ServerProviderVolume volumeToRemove; volumeToRemove = volumes.data.firstWhere( (final el) => el.serverId == foundServer!.id, ); @@ -548,10 +548,10 @@ class DigitalOceanServerProvider extends ServerProvider { ); @override - Future>> getVolumes({ + Future>> getVolumes({ final String? status, }) async { - final List volumes = []; + final List volumes = []; final result = await _adapter.api().getVolumes(); @@ -568,7 +568,7 @@ class DigitalOceanServerProvider extends ServerProvider { int id = 0; for (final rawVolume in result.data) { final String volumeName = rawVolume.name; - final volume = ServerVolume( + final volume = ServerProviderVolume( id: id++, name: volumeName, sizeByte: rawVolume.sizeGigabytes * 1024 * 1024 * 1024, @@ -597,8 +597,8 @@ class DigitalOceanServerProvider extends ServerProvider { } @override - Future> createVolume(final int gb) async { - ServerVolume? volume; + Future> createVolume(final int gb) async { + ServerProviderVolume? volume; final result = await _adapter.api().createVolume(gb); @@ -623,7 +623,7 @@ class DigitalOceanServerProvider extends ServerProvider { } final String volumeName = result.data!.name; - volume = ServerVolume( + volume = ServerProviderVolume( id: getVolumesResult.data.length, name: volumeName, sizeByte: result.data!.sizeGigabytes, @@ -638,10 +638,10 @@ class DigitalOceanServerProvider extends ServerProvider { ); } - Future> getVolume( + Future> getVolume( final String volumeUuid, ) async { - ServerVolume? requestedVolume; + ServerProviderVolume? requestedVolume; final result = await getVolumes(); @@ -668,7 +668,7 @@ class DigitalOceanServerProvider extends ServerProvider { @override Future> attachVolume( - final ServerVolume volume, + final ServerProviderVolume volume, final int serverId, ) async => _adapter.api().attachVolume( @@ -678,7 +678,7 @@ class DigitalOceanServerProvider extends ServerProvider { @override Future> detachVolume( - final ServerVolume volume, + final ServerProviderVolume volume, ) async => _adapter.api().detachVolume( volume.name, @@ -687,7 +687,7 @@ class DigitalOceanServerProvider extends ServerProvider { @override Future> deleteVolume( - final ServerVolume volume, + final ServerProviderVolume volume, ) async => _adapter.api().deleteVolume( volume.uuid!, @@ -695,7 +695,7 @@ class DigitalOceanServerProvider extends ServerProvider { @override Future> resizeVolume( - final ServerVolume volume, + final ServerProviderVolume volume, final DiskSize size, ) async => _adapter.api().resizeVolume( diff --git a/lib/logic/providers/server_providers/hetzner.dart b/lib/logic/providers/server_providers/hetzner.dart index 58e66701..ff60b136 100644 --- a/lib/logic/providers/server_providers/hetzner.dart +++ b/lib/logic/providers/server_providers/hetzner.dart @@ -280,7 +280,7 @@ class HetznerServerProvider extends ServerProvider { id: serverResult.data!.id, ip4: serverResult.data!.publicNet.ipv4!.ip, createTime: DateTime.now(), - volume: ServerVolume( + volume: ServerProviderVolume( id: volume.id, name: volume.name, sizeByte: volume.size * 1024 * 1024 * 1024, @@ -580,10 +580,10 @@ class HetznerServerProvider extends ServerProvider { } @override - Future>> getVolumes({ + Future>> getVolumes({ final String? status, }) async { - final List volumes = []; + final List volumes = []; final result = await _adapter.api().getVolumes(); @@ -603,7 +603,7 @@ class HetznerServerProvider extends ServerProvider { final volumeServer = rawVolume.serverId; final String volumeName = rawVolume.name; final volumeDevice = rawVolume.linuxDevice; - final volume = ServerVolume( + final volume = ServerProviderVolume( id: volumeId, name: volumeName, sizeByte: volumeSize, @@ -629,8 +629,8 @@ class HetznerServerProvider extends ServerProvider { } @override - Future> createVolume(final int gb) async { - ServerVolume? volume; + Future> createVolume(final int gb) async { + ServerProviderVolume? volume; final result = await _adapter.api().createVolume(gb); @@ -644,7 +644,7 @@ class HetznerServerProvider extends ServerProvider { } try { - volume = ServerVolume( + volume = ServerProviderVolume( id: result.data!.id, name: result.data!.name, sizeByte: result.data!.size * 1024 * 1024 * 1024, @@ -669,12 +669,12 @@ class HetznerServerProvider extends ServerProvider { } @override - Future> deleteVolume(final ServerVolume volume) async => + Future> deleteVolume(final ServerProviderVolume volume) async => _adapter.api().deleteVolume(volume.id); @override Future> resizeVolume( - final ServerVolume volume, + final ServerProviderVolume volume, final DiskSize size, ) async => _adapter.api().resizeVolume( @@ -690,7 +690,7 @@ class HetznerServerProvider extends ServerProvider { @override Future> attachVolume( - final ServerVolume volume, + final ServerProviderVolume volume, final int serverId, ) async => _adapter.api().attachVolume( @@ -706,7 +706,7 @@ class HetznerServerProvider extends ServerProvider { @override Future> detachVolume( - final ServerVolume volume, + final ServerProviderVolume volume, ) async => _adapter.api().detachVolume( volume.id, diff --git a/lib/logic/providers/server_providers/server_provider.dart b/lib/logic/providers/server_providers/server_provider.dart index ce7b6944..ed7d3519 100644 --- a/lib/logic/providers/server_providers/server_provider.dart +++ b/lib/logic/providers/server_providers/server_provider.dart @@ -94,35 +94,35 @@ abstract class ServerProvider { /// main server type pricing Future> getAdditionalPricing(); - /// Returns [ServerVolume] of all available volumes + /// Returns [ServerProviderVolume] of all available volumes /// assigned to the authorized user and attached to active machine. - Future>> getVolumes({final String? status}); + Future>> getVolumes({final String? status}); - /// Tries to create an empty unattached [ServerVolume]. + /// Tries to create an empty unattached [ServerProviderVolume]. /// /// If success, returns this volume information. - Future> createVolume(final int gb); + Future> createVolume(final int gb); - /// Tries to delete the requested accessible [ServerVolume]. - Future> deleteVolume(final ServerVolume volume); + /// Tries to delete the requested accessible [ServerProviderVolume]. + Future> deleteVolume(final ServerProviderVolume volume); - /// Tries to resize the requested accessible [ServerVolume] + /// Tries to resize the requested accessible [ServerProviderVolume] /// to the provided size **(not by!)**, must be greater than current size. Future> resizeVolume( - final ServerVolume volume, + final ServerProviderVolume volume, final DiskSize size, ); - /// Tries to attach the requested accessible [ServerVolume] + /// Tries to attach the requested accessible [ServerProviderVolume] /// to an accessible machine by the provided identificator. Future> attachVolume( - final ServerVolume volume, + final ServerProviderVolume volume, final int serverId, ); - /// Tries to attach the requested accessible [ServerVolume] + /// Tries to attach the requested accessible [ServerProviderVolume] /// from any machine. - Future> detachVolume(final ServerVolume volume); + Future> detachVolume(final ServerProviderVolume volume); /// Returns metedata of an accessible machine by the provided identificator /// to show on ServerDetailsScreen. From f46865ca7111046f3280a04913341a866c7a98ae Mon Sep 17 00:00:00 2001 From: Inex Code Date: Wed, 31 Jan 2024 14:57:12 +0400 Subject: [PATCH 10/34] style: Apply directives_ordering lint --- lib/config/bloc_config.dart | 10 +++++----- lib/config/get_it_config.dart | 2 +- lib/logic/cubit/dns_records/dns_records_cubit.dart | 3 +-- .../server_connection_dependent_cubit.dart | 3 ++- .../cubit/server_volumes/server_volume_cubit.dart | 2 +- lib/logic/models/service.dart | 3 +-- lib/ui/components/jobs_content/jobs_content.dart | 2 +- lib/ui/pages/backups/backup_details.dart | 6 +++--- lib/ui/pages/backups/change_period_modal.dart | 2 +- lib/ui/pages/backups/change_rotation_quotas_modal.dart | 2 +- lib/ui/pages/backups/copy_encryption_key_modal.dart | 2 +- lib/ui/pages/more/more.dart | 4 ++-- lib/ui/pages/server_storage/server_storage.dart | 6 +++--- lib/ui/pages/server_storage/storage_card.dart | 2 +- lib/ui/pages/services/service_page.dart | 2 +- lib/ui/pages/services/services.dart | 2 +- 16 files changed, 26 insertions(+), 27 deletions(-) diff --git a/lib/config/bloc_config.dart b/lib/config/bloc_config.dart index ff7a8236..5f25d32d 100644 --- a/lib/config/bloc_config.dart +++ b/lib/config/bloc_config.dart @@ -1,20 +1,20 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart'; +import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart'; import 'package:selfprivacy/logic/bloc/services/services_bloc.dart'; +import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart'; +import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart'; import 'package:selfprivacy/logic/cubit/connection_status/connection_status_bloc.dart'; import 'package:selfprivacy/logic/cubit/devices/devices_cubit.dart'; +import 'package:selfprivacy/logic/cubit/dns_records/dns_records_cubit.dart'; +import 'package:selfprivacy/logic/cubit/provider_volumes/provider_volume_cubit.dart'; import 'package:selfprivacy/logic/cubit/recovery_key/recovery_key_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_detailed_info/server_detailed_info_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; -import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart'; -import 'package:selfprivacy/logic/cubit/dns_records/dns_records_cubit.dart'; -import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart'; -import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart'; import 'package:selfprivacy/logic/cubit/server_volumes/server_volume_cubit.dart'; import 'package:selfprivacy/logic/cubit/support_system/support_system_cubit.dart'; import 'package:selfprivacy/logic/cubit/users/users_cubit.dart'; -import 'package:selfprivacy/logic/cubit/provider_volumes/provider_volume_cubit.dart'; class BlocAndProviderConfig extends StatelessWidget { const BlocAndProviderConfig({super.key, this.child}); diff --git a/lib/config/get_it_config.dart b/lib/config/get_it_config.dart index b8ac1866..78e40261 100644 --- a/lib/config/get_it_config.dart +++ b/lib/config/get_it_config.dart @@ -5,9 +5,9 @@ import 'package:selfprivacy/logic/get_it/console.dart'; import 'package:selfprivacy/logic/get_it/navigation.dart'; export 'package:selfprivacy/logic/get_it/api_config.dart'; +export 'package:selfprivacy/logic/get_it/api_connection_repository.dart'; export 'package:selfprivacy/logic/get_it/console.dart'; export 'package:selfprivacy/logic/get_it/navigation.dart'; -export 'package:selfprivacy/logic/get_it/api_connection_repository.dart'; final GetIt getIt = GetIt.instance; diff --git a/lib/logic/cubit/dns_records/dns_records_cubit.dart b/lib/logic/cubit/dns_records/dns_records_cubit.dart index 6e928b63..fcf5d152 100644 --- a/lib/logic/cubit/dns_records/dns_records_cubit.dart +++ b/lib/logic/cubit/dns_records/dns_records_cubit.dart @@ -1,11 +1,10 @@ import 'package:cubit_form/cubit_form.dart'; import 'package:selfprivacy/config/get_it_config.dart'; +import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart'; import 'package:selfprivacy/logic/api_maps/rest_maps/dns_providers/desired_dns_record.dart'; import 'package:selfprivacy/logic/cubit/server_connection_dependent/server_connection_dependent_cubit.dart'; import 'package:selfprivacy/logic/models/hive/server_domain.dart'; import 'package:selfprivacy/logic/models/json/dns_records.dart'; - -import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart'; import 'package:selfprivacy/logic/providers/providers_controller.dart'; import 'package:selfprivacy/utils/network_utils.dart'; diff --git a/lib/logic/cubit/server_connection_dependent/server_connection_dependent_cubit.dart b/lib/logic/cubit/server_connection_dependent/server_connection_dependent_cubit.dart index f46c7ef9..073c9bdd 100644 --- a/lib/logic/cubit/server_connection_dependent/server_connection_dependent_cubit.dart +++ b/lib/logic/cubit/server_connection_dependent/server_connection_dependent_cubit.dart @@ -1,6 +1,7 @@ import 'dart:async'; -import 'package:flutter_bloc/flutter_bloc.dart'; + import 'package:equatable/equatable.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:selfprivacy/config/get_it_config.dart'; export 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; diff --git a/lib/logic/cubit/server_volumes/server_volume_cubit.dart b/lib/logic/cubit/server_volumes/server_volume_cubit.dart index 41854f84..2941ed3f 100644 --- a/lib/logic/cubit/server_volumes/server_volume_cubit.dart +++ b/lib/logic/cubit/server_volumes/server_volume_cubit.dart @@ -2,8 +2,8 @@ import 'dart:async'; import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart'; import 'package:selfprivacy/logic/common_enum/common_enum.dart'; -import 'package:selfprivacy/logic/cubit/server_connection_dependent/server_connection_dependent_cubit.dart'; import 'package:selfprivacy/logic/cubit/provider_volumes/provider_volume_cubit.dart'; +import 'package:selfprivacy/logic/cubit/server_connection_dependent/server_connection_dependent_cubit.dart'; import 'package:selfprivacy/logic/models/disk_status.dart'; import 'package:selfprivacy/logic/models/json/server_disk_volume.dart'; diff --git a/lib/logic/models/service.dart b/lib/logic/models/service.dart index 5f2364e9..d5b4e0af 100644 --- a/lib/logic/models/service.dart +++ b/lib/logic/models/service.dart @@ -3,12 +3,11 @@ import 'dart:convert'; import 'package:easy_localization/easy_localization.dart'; import 'package:equatable/equatable.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/schema.graphql.dart'; +import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/server_settings.graphql.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/services.graphql.dart'; import 'package:selfprivacy/logic/models/disk_size.dart'; import 'package:selfprivacy/logic/models/json/dns_records.dart'; -import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/server_settings.graphql.dart'; - class Service extends Equatable { Service.fromGraphQL(final Query$AllServices$services$allServices service) : this( diff --git a/lib/ui/components/jobs_content/jobs_content.dart b/lib/ui/components/jobs_content/jobs_content.dart index 8053d821..d583e039 100644 --- a/lib/ui/components/jobs_content/jobs_content.dart +++ b/lib/ui/components/jobs_content/jobs_content.dart @@ -2,9 +2,9 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:selfprivacy/config/brand_theme.dart'; +import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart'; import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; -import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart'; import 'package:selfprivacy/logic/models/json/server_job.dart'; import 'package:selfprivacy/ui/components/brand_loader/brand_loader.dart'; import 'package:selfprivacy/ui/components/buttons/brand_button.dart'; diff --git a/lib/ui/pages/backups/backup_details.dart b/lib/ui/pages/backups/backup_details.dart index d5dbe4e4..5dd94a08 100644 --- a/lib/ui/pages/backups/backup_details.dart +++ b/lib/ui/pages/backups/backup_details.dart @@ -3,18 +3,18 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart'; -import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart'; import 'package:selfprivacy/logic/bloc/services/services_bloc.dart'; +import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/logic/models/backup.dart'; import 'package:selfprivacy/logic/models/json/server_job.dart'; import 'package:selfprivacy/logic/models/service.dart'; import 'package:selfprivacy/logic/models/state_types.dart'; +import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart'; import 'package:selfprivacy/ui/components/buttons/brand_button.dart'; import 'package:selfprivacy/ui/components/jobs_content/server_job_card.dart'; -import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; -import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart'; import 'package:selfprivacy/ui/helpers/modals.dart'; +import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; import 'package:selfprivacy/ui/pages/backups/change_period_modal.dart'; import 'package:selfprivacy/ui/pages/backups/change_rotation_quotas_modal.dart'; import 'package:selfprivacy/ui/pages/backups/copy_encryption_key_modal.dart'; diff --git a/lib/ui/pages/backups/change_period_modal.dart b/lib/ui/pages/backups/change_period_modal.dart index 87c9fe2d..d3217b64 100644 --- a/lib/ui/pages/backups/change_period_modal.dart +++ b/lib/ui/pages/backups/change_period_modal.dart @@ -1,8 +1,8 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart'; -import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart'; +import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/utils/extensions/duration.dart'; class ChangeAutobackupsPeriodModal extends StatefulWidget { diff --git a/lib/ui/pages/backups/change_rotation_quotas_modal.dart b/lib/ui/pages/backups/change_rotation_quotas_modal.dart index f3972387..1a7e91bc 100644 --- a/lib/ui/pages/backups/change_rotation_quotas_modal.dart +++ b/lib/ui/pages/backups/change_rotation_quotas_modal.dart @@ -1,8 +1,8 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart'; -import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart'; +import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/logic/models/backup.dart'; class ChangeRotationQuotasModal extends StatefulWidget { diff --git a/lib/ui/pages/backups/copy_encryption_key_modal.dart b/lib/ui/pages/backups/copy_encryption_key_modal.dart index 19c98727..b5ceb2fe 100644 --- a/lib/ui/pages/backups/copy_encryption_key_modal.dart +++ b/lib/ui/pages/backups/copy_encryption_key_modal.dart @@ -3,8 +3,8 @@ import 'dart:async'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart'; -import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart'; +import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/ui/components/info_box/info_box.dart'; import 'package:selfprivacy/utils/platform_adapter.dart'; diff --git a/lib/ui/pages/more/more.dart b/lib/ui/pages/more/more.dart index 189446db..0dfa3e4e 100644 --- a/lib/ui/pages/more/more.dart +++ b/lib/ui/pages/more/more.dart @@ -4,11 +4,11 @@ import 'package:flutter/material.dart'; import 'package:ionicons/ionicons.dart'; import 'package:selfprivacy/config/brand_theme.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; -import 'package:selfprivacy/ui/components/cards/filled_card.dart'; import 'package:selfprivacy/ui/components/brand_header/brand_header.dart'; import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart'; -import 'package:selfprivacy/utils/breakpoints.dart'; +import 'package:selfprivacy/ui/components/cards/filled_card.dart'; import 'package:selfprivacy/ui/router/router.dart'; +import 'package:selfprivacy/utils/breakpoints.dart'; @RoutePage() class MorePage extends StatelessWidget { diff --git a/lib/ui/pages/server_storage/server_storage.dart b/lib/ui/pages/server_storage/server_storage.dart index fb1602b9..2d356e38 100644 --- a/lib/ui/pages/server_storage/server_storage.dart +++ b/lib/ui/pages/server_storage/server_storage.dart @@ -2,13 +2,13 @@ import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/logic/bloc/services/services_bloc.dart'; +import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; +import 'package:selfprivacy/logic/models/disk_status.dart'; import 'package:selfprivacy/logic/models/service.dart'; import 'package:selfprivacy/ui/components/buttons/outlined_button.dart'; -import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; -import 'package:selfprivacy/logic/models/disk_status.dart'; import 'package:selfprivacy/ui/components/storage_list_items/server_storage_list_item.dart'; +import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; import 'package:selfprivacy/ui/router/router.dart'; @RoutePage() diff --git a/lib/ui/pages/server_storage/storage_card.dart b/lib/ui/pages/server_storage/storage_card.dart index 240c99c9..15ac54fc 100644 --- a/lib/ui/pages/server_storage/storage_card.dart +++ b/lib/ui/pages/server_storage/storage_card.dart @@ -2,9 +2,9 @@ import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; +import 'package:selfprivacy/logic/models/disk_status.dart'; import 'package:selfprivacy/logic/models/state_types.dart'; import 'package:selfprivacy/ui/components/icon_status_mask/icon_status_mask.dart'; -import 'package:selfprivacy/logic/models/disk_status.dart'; import 'package:selfprivacy/ui/components/storage_list_items/server_storage_list_item.dart'; import 'package:selfprivacy/ui/router/router.dart'; diff --git a/lib/ui/pages/services/service_page.dart b/lib/ui/pages/services/service_page.dart index 4291c6e2..bd18e5ea 100644 --- a/lib/ui/pages/services/service_page.dart +++ b/lib/ui/pages/services/service_page.dart @@ -2,9 +2,9 @@ import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; +import 'package:selfprivacy/logic/bloc/services/services_bloc.dart'; import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_volumes/server_volume_cubit.dart'; -import 'package:selfprivacy/logic/bloc/services/services_bloc.dart'; import 'package:selfprivacy/logic/models/job.dart'; import 'package:selfprivacy/logic/models/service.dart'; import 'package:selfprivacy/ui/components/cards/filled_card.dart'; diff --git a/lib/ui/pages/services/services.dart b/lib/ui/pages/services/services.dart index 4c150b89..ba713ccc 100644 --- a/lib/ui/pages/services/services.dart +++ b/lib/ui/pages/services/services.dart @@ -3,8 +3,8 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:selfprivacy/config/brand_theme.dart'; -import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/logic/bloc/services/services_bloc.dart'; +import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/logic/models/service.dart'; import 'package:selfprivacy/logic/models/state_types.dart'; import 'package:selfprivacy/ui/components/brand_header/brand_header.dart'; From fe6f90016506d754a12be389ae3bccbb5520dd11 Mon Sep 17 00:00:00 2001 From: Inex Code Date: Wed, 31 Jan 2024 15:04:59 +0400 Subject: [PATCH 11/34] refactor: Move event handler registration to the beginning of blocs --- lib/logic/bloc/backups/backups_bloc.dart | 62 +++++++++---------- .../bloc/server_jobs/server_jobs_bloc.dart | 18 +++--- lib/logic/bloc/services/services_bloc.dart | 26 ++++---- 3 files changed, 53 insertions(+), 53 deletions(-) diff --git a/lib/logic/bloc/backups/backups_bloc.dart b/lib/logic/bloc/backups/backups_bloc.dart index fcdedfeb..5f918868 100644 --- a/lib/logic/bloc/backups/backups_bloc.dart +++ b/lib/logic/bloc/backups/backups_bloc.dart @@ -17,6 +17,37 @@ part 'backups_state.dart'; class BackupsBloc extends Bloc { BackupsBloc() : super(BackupsInitial()) { + on( + _loadState, + ); + on( + _resetState, + ); + on( + _updateState, + ); + on( + _initializeRepository, + ); + on( + _forceSnapshotListUpdate, + ); + on( + _createBackups, + ); + on( + _restoreBackup, + ); + on( + _setAutobackupPeriod, + ); + on( + _setAutobackupQuotas, + ); + on( + _forgetSnapshot, + ); + final connectionRepository = getIt(); _apiStatusSubscription = connectionRepository.connectionStatusStream @@ -58,37 +89,6 @@ class BackupsBloc extends Bloc { add(const BackupsServerLoaded()); isLoaded = true; } - - on( - _loadState, - ); - on( - _resetState, - ); - on( - _updateState, - ); - on( - _initializeRepository, - ); - on( - _forceSnapshotListUpdate, - ); - on( - _createBackups, - ); - on( - _restoreBackup, - ); - on( - _setAutobackupPeriod, - ); - on( - _setAutobackupQuotas, - ); - on( - _forgetSnapshot, - ); } final BackblazeApi backblaze = BackblazeApi(); diff --git a/lib/logic/bloc/server_jobs/server_jobs_bloc.dart b/lib/logic/bloc/server_jobs/server_jobs_bloc.dart index 0104f3e5..c0396661 100644 --- a/lib/logic/bloc/server_jobs/server_jobs_bloc.dart +++ b/lib/logic/bloc/server_jobs/server_jobs_bloc.dart @@ -15,15 +15,6 @@ class ServerJobsBloc extends Bloc { : super( ServerJobsInitialState(), ) { - final apiConnectionRepository = getIt(); - _apiDataSubscription = apiConnectionRepository.dataStream.listen( - (final ApiData apiData) { - add( - ServerJobsListChanged([...apiData.serverJobs.data ?? []]), - ); - }, - ); - on( _mapServerJobsListChangedToState, ); @@ -33,6 +24,15 @@ class ServerJobsBloc extends Bloc { on( _mapRemoveAllFinishedJobsToState, ); + + final apiConnectionRepository = getIt(); + _apiDataSubscription = apiConnectionRepository.dataStream.listen( + (final ApiData apiData) { + add( + ServerJobsListChanged([...apiData.serverJobs.data ?? []]), + ); + }, + ); } StreamSubscription? _apiDataSubscription; diff --git a/lib/logic/bloc/services/services_bloc.dart b/lib/logic/bloc/services/services_bloc.dart index 1d5293fd..0f7e927d 100644 --- a/lib/logic/bloc/services/services_bloc.dart +++ b/lib/logic/bloc/services/services_bloc.dart @@ -12,6 +12,19 @@ part 'services_state.dart'; class ServicesBloc extends Bloc { ServicesBloc() : super(ServicesInitial()) { + on( + _updateList, + ); + on( + _reload, + ); + on( + _restart, + ); + on( + _move, + ); + final connectionRepository = getIt(); _apiDataSubscription = connectionRepository.dataStream.listen( @@ -29,19 +42,6 @@ class ServicesBloc extends Bloc { ), ); } - - on( - _updateList, - ); - on( - _reload, - ); - on( - _restart, - ); - on( - _move, - ); } Future _updateList( From 02870c3149265fbcdad32a136df3587cb721ea57 Mon Sep 17 00:00:00 2001 From: Inex Code Date: Wed, 31 Jan 2024 15:05:12 +0400 Subject: [PATCH 12/34] style: Formatting --- lib/logic/models/disk_status.dart | 3 ++- lib/logic/providers/server_providers/digital_ocean.dart | 4 +++- lib/logic/providers/server_providers/hetzner.dart | 8 ++++++-- lib/logic/providers/server_providers/server_provider.dart | 4 +++- 4 files changed, 14 insertions(+), 5 deletions(-) diff --git a/lib/logic/models/disk_status.dart b/lib/logic/models/disk_status.dart index 033ed0c7..1010f747 100644 --- a/lib/logic/models/disk_status.dart +++ b/lib/logic/models/disk_status.dart @@ -90,7 +90,8 @@ class DiskStatus { ) { ServerProviderVolume? providerVolume; - for (final ServerProviderVolume iterableProviderVolume in providerVolumes) { + for (final ServerProviderVolume iterableProviderVolume + in providerVolumes) { if (iterableProviderVolume.linuxDevice == null || volume.model == null || volume.serial == null) { diff --git a/lib/logic/providers/server_providers/digital_ocean.dart b/lib/logic/providers/server_providers/digital_ocean.dart index a1df7a70..c69dfa8c 100644 --- a/lib/logic/providers/server_providers/digital_ocean.dart +++ b/lib/logic/providers/server_providers/digital_ocean.dart @@ -597,7 +597,9 @@ class DigitalOceanServerProvider extends ServerProvider { } @override - Future> createVolume(final int gb) async { + Future> createVolume( + final int gb, + ) async { ServerProviderVolume? volume; final result = await _adapter.api().createVolume(gb); diff --git a/lib/logic/providers/server_providers/hetzner.dart b/lib/logic/providers/server_providers/hetzner.dart index ff60b136..d6e4f1ba 100644 --- a/lib/logic/providers/server_providers/hetzner.dart +++ b/lib/logic/providers/server_providers/hetzner.dart @@ -629,7 +629,9 @@ class HetznerServerProvider extends ServerProvider { } @override - Future> createVolume(final int gb) async { + Future> createVolume( + final int gb, + ) async { ServerProviderVolume? volume; final result = await _adapter.api().createVolume(gb); @@ -669,7 +671,9 @@ class HetznerServerProvider extends ServerProvider { } @override - Future> deleteVolume(final ServerProviderVolume volume) async => + Future> deleteVolume( + final ServerProviderVolume volume, + ) async => _adapter.api().deleteVolume(volume.id); @override diff --git a/lib/logic/providers/server_providers/server_provider.dart b/lib/logic/providers/server_providers/server_provider.dart index ed7d3519..bdb56936 100644 --- a/lib/logic/providers/server_providers/server_provider.dart +++ b/lib/logic/providers/server_providers/server_provider.dart @@ -96,7 +96,9 @@ abstract class ServerProvider { /// Returns [ServerProviderVolume] of all available volumes /// assigned to the authorized user and attached to active machine. - Future>> getVolumes({final String? status}); + Future>> getVolumes({ + final String? status, + }); /// Tries to create an empty unattached [ServerProviderVolume]. /// From 725c592086ba55b46b6d7dd6699007f40b93cb21 Mon Sep 17 00:00:00 2001 From: Inex Code Date: Wed, 31 Jan 2024 15:14:37 +0400 Subject: [PATCH 13/34] refactor: Fix callbacks returning sets --- lib/ui/pages/backups/backup_details.dart | 21 +++--- lib/ui/pages/backups/backups_list.dart | 8 +-- lib/ui/pages/services/service_page.dart | 17 ++--- lib/ui/pages/services/services.dart | 5 +- lib/ui/pages/users/user_details.dart | 88 +++++++++++------------- 5 files changed, 62 insertions(+), 77 deletions(-) diff --git a/lib/ui/pages/backups/backup_details.dart b/lib/ui/pages/backups/backup_details.dart index 5dd94a08..6eb9d009 100644 --- a/lib/ui/pages/backups/backup_details.dart +++ b/lib/ui/pages/backups/backup_details.dart @@ -335,13 +335,12 @@ class BackupDetailsPage extends StatelessWidget { 'backup.forget_snapshot_alert'.tr(), actionButtonTitle: 'backup.forget_snapshot'.tr(), - actionButtonOnPressed: () => { - context.read().add( - ForgetSnapshot( - backup.id, + actionButtonOnPressed: () => + context.read().add( + ForgetSnapshot( + backup.id, + ), ), - ), - }, ); }, title: Text( @@ -416,11 +415,9 @@ class BackupDetailsPage extends StatelessWidget { ), onTap: preventActions ? null - : () => { - context - .read() - .add(const ForceSnapshotListUpdate()), - }, + : () => context + .read() + .add(const ForceSnapshotListUpdate()), ), const SizedBox(height: 8), const Divider(), @@ -444,7 +441,7 @@ class BackupDetailsPage extends StatelessWidget { ), // onTap: preventActions // ? null - // : () => {context.read().reuploadKey()}, + // : () => context.read().reuploadKey(), ), ], ), diff --git a/lib/ui/pages/backups/backups_list.dart b/lib/ui/pages/backups/backups_list.dart index fd2b541e..20843ef4 100644 --- a/lib/ui/pages/backups/backups_list.dart +++ b/lib/ui/pages/backups/backups_list.dart @@ -75,11 +75,9 @@ class BackupsListPage extends StatelessWidget { alertTitle: 'backup.forget_snapshot'.tr(), description: 'backup.forget_snapshot_alert'.tr(), actionButtonTitle: 'backup.forget_snapshot'.tr(), - actionButtonOnPressed: () => { - context - .read() - .add(ForgetSnapshot(backup.id)), - }, + actionButtonOnPressed: () => context + .read() + .add(ForgetSnapshot(backup.id)), ); }, title: Text( diff --git a/lib/ui/pages/services/service_page.dart b/lib/ui/pages/services/service_page.dart index bd18e5ea..a7c2be34 100644 --- a/lib/ui/pages/services/service_page.dart +++ b/lib/ui/pages/services/service_page.dart @@ -80,9 +80,8 @@ class _ServicePageState extends State { const SizedBox(height: 8), ListTile( iconColor: Theme.of(context).colorScheme.onBackground, - onTap: () => { - context.read().add(ServiceRestart(service)), - }, + onTap: () => + context.read().add(ServiceRestart(service)), leading: const Icon(Icons.restart_alt_outlined), title: Text( 'service_page.restart'.tr(), @@ -92,14 +91,12 @@ class _ServicePageState extends State { ), ListTile( iconColor: Theme.of(context).colorScheme.onBackground, - onTap: () => { - context.read().addJob( - ServiceToggleJob( - service: service, - needToTurnOn: serviceDisabled, - ), + onTap: () => context.read().addJob( + ServiceToggleJob( + service: service, + needToTurnOn: serviceDisabled, ), - }, + ), leading: const Icon(Icons.power_settings_new), title: Text( serviceDisabled diff --git a/lib/ui/pages/services/services.dart b/lib/ui/pages/services/services.dart index ba713ccc..994e2040 100644 --- a/lib/ui/pages/services/services.dart +++ b/lib/ui/pages/services/services.dart @@ -51,10 +51,7 @@ class _ServicesPageState extends State { iconData: BrandIcons.box, ) : RefreshIndicator( - onRefresh: () async { - // Create a ServicesRelaod event and wait for the state to change. - await context.read().awaitReload(); - }, + onRefresh: context.read().awaitReload, child: ListView( padding: paddingH15V0, children: [ diff --git a/lib/ui/pages/users/user_details.dart b/lib/ui/pages/users/user_details.dart index 9523ccf4..0dd2c6d8 100644 --- a/lib/ui/pages/users/user_details.dart +++ b/lib/ui/pages/users/user_details.dart @@ -46,17 +46,15 @@ class UserDetailsPage extends StatelessWidget { const SizedBox(height: 8), ListTile( iconColor: Theme.of(context).colorScheme.onBackground, - onTap: () => { - showModalBottomSheet( - context: context, - isScrollControlled: true, - useRootNavigator: true, - builder: (final BuildContext context) => Padding( - padding: MediaQuery.of(context).viewInsets, - child: ResetPassword(user: user), - ), + onTap: () => showModalBottomSheet( + context: context, + isScrollControlled: true, + useRootNavigator: true, + builder: (final BuildContext context) => Padding( + padding: MediaQuery.of(context).viewInsets, + child: ResetPassword(user: user), ), - }, + ), leading: const Icon(Icons.lock_reset_outlined), title: Text( 'users.reset_password'.tr(), @@ -87,45 +85,43 @@ class _DeleteUserTile extends StatelessWidget { Widget build(final BuildContext context) => ListTile( iconColor: Theme.of(context).colorScheme.error, textColor: Theme.of(context).colorScheme.error, - onTap: () => { - showDialog( - context: context, - // useRootNavigator: false, - builder: (final BuildContext context) => AlertDialog( - title: Text('basis.confirmation'.tr()), - content: SingleChildScrollView( - child: ListBody( - children: [ - Text( - 'users.delete_confirm_question'.tr(), - ), - ], - ), - ), - actions: [ - TextButton( - child: Text('basis.cancel'.tr()), - onPressed: () { - context.router.pop(); - }, - ), - TextButton( - child: Text( - 'basis.delete'.tr(), - style: TextStyle( - color: Theme.of(context).colorScheme.error, - ), + onTap: () => showDialog( + context: context, + // useRootNavigator: false, + builder: (final BuildContext context) => AlertDialog( + title: Text('basis.confirmation'.tr()), + content: SingleChildScrollView( + child: ListBody( + children: [ + Text( + 'users.delete_confirm_question'.tr(), ), - onPressed: () { - context.read().addJob(DeleteUserJob(user: user)); - context.router.childControllers.first.pop(); - context.router.pop(); - }, - ), - ], + ], + ), ), + actions: [ + TextButton( + child: Text('basis.cancel'.tr()), + onPressed: () { + context.router.pop(); + }, + ), + TextButton( + child: Text( + 'basis.delete'.tr(), + style: TextStyle( + color: Theme.of(context).colorScheme.error, + ), + ), + onPressed: () { + context.read().addJob(DeleteUserJob(user: user)); + context.router.childControllers.first.pop(); + context.router.pop(); + }, + ), + ], ), - }, + ), leading: const Icon(Icons.person_remove_outlined), title: Text( 'users.delete_user'.tr(), From 21c0e200a97be6ce63ddd42884b618fa149687c0 Mon Sep 17 00:00:00 2001 From: Inex Code Date: Wed, 31 Jan 2024 16:03:15 +0400 Subject: [PATCH 14/34] fix: Regenerate codegen for updated model name --- lib/config/hive_config.dart | 2 +- lib/logic/models/hive/server_details.g.dart | 12 ++++++------ 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/config/hive_config.dart b/lib/config/hive_config.dart index 01118bba..55b35e9e 100644 --- a/lib/config/hive_config.dart +++ b/lib/config/hive_config.dart @@ -20,7 +20,7 @@ class HiveConfig { Hive.registerAdapter(ServerDomainAdapter()); Hive.registerAdapter(BackupsCredentialAdapter()); Hive.registerAdapter(BackblazeBucketAdapter()); - Hive.registerAdapter(ServerVolumeAdapter()); + Hive.registerAdapter(ServerProviderVolumeAdapter()); Hive.registerAdapter(UserTypeAdapter()); Hive.registerAdapter(DnsProviderTypeAdapter()); Hive.registerAdapter(ServerProviderTypeAdapter()); diff --git a/lib/logic/models/hive/server_details.g.dart b/lib/logic/models/hive/server_details.g.dart index 3bb443d7..491344b9 100644 --- a/lib/logic/models/hive/server_details.g.dart +++ b/lib/logic/models/hive/server_details.g.dart @@ -20,7 +20,7 @@ class ServerHostingDetailsAdapter extends TypeAdapter { ip4: fields[0] as String, id: fields[1] as int, createTime: fields[3] as DateTime?, - volume: fields[4] as ServerVolume, + volume: fields[4] as ServerProviderVolume, apiToken: fields[5] as String, provider: fields[6] == null ? ServerProviderType.hetzner @@ -60,17 +60,17 @@ class ServerHostingDetailsAdapter extends TypeAdapter { typeId == other.typeId; } -class ServerVolumeAdapter extends TypeAdapter { +class ServerProviderVolumeAdapter extends TypeAdapter { @override final int typeId = 5; @override - ServerVolume read(BinaryReader reader) { + ServerProviderVolume read(BinaryReader reader) { final numOfFields = reader.readByte(); final fields = { for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), }; - return ServerVolume( + return ServerProviderVolume( id: fields[1] as int, name: fields[2] as String, sizeByte: fields[3] == null ? 10737418240 : fields[3] as int, @@ -81,7 +81,7 @@ class ServerVolumeAdapter extends TypeAdapter { } @override - void write(BinaryWriter writer, ServerVolume obj) { + void write(BinaryWriter writer, ServerProviderVolume obj) { writer ..writeByte(6) ..writeByte(1) @@ -104,7 +104,7 @@ class ServerVolumeAdapter extends TypeAdapter { @override bool operator ==(Object other) => identical(this, other) || - other is ServerVolumeAdapter && + other is ServerProviderVolumeAdapter && runtimeType == other.runtimeType && typeId == other.typeId; } From 1ba8f324fea6ebaf6ab6cbe2366fc2c806741612 Mon Sep 17 00:00:00 2001 From: Inex Code Date: Wed, 31 Jan 2024 16:17:27 +0400 Subject: [PATCH 15/34] refactor: Use transformers for blocs --- lib/logic/bloc/backups/backups_bloc.dart | 11 +++++++++++ lib/logic/bloc/server_jobs/server_jobs_bloc.dart | 4 ++++ lib/logic/bloc/services/services_bloc.dart | 5 +++++ pubspec.lock | 8 ++++++++ pubspec.yaml | 1 + 5 files changed, 29 insertions(+) diff --git a/lib/logic/bloc/backups/backups_bloc.dart b/lib/logic/bloc/backups/backups_bloc.dart index 5f918868..c5e2c43f 100644 --- a/lib/logic/bloc/backups/backups_bloc.dart +++ b/lib/logic/bloc/backups/backups_bloc.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:bloc_concurrency/bloc_concurrency.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -19,33 +20,43 @@ class BackupsBloc extends Bloc { BackupsBloc() : super(BackupsInitial()) { on( _loadState, + transformer: droppable(), ); on( _resetState, + transformer: droppable(), ); on( _updateState, + transformer: droppable(), ); on( _initializeRepository, + transformer: droppable(), ); on( _forceSnapshotListUpdate, + transformer: droppable(), ); on( _createBackups, + transformer: sequential(), ); on( _restoreBackup, + transformer: sequential(), ); on( _setAutobackupPeriod, + transformer: restartable(), ); on( _setAutobackupQuotas, + transformer: restartable(), ); on( _forgetSnapshot, + transformer: sequential(), ); final connectionRepository = getIt(); diff --git a/lib/logic/bloc/server_jobs/server_jobs_bloc.dart b/lib/logic/bloc/server_jobs/server_jobs_bloc.dart index c0396661..43b415e6 100644 --- a/lib/logic/bloc/server_jobs/server_jobs_bloc.dart +++ b/lib/logic/bloc/server_jobs/server_jobs_bloc.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:bloc_concurrency/bloc_concurrency.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:selfprivacy/config/get_it_config.dart'; @@ -17,12 +18,15 @@ class ServerJobsBloc extends Bloc { ) { on( _mapServerJobsListChangedToState, + transformer: sequential(), ); on( _mapRemoveServerJobToState, + transformer: sequential(), ); on( _mapRemoveAllFinishedJobsToState, + transformer: droppable(), ); final apiConnectionRepository = getIt(); diff --git a/lib/logic/bloc/services/services_bloc.dart b/lib/logic/bloc/services/services_bloc.dart index 0f7e927d..7f84eca7 100644 --- a/lib/logic/bloc/services/services_bloc.dart +++ b/lib/logic/bloc/services/services_bloc.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:bloc_concurrency/bloc_concurrency.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -14,15 +15,19 @@ class ServicesBloc extends Bloc { ServicesBloc() : super(ServicesInitial()) { on( _updateList, + transformer: sequential(), ); on( _reload, + transformer: droppable(), ); on( _restart, + transformer: sequential(), ); on( _move, + transformer: sequential(), ); final connectionRepository = getIt(); diff --git a/pubspec.lock b/pubspec.lock index e1874330..4c2cee42 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -81,6 +81,14 @@ packages: url: "https://pub.dev" source: hosted version: "8.1.2" + bloc_concurrency: + dependency: "direct main" + description: + name: bloc_concurrency + sha256: d945b33641a3c3f12fa12a1437e77f784444dd9a3a66a3a4f5aaa6e049154d36 + url: "https://pub.dev" + source: hosted + version: "0.2.3" boolean_selector: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 12a071d1..289888d7 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,6 +11,7 @@ dependencies: animations: ^2.0.8 auto_route: ^7.8.4 auto_size_text: ^3.0.0 + bloc_concurrency: ^0.2.3 crypt: ^4.3.1 cubit_form: ^2.0.1 device_info_plus: ^9.1.1 From e330f71b63aaf15136375dfee6c52d494933bd06 Mon Sep 17 00:00:00 2001 From: Inex Code Date: Wed, 31 Jan 2024 18:06:22 +0400 Subject: [PATCH 16/34] refactor: Optimistic state update when forgetting a snapshot --- lib/logic/bloc/backups/backups_bloc.dart | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/logic/bloc/backups/backups_bloc.dart b/lib/logic/bloc/backups/backups_bloc.dart index c5e2c43f..5c0e9e64 100644 --- a/lib/logic/bloc/backups/backups_bloc.dart +++ b/lib/logic/bloc/backups/backups_bloc.dart @@ -366,6 +366,14 @@ class BackupsBloc extends Bloc { ) async { final currentState = state; if (currentState is BackupsInitialized) { + // Optimistically remove the snapshot from the list + getIt().apiData.backups.data = + getIt() + .apiData + .backups + .data + ?.where((final Backup backup) => backup.id != event.backupId) + .toList(); emit(BackupsBusy.fromState(currentState)); final GenericResult result = await getIt().api.forgetSnapshot( @@ -377,16 +385,8 @@ class BackupsBloc extends Bloc { } else if (result.data == false) { getIt() .showSnackBar('backup.forget_snapshot_error'.tr()); - } else { - final backups = getIt().apiData.backups.data; - if (backups != null) { - getIt().apiData.backups.data = backups - .where((final Backup backup) => backup.id != event.backupId) - .toList(); - } } emit(currentState); - getIt().emitData(); } } From 3222a9b500b06c9d5a1b9b4248f94bdad2da7e56 Mon Sep 17 00:00:00 2001 From: Inex Code Date: Wed, 31 Jan 2024 18:06:49 +0400 Subject: [PATCH 17/34] refactor: Init blocs in initState and not in widget build --- lib/config/bloc_config.dart | 67 +++++++++++++------ lib/logic/cubit/devices/devices_cubit.dart | 3 +- .../cubit/dns_records/dns_records_cubit.dart | 2 +- .../provider_volume_cubit.dart | 3 +- .../recovery_key/recovery_key_cubit.dart | 3 +- .../server_detailed_info_cubit.dart | 3 +- .../server_volumes/server_volume_cubit.dart | 1 - lib/logic/cubit/users/users_cubit.dart | 2 +- 8 files changed, 51 insertions(+), 33 deletions(-) diff --git a/lib/config/bloc_config.dart b/lib/config/bloc_config.dart index 5f25d32d..09d0f361 100644 --- a/lib/config/bloc_config.dart +++ b/lib/config/bloc_config.dart @@ -16,29 +16,52 @@ import 'package:selfprivacy/logic/cubit/server_volumes/server_volume_cubit.dart' import 'package:selfprivacy/logic/cubit/support_system/support_system_cubit.dart'; import 'package:selfprivacy/logic/cubit/users/users_cubit.dart'; -class BlocAndProviderConfig extends StatelessWidget { +class BlocAndProviderConfig extends StatefulWidget { const BlocAndProviderConfig({super.key, this.child}); final Widget? child; + @override + BlocAndProviderConfigState createState() => BlocAndProviderConfigState(); +} + +class BlocAndProviderConfigState extends State { + late final ServerInstallationCubit serverInstallationCubit; + late final SupportSystemCubit supportSystemCubit; + late final UsersCubit usersCubit; + late final ServicesBloc servicesBloc; + late final BackupsBloc backupsBloc; + late final DnsRecordsCubit dnsRecordsCubit; + late final RecoveryKeyCubit recoveryKeyCubit; + late final ApiDevicesCubit apiDevicesCubit; + late final ApiProviderVolumeCubit apiVolumesCubit; + late final ApiServerVolumeCubit apiServerVolumesCubit; + late final ServerJobsBloc serverJobsBloc; + late final ConnectionStatusBloc connectionStatusBloc; + late final ServerDetailsCubit serverDetailsCubit; + + @override + void initState() { + super.initState(); + serverInstallationCubit = ServerInstallationCubit()..load(); + supportSystemCubit = SupportSystemCubit(); + usersCubit = UsersCubit(); + servicesBloc = ServicesBloc(); + backupsBloc = BackupsBloc(); + dnsRecordsCubit = DnsRecordsCubit(); + recoveryKeyCubit = RecoveryKeyCubit(); + apiDevicesCubit = ApiDevicesCubit(); + apiVolumesCubit = ApiProviderVolumeCubit(); + apiServerVolumesCubit = ApiServerVolumeCubit(apiVolumesCubit); + serverJobsBloc = ServerJobsBloc(); + connectionStatusBloc = ConnectionStatusBloc(); + serverDetailsCubit = ServerDetailsCubit(); + } + @override Widget build(final BuildContext context) { const isDark = false; const isAutoDark = true; - final serverInstallationCubit = ServerInstallationCubit()..load(); - final supportSystemCubit = SupportSystemCubit(); - final usersCubit = UsersCubit(serverInstallationCubit); - final servicesBloc = ServicesBloc(); - final backupsBloc = BackupsBloc(); - final dnsRecordsCubit = DnsRecordsCubit(serverInstallationCubit); - final recoveryKeyCubit = RecoveryKeyCubit(serverInstallationCubit); - final apiDevicesCubit = ApiDevicesCubit(serverInstallationCubit); - final apiVolumesCubit = ApiProviderVolumeCubit(serverInstallationCubit); - final apiServerVolumesCubit = - ApiServerVolumeCubit(serverInstallationCubit, apiVolumesCubit); - final serverJobsBloc = ServerJobsBloc(); - final connectionStatusBloc = ConnectionStatusBloc(); - final serverDetailsCubit = ServerDetailsCubit(serverInstallationCubit); return MultiProvider( providers: [ @@ -57,7 +80,7 @@ class BlocAndProviderConfig extends StatelessWidget { lazy: false, ), BlocProvider( - create: (final _) => usersCubit..load(), + create: (final _) => usersCubit, lazy: false, ), BlocProvider( @@ -70,23 +93,23 @@ class BlocAndProviderConfig extends StatelessWidget { create: (final _) => dnsRecordsCubit, ), BlocProvider( - create: (final _) => recoveryKeyCubit..load(), + create: (final _) => recoveryKeyCubit, ), BlocProvider( - create: (final _) => apiDevicesCubit..load(), + create: (final _) => apiDevicesCubit, ), BlocProvider( - create: (final _) => apiVolumesCubit..load(), + create: (final _) => apiVolumesCubit, ), BlocProvider( - create: (final _) => apiServerVolumesCubit..load(), + create: (final _) => apiServerVolumesCubit, ), BlocProvider( create: (final _) => serverJobsBloc, ), BlocProvider(create: (final _) => connectionStatusBloc), BlocProvider( - create: (final _) => serverDetailsCubit..load(), + create: (final _) => serverDetailsCubit, ), BlocProvider( create: (final _) => JobsCubit( @@ -95,7 +118,7 @@ class BlocAndProviderConfig extends StatelessWidget { ), ), ], - child: child, + child: widget.child, ); } } diff --git a/lib/logic/cubit/devices/devices_cubit.dart b/lib/logic/cubit/devices/devices_cubit.dart index 1e3852cd..699764dd 100644 --- a/lib/logic/cubit/devices/devices_cubit.dart +++ b/lib/logic/cubit/devices/devices_cubit.dart @@ -7,8 +7,7 @@ import 'package:selfprivacy/logic/models/json/api_token.dart'; part 'devices_state.dart'; class ApiDevicesCubit extends ServerConnectionDependentCubit { - ApiDevicesCubit(final ServerInstallationCubit serverInstallationCubit) - : super(const ApiDevicesState.initial()); + ApiDevicesCubit() : super(const ApiDevicesState.initial()); final ServerApi api = ServerApi(); diff --git a/lib/logic/cubit/dns_records/dns_records_cubit.dart b/lib/logic/cubit/dns_records/dns_records_cubit.dart index fcf5d152..cdb4e9b1 100644 --- a/lib/logic/cubit/dns_records/dns_records_cubit.dart +++ b/lib/logic/cubit/dns_records/dns_records_cubit.dart @@ -11,7 +11,7 @@ import 'package:selfprivacy/utils/network_utils.dart'; part 'dns_records_state.dart'; class DnsRecordsCubit extends ServerConnectionDependentCubit { - DnsRecordsCubit(final ServerInstallationCubit serverInstallationCubit) + DnsRecordsCubit() : super( const DnsRecordsState(dnsState: DnsRecordsStatus.refreshing), ); diff --git a/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart b/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart index a1e99060..951b57dd 100644 --- a/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart +++ b/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart @@ -15,8 +15,7 @@ part 'provider_volume_state.dart'; class ApiProviderVolumeCubit extends ServerConnectionDependentCubit { - ApiProviderVolumeCubit(final ServerInstallationCubit serverInstallationCubit) - : super(const ApiProviderVolumeState.initial()); + ApiProviderVolumeCubit() : super(const ApiProviderVolumeState.initial()); final ServerApi serverApi = ServerApi(); @override diff --git a/lib/logic/cubit/recovery_key/recovery_key_cubit.dart b/lib/logic/cubit/recovery_key/recovery_key_cubit.dart index f49f1b24..42811b85 100644 --- a/lib/logic/cubit/recovery_key/recovery_key_cubit.dart +++ b/lib/logic/cubit/recovery_key/recovery_key_cubit.dart @@ -9,8 +9,7 @@ part 'recovery_key_state.dart'; class RecoveryKeyCubit extends ServerConnectionDependentCubit { - RecoveryKeyCubit(final ServerInstallationCubit serverInstallationCubit) - : super(const RecoveryKeyState.initial()); + RecoveryKeyCubit() : super(const RecoveryKeyState.initial()); final ServerApi api = ServerApi(); diff --git a/lib/logic/cubit/server_detailed_info/server_detailed_info_cubit.dart b/lib/logic/cubit/server_detailed_info/server_detailed_info_cubit.dart index 9e760ebb..0ffe7766 100644 --- a/lib/logic/cubit/server_detailed_info/server_detailed_info_cubit.dart +++ b/lib/logic/cubit/server_detailed_info/server_detailed_info_cubit.dart @@ -9,8 +9,7 @@ part 'server_detailed_info_state.dart'; class ServerDetailsCubit extends ServerConnectionDependentCubit { - ServerDetailsCubit(final ServerInstallationCubit serverInstallationCubit) - : super(ServerDetailsInitial()); + ServerDetailsCubit() : super(ServerDetailsInitial()); ServerDetailsRepository repository = ServerDetailsRepository(); diff --git a/lib/logic/cubit/server_volumes/server_volume_cubit.dart b/lib/logic/cubit/server_volumes/server_volume_cubit.dart index 2941ed3f..f3659d77 100644 --- a/lib/logic/cubit/server_volumes/server_volume_cubit.dart +++ b/lib/logic/cubit/server_volumes/server_volume_cubit.dart @@ -12,7 +12,6 @@ part 'server_volume_state.dart'; class ApiServerVolumeCubit extends ServerConnectionDependentCubit { ApiServerVolumeCubit( - final ServerInstallationCubit serverInstallationCubit, this.providerVolumeCubit, ) : super(ApiServerVolumeState.initial()) { // TODO: Remove this connection between cubits diff --git a/lib/logic/cubit/users/users_cubit.dart b/lib/logic/cubit/users/users_cubit.dart index 858a7b4f..1c242adb 100644 --- a/lib/logic/cubit/users/users_cubit.dart +++ b/lib/logic/cubit/users/users_cubit.dart @@ -13,7 +13,7 @@ export 'package:provider/provider.dart'; part 'users_state.dart'; class UsersCubit extends ServerConnectionDependentCubit { - UsersCubit(final ServerInstallationCubit serverInstallationCubit) + UsersCubit() : super( const UsersState( [], From 3b9d61604571f57030e7ffbc81a69daaa084b918 Mon Sep 17 00:00:00 2001 From: Inex Code Date: Thu, 1 Feb 2024 18:30:06 +0400 Subject: [PATCH 18/34] refactor: Introduce VolumesBloc, remove ServerVolumeCubit --- lib/config/bloc_config.dart | 14 +- .../graphql_maps/server_api/volume_api.dart | 1 + lib/logic/bloc/volumes/volumes_bloc.dart | 163 ++++++++++++++++++ lib/logic/bloc/volumes/volumes_event.dart | 30 ++++ lib/logic/bloc/volumes/volumes_state.dart | 97 +++++++++++ .../provider_volume_cubit.dart | 80 ++++----- .../provider_volume_state.dart | 10 +- .../server_volumes/server_volume_cubit.dart | 73 -------- .../server_volumes/server_volume_state.dart | 38 ---- .../get_it/api_connection_repository.dart | 16 +- lib/logic/models/disk_status.dart | 4 - lib/logic/models/hive/server_details.dart | 1 + lib/logic/models/json/server_disk_volume.dart | 17 +- lib/ui/pages/providers/providers.dart | 4 +- .../server_details/server_details_screen.dart | 4 +- .../server_storage/extending_volume.dart | 10 +- lib/ui/pages/services/service_page.dart | 7 +- 17 files changed, 382 insertions(+), 187 deletions(-) create mode 100644 lib/logic/bloc/volumes/volumes_bloc.dart create mode 100644 lib/logic/bloc/volumes/volumes_event.dart create mode 100644 lib/logic/bloc/volumes/volumes_state.dart delete mode 100644 lib/logic/cubit/server_volumes/server_volume_cubit.dart delete mode 100644 lib/logic/cubit/server_volumes/server_volume_state.dart diff --git a/lib/config/bloc_config.dart b/lib/config/bloc_config.dart index 09d0f361..268048c5 100644 --- a/lib/config/bloc_config.dart +++ b/lib/config/bloc_config.dart @@ -3,6 +3,7 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart'; import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart'; import 'package:selfprivacy/logic/bloc/services/services_bloc.dart'; +import 'package:selfprivacy/logic/bloc/volumes/volumes_bloc.dart'; import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart'; import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart'; import 'package:selfprivacy/logic/cubit/connection_status/connection_status_bloc.dart'; @@ -12,7 +13,6 @@ import 'package:selfprivacy/logic/cubit/provider_volumes/provider_volume_cubit.d import 'package:selfprivacy/logic/cubit/recovery_key/recovery_key_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_detailed_info/server_detailed_info_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; -import 'package:selfprivacy/logic/cubit/server_volumes/server_volume_cubit.dart'; import 'package:selfprivacy/logic/cubit/support_system/support_system_cubit.dart'; import 'package:selfprivacy/logic/cubit/users/users_cubit.dart'; @@ -34,11 +34,11 @@ class BlocAndProviderConfigState extends State { late final DnsRecordsCubit dnsRecordsCubit; late final RecoveryKeyCubit recoveryKeyCubit; late final ApiDevicesCubit apiDevicesCubit; - late final ApiProviderVolumeCubit apiVolumesCubit; - late final ApiServerVolumeCubit apiServerVolumesCubit; + late final ProviderVolumeCubit apiVolumesCubit; late final ServerJobsBloc serverJobsBloc; late final ConnectionStatusBloc connectionStatusBloc; late final ServerDetailsCubit serverDetailsCubit; + late final VolumesBloc volumesBloc; @override void initState() { @@ -51,11 +51,11 @@ class BlocAndProviderConfigState extends State { dnsRecordsCubit = DnsRecordsCubit(); recoveryKeyCubit = RecoveryKeyCubit(); apiDevicesCubit = ApiDevicesCubit(); - apiVolumesCubit = ApiProviderVolumeCubit(); - apiServerVolumesCubit = ApiServerVolumeCubit(apiVolumesCubit); + apiVolumesCubit = ProviderVolumeCubit(); serverJobsBloc = ServerJobsBloc(); connectionStatusBloc = ConnectionStatusBloc(); serverDetailsCubit = ServerDetailsCubit(); + volumesBloc = VolumesBloc(); } @override @@ -101,9 +101,6 @@ class BlocAndProviderConfigState extends State { BlocProvider( create: (final _) => apiVolumesCubit, ), - BlocProvider( - create: (final _) => apiServerVolumesCubit, - ), BlocProvider( create: (final _) => serverJobsBloc, ), @@ -111,6 +108,7 @@ class BlocAndProviderConfigState extends State { BlocProvider( create: (final _) => serverDetailsCubit, ), + BlocProvider(create: (final _) => volumesBloc), BlocProvider( create: (final _) => JobsCubit( usersCubit: usersCubit, diff --git a/lib/logic/api_maps/graphql_maps/server_api/volume_api.dart b/lib/logic/api_maps/graphql_maps/server_api/volume_api.dart index 050b1de0..713544eb 100644 --- a/lib/logic/api_maps/graphql_maps/server_api/volume_api.dart +++ b/lib/logic/api_maps/graphql_maps/server_api/volume_api.dart @@ -11,6 +11,7 @@ mixin VolumeApi on GraphQLApiMap { if (response.hasException) { print(response.exception.toString()); } + // TODO: Rewrite to use fromGraphQL volumes = response.data!['storage']['volumes'] .map((final e) => ServerDiskVolume.fromJson(e)) .toList(); diff --git a/lib/logic/bloc/volumes/volumes_bloc.dart b/lib/logic/bloc/volumes/volumes_bloc.dart new file mode 100644 index 00000000..ed365b3a --- /dev/null +++ b/lib/logic/bloc/volumes/volumes_bloc.dart @@ -0,0 +1,163 @@ +import 'dart:async'; + +import 'package:bloc_concurrency/bloc_concurrency.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:selfprivacy/config/get_it_config.dart'; +import 'package:selfprivacy/logic/models/disk_status.dart'; +import 'package:selfprivacy/logic/models/hive/server_details.dart'; +import 'package:selfprivacy/logic/models/json/server_disk_volume.dart'; +import 'package:selfprivacy/logic/models/price.dart'; +import 'package:selfprivacy/logic/providers/providers_controller.dart'; + +part 'volumes_event.dart'; +part 'volumes_state.dart'; + +class VolumesBloc extends Bloc { + VolumesBloc() : super(VolumesInitial()) { + on( + _loadState, + transformer: droppable(), + ); + on( + _resetState, + transformer: droppable(), + ); + on( + _updateState, + transformer: droppable(), + ); + + final connectionRepository = getIt(); + + _apiStatusSubscription = connectionRepository.connectionStatusStream + .listen((final ConnectionStatus connectionStatus) { + switch (connectionStatus) { + case ConnectionStatus.nonexistent: + add(const VolumesServerReset()); + isLoaded = false; + break; + case ConnectionStatus.connected: + if (!isLoaded) { + add(const VolumesServerLoaded()); + isLoaded = true; + } + break; + default: + break; + } + }); + + _apiDataSubscription = connectionRepository.dataStream.listen( + (final ApiData apiData) { + if (apiData.volumes.data == null) { + add(const VolumesServerReset()); + } else { + add( + VolumesServerStateChanged( + apiData.volumes.data!, + ), + ); + } + }, + ); + } + + late StreamSubscription _apiStatusSubscription; + late StreamSubscription _apiDataSubscription; + bool isLoaded = false; + + Future getPricePerGb() async { + if (ProvidersController.currentServerProvider == null) { + return null; + } + Price? price; + final pricingResult = + await ProvidersController.currentServerProvider!.getAdditionalPricing(); + if (pricingResult.data == null || !pricingResult.success) { + getIt().showSnackBar('server.pricing_error'.tr()); + return price; + } + price = pricingResult.data!.perVolumeGb; + return price; + } + + Future _loadState( + final VolumesServerLoaded event, + final Emitter emit, + ) async { + if (ProvidersController.currentServerProvider == null) { + return; + } + + emit(VolumesLoading()); + + final volumesResult = + await ProvidersController.currentServerProvider!.getVolumes(); + + if (!volumesResult.success || volumesResult.data.isEmpty) { + emit(VolumesInitial()); + return; + } + + final serverVolumes = getIt().apiData.volumes.data; + + if (serverVolumes == null) { + emit(VolumesLoading(providerVolumes: volumesResult.data)); + return; + } else { + emit( + VolumesLoaded( + diskStatus: DiskStatus.fromVolumes( + serverVolumes, + volumesResult.data, + ), + providerVolumes: volumesResult.data, + serverVolumesHashCode: Object.hashAll(serverVolumes), + ), + ); + } + } + + Future _resetState( + final VolumesServerReset event, + final Emitter emit, + ) async { + emit(VolumesInitial()); + } + + @override + void onChange(final Change change) { + super.onChange(change); + } + + @override + Future close() async { + await _apiStatusSubscription.cancel(); + await _apiDataSubscription.cancel(); + await super.close(); + } + + Future invalidateCache() async { + getIt().apiData.volumes.invalidate(); + } + + Future _updateState( + final VolumesServerStateChanged event, + final Emitter emit, + ) async { + final serverVolumes = event.volumes; + final providerVolumes = state.providerVolumes; + emit( + VolumesLoaded( + diskStatus: DiskStatus.fromVolumes( + serverVolumes, + providerVolumes, + ), + providerVolumes: providerVolumes, + serverVolumesHashCode: Object.hashAll(serverVolumes), + ), + ); + } +} diff --git a/lib/logic/bloc/volumes/volumes_event.dart b/lib/logic/bloc/volumes/volumes_event.dart new file mode 100644 index 00000000..5e65b07d --- /dev/null +++ b/lib/logic/bloc/volumes/volumes_event.dart @@ -0,0 +1,30 @@ +part of 'volumes_bloc.dart'; + +abstract class VolumesEvent extends Equatable { + const VolumesEvent(); +} + +class VolumesServerLoaded extends VolumesEvent { + const VolumesServerLoaded(); + + @override + List get props => []; +} + +class VolumesServerReset extends VolumesEvent { + const VolumesServerReset(); + + @override + List get props => []; +} + +class VolumesServerStateChanged extends VolumesEvent { + const VolumesServerStateChanged( + this.volumes, + ); + + final List volumes; + + @override + List get props => [volumes]; +} diff --git a/lib/logic/bloc/volumes/volumes_state.dart b/lib/logic/bloc/volumes/volumes_state.dart new file mode 100644 index 00000000..a9ba2d06 --- /dev/null +++ b/lib/logic/bloc/volumes/volumes_state.dart @@ -0,0 +1,97 @@ +part of 'volumes_bloc.dart'; + +abstract class VolumesState extends Equatable { + const VolumesState({ + required this.diskStatus, + required final serverVolumesHashCode, + this.providerVolumes = const [], + }) : _serverVolumesHashCode = serverVolumesHashCode; + + final DiskStatus diskStatus; + final List providerVolumes; + List get volumes => diskStatus.diskVolumes; + final int? _serverVolumesHashCode; + + DiskVolume getVolume(final String volumeName) => volumes.firstWhere( + (final volume) => volume.name == volumeName, + orElse: () => DiskVolume(), + ); + + bool get isProviderVolumesLoaded => providerVolumes.isNotEmpty; + + VolumesState copyWith({ + required final int? serverVolumesHashCode, + final DiskStatus? diskStatus, + final List? providerVolumes, + }); +} + +class VolumesInitial extends VolumesState { + VolumesInitial() + : super( + diskStatus: DiskStatus(), + serverVolumesHashCode: null, + ); + + @override + List get props => [providerVolumes, _serverVolumesHashCode]; + + @override + VolumesInitial copyWith({ + required final int? serverVolumesHashCode, + final DiskStatus? diskStatus, + final List? providerVolumes, + }) => + VolumesInitial(); +} + +class VolumesLoading extends VolumesState { + VolumesLoading({ + super.serverVolumesHashCode, + final DiskStatus? diskStatus, + final List? providerVolumes, + }) : super( + diskStatus: diskStatus ?? DiskStatus(), + providerVolumes: providerVolumes ?? const [], + ); + + @override + List get props => [providerVolumes, _serverVolumesHashCode]; + + @override + VolumesLoading copyWith({ + required final int? serverVolumesHashCode, + final DiskStatus? diskStatus, + final List? providerVolumes, + }) => + VolumesLoading( + diskStatus: diskStatus ?? this.diskStatus, + providerVolumes: providerVolumes ?? this.providerVolumes, + serverVolumesHashCode: serverVolumesHashCode ?? _serverVolumesHashCode!, + ); +} + +class VolumesLoaded extends VolumesState { + const VolumesLoaded({ + required super.serverVolumesHashCode, + required super.diskStatus, + final List? providerVolumes, + }) : super( + providerVolumes: providerVolumes ?? const [], + ); + + @override + List get props => [providerVolumes, _serverVolumesHashCode]; + + @override + VolumesLoaded copyWith({ + final DiskStatus? diskStatus, + final List? providerVolumes, + final int? serverVolumesHashCode, + }) => + VolumesLoaded( + diskStatus: diskStatus ?? this.diskStatus, + providerVolumes: providerVolumes ?? this.providerVolumes, + serverVolumesHashCode: serverVolumesHashCode ?? _serverVolumesHashCode!, + ); +} diff --git a/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart b/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart index 951b57dd..3432fe7e 100644 --- a/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart +++ b/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart @@ -13,9 +13,9 @@ import 'package:selfprivacy/logic/providers/providers_controller.dart'; part 'provider_volume_state.dart'; -class ApiProviderVolumeCubit - extends ServerConnectionDependentCubit { - ApiProviderVolumeCubit() : super(const ApiProviderVolumeState.initial()); +class ProviderVolumeCubit + extends ServerConnectionDependentCubit { + ProviderVolumeCubit() : super(const ProviderVolumeState.initial()); final ServerApi serverApi = ServerApi(); @override @@ -36,24 +36,24 @@ class ApiProviderVolumeCubit } Future refresh() async { - emit(const ApiProviderVolumeState([], LoadingStatus.refreshing, false)); + emit(const ProviderVolumeState([], LoadingStatus.refreshing, false)); unawaited(_refetch()); } Future _refetch() async { if (ProvidersController.currentServerProvider == null) { - return emit(const ApiProviderVolumeState([], LoadingStatus.error, false)); + return emit(const ProviderVolumeState([], LoadingStatus.error, false)); } final volumesResult = await ProvidersController.currentServerProvider!.getVolumes(); if (!volumesResult.success || volumesResult.data.isEmpty) { - return emit(const ApiProviderVolumeState([], LoadingStatus.error, false)); + return emit(const ProviderVolumeState([], LoadingStatus.error, false)); } emit( - ApiProviderVolumeState( + ProviderVolumeState( volumesResult.data, LoadingStatus.success, false, @@ -61,18 +61,18 @@ class ApiProviderVolumeCubit ); } - Future attachVolume(final DiskVolume volume) async { - final ServerHostingDetails server = getIt().serverDetails!; - await ProvidersController.currentServerProvider! - .attachVolume(volume.providerVolume!, server.id); - unawaited(refresh()); - } - - Future detachVolume(final DiskVolume volume) async { - await ProvidersController.currentServerProvider! - .detachVolume(volume.providerVolume!); - unawaited(refresh()); - } + // Future attachVolume(final DiskVolume volume) async { + // final ServerHostingDetails server = getIt().serverDetails!; + // await ProvidersController.currentServerProvider! + // .attachVolume(volume.providerVolume!, server.id); + // unawaited(refresh()); + // } + // + // Future detachVolume(final DiskVolume volume) async { + // await ProvidersController.currentServerProvider! + // .detachVolume(volume.providerVolume!); + // unawaited(refresh()); + // } Future resizeVolume( final DiskVolume volume, @@ -119,29 +119,29 @@ class ApiProviderVolumeCubit return true; } - Future createVolume(final DiskSize size) async { - final ServerProviderVolume? volume = (await ProvidersController - .currentServerProvider! - .createVolume(size.gibibyte.toInt())) - .data; - - final diskVolume = DiskVolume(providerVolume: volume); - await attachVolume(diskVolume); - - await Future.delayed(const Duration(seconds: 10)); - - await ServerApi().mountVolume(volume!.name); - unawaited(refresh()); - } - - Future deleteVolume(final DiskVolume volume) async { - await ProvidersController.currentServerProvider! - .deleteVolume(volume.providerVolume!); - unawaited(refresh()); - } + // Future createVolume(final DiskSize size) async { + // final ServerProviderVolume? volume = (await ProvidersController + // .currentServerProvider! + // .createVolume(size.gibibyte.toInt())) + // .data; + // + // final diskVolume = DiskVolume(providerVolume: volume); + // await attachVolume(diskVolume); + // + // await Future.delayed(const Duration(seconds: 10)); + // + // await ServerApi().mountVolume(volume!.name); + // unawaited(refresh()); + // } + // + // Future deleteVolume(final DiskVolume volume) async { + // await ProvidersController.currentServerProvider! + // .deleteVolume(volume.providerVolume!); + // unawaited(refresh()); + // } @override void clear() { - emit(const ApiProviderVolumeState.initial()); + emit(const ProviderVolumeState.initial()); } } diff --git a/lib/logic/cubit/provider_volumes/provider_volume_state.dart b/lib/logic/cubit/provider_volumes/provider_volume_state.dart index 21faa52c..89603a5a 100644 --- a/lib/logic/cubit/provider_volumes/provider_volume_state.dart +++ b/lib/logic/cubit/provider_volumes/provider_volume_state.dart @@ -1,9 +1,9 @@ part of 'provider_volume_cubit.dart'; -class ApiProviderVolumeState extends ServerInstallationDependendState { - const ApiProviderVolumeState(this._volumes, this.status, this.isResizing); +class ProviderVolumeState extends ServerInstallationDependendState { + const ProviderVolumeState(this._volumes, this.status, this.isResizing); - const ApiProviderVolumeState.initial() + const ProviderVolumeState.initial() : this(const [], LoadingStatus.uninitialized, false); final List _volumes; final LoadingStatus status; @@ -11,12 +11,12 @@ class ApiProviderVolumeState extends ServerInstallationDependendState { List get volumes => _volumes; - ApiProviderVolumeState copyWith({ + ProviderVolumeState copyWith({ final List? volumes, final LoadingStatus? status, final bool? isResizing, }) => - ApiProviderVolumeState( + ProviderVolumeState( volumes ?? _volumes, status ?? this.status, isResizing ?? this.isResizing, diff --git a/lib/logic/cubit/server_volumes/server_volume_cubit.dart b/lib/logic/cubit/server_volumes/server_volume_cubit.dart deleted file mode 100644 index f3659d77..00000000 --- a/lib/logic/cubit/server_volumes/server_volume_cubit.dart +++ /dev/null @@ -1,73 +0,0 @@ -import 'dart:async'; - -import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart'; -import 'package:selfprivacy/logic/common_enum/common_enum.dart'; -import 'package:selfprivacy/logic/cubit/provider_volumes/provider_volume_cubit.dart'; -import 'package:selfprivacy/logic/cubit/server_connection_dependent/server_connection_dependent_cubit.dart'; -import 'package:selfprivacy/logic/models/disk_status.dart'; -import 'package:selfprivacy/logic/models/json/server_disk_volume.dart'; - -part 'server_volume_state.dart'; - -class ApiServerVolumeCubit - extends ServerConnectionDependentCubit { - ApiServerVolumeCubit( - this.providerVolumeCubit, - ) : super(ApiServerVolumeState.initial()) { - // TODO: Remove this connection between cubits - _providerVolumeSubscription = - providerVolumeCubit.stream.listen(checkProviderVolumes); - } - - final ServerApi serverApi = ServerApi(); - - @override - Future load() async { - unawaited(reload()); - } - - late StreamSubscription _providerVolumeSubscription; - final ApiProviderVolumeCubit providerVolumeCubit; - - void checkProviderVolumes(final ApiProviderVolumeState state) { - emit( - ApiServerVolumeState( - this.state._volumes, - this.state.status, - DiskStatus.fromVolumes(this.state._volumes, state.volumes), - ), - ); - return; - } - - Future reload() async { - final volumes = await serverApi.getServerDiskVolumes(); - var status = LoadingStatus.error; - - if (volumes.isNotEmpty) { - status = LoadingStatus.success; - } - - emit( - ApiServerVolumeState( - volumes, - status, - DiskStatus.fromVolumes( - volumes, - providerVolumeCubit.state.volumes, - ), - ), - ); - } - - @override - void clear() { - emit(ApiServerVolumeState.initial()); - } - - @override - Future close() { - _providerVolumeSubscription.cancel(); - return super.close(); - } -} diff --git a/lib/logic/cubit/server_volumes/server_volume_state.dart b/lib/logic/cubit/server_volumes/server_volume_state.dart deleted file mode 100644 index 3ea7aaa5..00000000 --- a/lib/logic/cubit/server_volumes/server_volume_state.dart +++ /dev/null @@ -1,38 +0,0 @@ -part of 'server_volume_cubit.dart'; - -class ApiServerVolumeState extends ServerInstallationDependendState { - const ApiServerVolumeState( - this._volumes, - this.status, - this._diskStatus, - ); - - ApiServerVolumeState.initial() - : this(const [], LoadingStatus.uninitialized, DiskStatus()); - - final List _volumes; - final DiskStatus _diskStatus; - final LoadingStatus status; - - List get volumes => _diskStatus.diskVolumes; - DiskStatus get diskStatus => _diskStatus; - - DiskVolume getVolume(final String volumeName) => volumes.firstWhere( - (final volume) => volume.name == volumeName, - orElse: () => DiskVolume(), - ); - - ApiServerVolumeState copyWith({ - final List? volumes, - final LoadingStatus? status, - final DiskStatus? diskStatus, - }) => - ApiServerVolumeState( - volumes ?? _volumes, - status ?? this.status, - diskStatus ?? _diskStatus, - ); - - @override - List get props => [_volumes, status]; -} diff --git a/lib/logic/get_it/api_connection_repository.dart b/lib/logic/get_it/api_connection_repository.dart index 8a64b09d..6f0210ee 100644 --- a/lib/logic/get_it/api_connection_repository.dart +++ b/lib/logic/get_it/api_connection_repository.dart @@ -8,6 +8,7 @@ import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.da import 'package:selfprivacy/logic/models/backup.dart'; import 'package:selfprivacy/logic/models/hive/server_details.dart'; import 'package:selfprivacy/logic/models/hive/server_domain.dart'; +import 'package:selfprivacy/logic/models/json/server_disk_volume.dart'; import 'package:selfprivacy/logic/models/json/server_job.dart'; import 'package:selfprivacy/logic/models/service.dart'; @@ -95,10 +96,11 @@ class ApiConnectionRepository { _dataStream.add(_apiData); } - _apiData.serverJobs.data = await api.getServerJobs(); - _apiData.backupConfig.data = await api.getBackupsConfiguration(); - _apiData.backups.data = await api.getBackups(); - _apiData.services.data = await api.getAllServices(); + _apiData.serverJobs.data = await _apiData.serverJobs.fetchData(); + _apiData.backupConfig.data = await _apiData.backupConfig.fetchData(); + _apiData.backups.data = await _apiData.backups.fetchData(); + _apiData.services.data = await _apiData.services.fetchData(); + _apiData.volumes.data = await _apiData.volumes.fetchData(); _dataStream.add(_apiData); connectionStatus = ConnectionStatus.connected; @@ -136,6 +138,8 @@ class ApiConnectionRepository { .refetchData(version, () => _dataStream.add(_apiData)); await _apiData.services .refetchData(version, () => _dataStream.add(_apiData)); + await _apiData.volumes + .refetchData(version, () => _dataStream.add(_apiData)); } void emitData() { @@ -163,6 +167,9 @@ class ApiData { services = ApiDataElement>( fetchData: () async => api.getAllServices(), requiredApiVersion: '>=2.4.3', + ), + volumes = ApiDataElement>( + fetchData: () async => api.getServerDiskVolumes(), ); ApiDataElement> serverJobs; @@ -170,6 +177,7 @@ class ApiData { ApiDataElement backupConfig; ApiDataElement> backups; ApiDataElement> services; + ApiDataElement> volumes; } enum ConnectionStatus { diff --git a/lib/logic/models/disk_status.dart b/lib/logic/models/disk_status.dart index 1010f747..6708cf12 100644 --- a/lib/logic/models/disk_status.dart +++ b/lib/logic/models/disk_status.dart @@ -9,7 +9,6 @@ class DiskVolume { this.sizeUsed = const DiskSize(byte: 0), this.root = false, this.isResizable = false, - this.serverDiskVolume, this.providerVolume, }); @@ -27,7 +26,6 @@ class DiskVolume { ), root: volume.root, isResizable: providerVolume != null, - serverDiskVolume: volume, providerVolume: providerVolume, ); @@ -51,7 +49,6 @@ class DiskVolume { String name; bool root; bool isResizable; - ServerDiskVolume? serverDiskVolume; ServerProviderVolume? providerVolume; /// from 0.0 to 1.0 @@ -75,7 +72,6 @@ class DiskVolume { name: name ?? this.name, root: root ?? this.root, isResizable: isResizable ?? this.isResizable, - serverDiskVolume: serverDiskVolume ?? this.serverDiskVolume, providerVolume: providerVolume ?? this.providerVolume, ); } diff --git a/lib/logic/models/hive/server_details.dart b/lib/logic/models/hive/server_details.dart index e332be66..c19be012 100644 --- a/lib/logic/models/hive/server_details.dart +++ b/lib/logic/models/hive/server_details.dart @@ -27,6 +27,7 @@ class ServerHostingDetails { @HiveField(2) final DateTime? startTime; + // TODO: Check if it is still needed @HiveField(4) final ServerProviderVolume volume; diff --git a/lib/logic/models/json/server_disk_volume.dart b/lib/logic/models/json/server_disk_volume.dart index 873b5d97..d83c4000 100644 --- a/lib/logic/models/json/server_disk_volume.dart +++ b/lib/logic/models/json/server_disk_volume.dart @@ -1,12 +1,13 @@ +import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; part 'server_disk_volume.g.dart'; @JsonSerializable() -class ServerDiskVolume { +class ServerDiskVolume extends Equatable { factory ServerDiskVolume.fromJson(final Map json) => _$ServerDiskVolumeFromJson(json); - ServerDiskVolume({ + const ServerDiskVolume({ required this.freeSpace, required this.model, required this.name, @@ -25,4 +26,16 @@ class ServerDiskVolume { final String totalSpace; final String type; final String usedSpace; + + @override + List get props => [ + freeSpace, + model, + name, + root, + serial, + totalSpace, + type, + usedSpace, + ]; } diff --git a/lib/ui/pages/providers/providers.dart b/lib/ui/pages/providers/providers.dart index e65098d0..b03c5b55 100644 --- a/lib/ui/pages/providers/providers.dart +++ b/lib/ui/pages/providers/providers.dart @@ -3,9 +3,9 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:selfprivacy/config/brand_theme.dart'; import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart'; +import 'package:selfprivacy/logic/bloc/volumes/volumes_bloc.dart'; import 'package:selfprivacy/logic/cubit/dns_records/dns_records_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; -import 'package:selfprivacy/logic/cubit/server_volumes/server_volume_cubit.dart'; import 'package:selfprivacy/logic/models/state_types.dart'; import 'package:selfprivacy/ui/components/brand_header/brand_header.dart'; import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart'; @@ -34,7 +34,7 @@ class _ProvidersPageState extends State { final DnsRecordsStatus dnsStatus = context.watch().state.dnsState; - final diskStatus = context.watch().state.diskStatus; + final diskStatus = context.watch().state.diskStatus; final ServerInstallationState appConfig = context.watch().state; diff --git a/lib/ui/pages/server_details/server_details_screen.dart b/lib/ui/pages/server_details/server_details_screen.dart index d1d5b8a4..1b66a21b 100644 --- a/lib/ui/pages/server_details/server_details_screen.dart +++ b/lib/ui/pages/server_details/server_details_screen.dart @@ -2,12 +2,12 @@ import 'package:auto_route/auto_route.dart'; import 'package:cubit_form/cubit_form.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:selfprivacy/logic/bloc/volumes/volumes_bloc.dart'; import 'package:selfprivacy/logic/common_enum/common_enum.dart'; import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart'; import 'package:selfprivacy/logic/cubit/metrics/metrics_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_detailed_info/server_detailed_info_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; -import 'package:selfprivacy/logic/cubit/server_volumes/server_volume_cubit.dart'; import 'package:selfprivacy/logic/models/auto_upgrade_settings.dart'; import 'package:selfprivacy/logic/models/job.dart'; import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart'; @@ -81,7 +81,7 @@ class _ServerDetailsScreenState extends State heroSubtitle: 'server.description'.tr(), children: [ StorageCard( - diskStatus: context.watch().state.diskStatus, + diskStatus: context.watch().state.diskStatus, ), const SizedBox(height: 16), const _ServerSettings(), diff --git a/lib/ui/pages/server_storage/extending_volume.dart b/lib/ui/pages/server_storage/extending_volume.dart index 9c145061..60ac7379 100644 --- a/lib/ui/pages/server_storage/extending_volume.dart +++ b/lib/ui/pages/server_storage/extending_volume.dart @@ -2,8 +2,8 @@ import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:selfprivacy/logic/bloc/volumes/volumes_bloc.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/disk_status.dart'; import 'package:selfprivacy/logic/models/price.dart'; @@ -59,7 +59,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, @@ -92,7 +92,7 @@ class _ExtendingVolumePageState extends State { } final isAlreadyResizing = - context.watch().state.isResizing; + context.watch().state.isResizing; return BrandHeroScreen( hasBackButton: true, @@ -163,12 +163,12 @@ class _ExtendingVolumePageState extends State { ), actionButtonTitle: 'basis.continue'.tr(), actionButtonOnPressed: () { - context.read().resizeVolume( + context.read().resizeVolume( widget.diskVolumeToResize, DiskSize.fromGibibyte( _currentSliderGbValue.truncate().toDouble(), ), - context.read().reload, + context.read().invalidateCache, ); context.router.popUntilRoot(); }, diff --git a/lib/ui/pages/services/service_page.dart b/lib/ui/pages/services/service_page.dart index a7c2be34..bb94356f 100644 --- a/lib/ui/pages/services/service_page.dart +++ b/lib/ui/pages/services/service_page.dart @@ -3,8 +3,8 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:selfprivacy/logic/bloc/services/services_bloc.dart'; +import 'package:selfprivacy/logic/bloc/volumes/volumes_bloc.dart'; import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart'; -import 'package:selfprivacy/logic/cubit/server_volumes/server_volume_cubit.dart'; import 'package:selfprivacy/logic/models/job.dart'; import 'package:selfprivacy/logic/models/service.dart'; import 'package:selfprivacy/ui/components/cards/filled_card.dart'; @@ -113,8 +113,7 @@ class _ServicePageState extends State { onTap: () => context.pushRoute( ServicesMigrationRoute( services: [service], - diskStatus: - context.read().state.diskStatus, + diskStatus: context.read().state.diskStatus, ), ), leading: const Icon(Icons.drive_file_move_outlined), @@ -127,7 +126,7 @@ class _ServicePageState extends State { namedArgs: { 'usage': service.storageUsage.used.toString(), 'volume': context - .read() + .read() .state .getVolume(service.storageUsage.volume ?? '') .displayName, From 6914b01d2a8aea1260ad7aeb2a8b04e539407bf2 Mon Sep 17 00:00:00 2001 From: Inex Code Date: Tue, 6 Feb 2024 18:21:21 +0300 Subject: [PATCH 19/34] refactor: remove ProviderVolumes cubit --- assets/translations/en.json | 6 +- lib/config/bloc_config.dart | 6 - lib/logic/bloc/volumes/volumes_bloc.dart | 85 +++++++++- lib/logic/bloc/volumes/volumes_event.dart | 13 ++ lib/logic/bloc/volumes/volumes_state.dart | 25 +++ .../provider_volume_cubit.dart | 147 ------------------ .../provider_volume_state.dart | 27 ---- .../server_storage/extending_volume.dart | 18 ++- 8 files changed, 137 insertions(+), 190 deletions(-) delete mode 100644 lib/logic/cubit/provider_volumes/provider_volume_cubit.dart delete mode 100644 lib/logic/cubit/provider_volumes/provider_volume_state.dart diff --git a/assets/translations/en.json b/assets/translations/en.json index 172fce4e..1410a950 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -305,6 +305,10 @@ "extending_volume_description": "Resizing volume will allow you to store more data on your server without extending the server itself. Volume can only be extended: shrinking is not possible.", "extending_volume_price_info": "Price includes VAT and is estimated from pricing data provided by your server provider. Server will be rebooted after resizing.", "extending_volume_error": "Couldn't initialize volume extending.", + "extending_volume_started": "Volume extending started", + "extending_volume_provider_waiting": "Provider volume resized, waiting 10 seconds…", + "extending_volume_server_waiting": "Server volume resized, waiting 20 seconds…", + "extending_volume_rebooting": "Rebooting server…", "extending_volume_modal_description": "Upgrade to {} for {} plan per month.", "size": "Size", "price": "Price", @@ -634,4 +638,4 @@ "reset_onboarding_description": "Reset onboarding switch to show onboarding screen again", "cubit_statuses": "Cubit loading statuses" } -} \ No newline at end of file +} diff --git a/lib/config/bloc_config.dart b/lib/config/bloc_config.dart index 268048c5..8c635a80 100644 --- a/lib/config/bloc_config.dart +++ b/lib/config/bloc_config.dart @@ -9,7 +9,6 @@ import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart'; import 'package:selfprivacy/logic/cubit/connection_status/connection_status_bloc.dart'; import 'package:selfprivacy/logic/cubit/devices/devices_cubit.dart'; import 'package:selfprivacy/logic/cubit/dns_records/dns_records_cubit.dart'; -import 'package:selfprivacy/logic/cubit/provider_volumes/provider_volume_cubit.dart'; import 'package:selfprivacy/logic/cubit/recovery_key/recovery_key_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_detailed_info/server_detailed_info_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; @@ -34,7 +33,6 @@ class BlocAndProviderConfigState extends State { late final DnsRecordsCubit dnsRecordsCubit; late final RecoveryKeyCubit recoveryKeyCubit; late final ApiDevicesCubit apiDevicesCubit; - late final ProviderVolumeCubit apiVolumesCubit; late final ServerJobsBloc serverJobsBloc; late final ConnectionStatusBloc connectionStatusBloc; late final ServerDetailsCubit serverDetailsCubit; @@ -51,7 +49,6 @@ class BlocAndProviderConfigState extends State { dnsRecordsCubit = DnsRecordsCubit(); recoveryKeyCubit = RecoveryKeyCubit(); apiDevicesCubit = ApiDevicesCubit(); - apiVolumesCubit = ProviderVolumeCubit(); serverJobsBloc = ServerJobsBloc(); connectionStatusBloc = ConnectionStatusBloc(); serverDetailsCubit = ServerDetailsCubit(); @@ -98,9 +95,6 @@ class BlocAndProviderConfigState extends State { BlocProvider( create: (final _) => apiDevicesCubit, ), - BlocProvider( - create: (final _) => apiVolumesCubit, - ), BlocProvider( create: (final _) => serverJobsBloc, ), diff --git a/lib/logic/bloc/volumes/volumes_bloc.dart b/lib/logic/bloc/volumes/volumes_bloc.dart index ed365b3a..55fb15e7 100644 --- a/lib/logic/bloc/volumes/volumes_bloc.dart +++ b/lib/logic/bloc/volumes/volumes_bloc.dart @@ -5,6 +5,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:selfprivacy/config/get_it_config.dart'; +import 'package:selfprivacy/logic/models/disk_size.dart'; import 'package:selfprivacy/logic/models/disk_status.dart'; import 'package:selfprivacy/logic/models/hive/server_details.dart'; import 'package:selfprivacy/logic/models/json/server_disk_volume.dart'; @@ -28,6 +29,10 @@ class VolumesBloc extends Bloc { _updateState, transformer: droppable(), ); + on( + _resizeVolume, + transformer: droppable(), + ); final connectionRepository = getIt(); @@ -149,8 +154,21 @@ class VolumesBloc extends Bloc { ) async { final serverVolumes = event.volumes; final providerVolumes = state.providerVolumes; + if (state is VolumesLoading) { + emit( + VolumesLoaded( + diskStatus: DiskStatus.fromVolumes( + serverVolumes, + providerVolumes, + ), + providerVolumes: providerVolumes, + serverVolumesHashCode: Object.hashAll(serverVolumes), + ), + ); + return; + } emit( - VolumesLoaded( + state.copyWith( diskStatus: DiskStatus.fromVolumes( serverVolumes, providerVolumes, @@ -160,4 +178,69 @@ class VolumesBloc extends Bloc { ), ); } + + Future _resizeVolume( + final VolumeResize event, + final Emitter emit, + ) async { + if (state is! VolumesLoaded) { + return; + } + getIt().showSnackBar( + 'storage.extending_volume_started'.tr(), + ); + emit( + VolumesResizing( + serverVolumesHashCode: state._serverVolumesHashCode, + diskStatus: state.diskStatus, + providerVolumes: state.providerVolumes, + ), + ); + + final resizedResult = + await ProvidersController.currentServerProvider!.resizeVolume( + event.volume.providerVolume!, + event.newSize, + ); + + if (!resizedResult.success || !resizedResult.data) { + getIt().showSnackBar( + 'storage.extending_volume_error'.tr(), + ); + emit( + VolumesLoaded( + serverVolumesHashCode: state._serverVolumesHashCode, + diskStatus: state.diskStatus, + providerVolumes: state.providerVolumes, + ), + ); + return; + } + + getIt().showSnackBar( + 'storage.extending_volume_waiting'.tr(), + ); + + await Future.delayed(const Duration(seconds: 10)); + + await getIt().api.resizeVolume(event.volume.name); + getIt().showSnackBar( + 'storage.extending_volume_server_waiting'.tr(), + ); + + await Future.delayed(const Duration(seconds: 20)); + getIt().showSnackBar( + 'storage.extending_volume_rebooting'.tr(), + ); + + emit( + VolumesLoaded( + serverVolumesHashCode: state._serverVolumesHashCode, + diskStatus: state.diskStatus, + providerVolumes: state.providerVolumes, + ), + ); + + await getIt().api.reboot(); + } } diff --git a/lib/logic/bloc/volumes/volumes_event.dart b/lib/logic/bloc/volumes/volumes_event.dart index 5e65b07d..a4346ce5 100644 --- a/lib/logic/bloc/volumes/volumes_event.dart +++ b/lib/logic/bloc/volumes/volumes_event.dart @@ -28,3 +28,16 @@ class VolumesServerStateChanged extends VolumesEvent { @override List get props => [volumes]; } + +class VolumeResize extends VolumesEvent { + const VolumeResize( + this.volume, + this.newSize, + ); + + final DiskVolume volume; + final DiskSize newSize; + + @override + List get props => [volume, newSize]; +} diff --git a/lib/logic/bloc/volumes/volumes_state.dart b/lib/logic/bloc/volumes/volumes_state.dart index a9ba2d06..6fc4cb45 100644 --- a/lib/logic/bloc/volumes/volumes_state.dart +++ b/lib/logic/bloc/volumes/volumes_state.dart @@ -95,3 +95,28 @@ class VolumesLoaded extends VolumesState { serverVolumesHashCode: serverVolumesHashCode ?? _serverVolumesHashCode!, ); } + +class VolumesResizing extends VolumesState { + const VolumesResizing({ + required super.serverVolumesHashCode, + required super.diskStatus, + final List? providerVolumes, + }) : super( + providerVolumes: providerVolumes ?? const [], + ); + + @override + List get props => [providerVolumes, _serverVolumesHashCode]; + + @override + VolumesResizing copyWith({ + final DiskStatus? diskStatus, + final List? providerVolumes, + final int? serverVolumesHashCode, + }) => + VolumesResizing( + diskStatus: diskStatus ?? this.diskStatus, + providerVolumes: providerVolumes ?? this.providerVolumes, + serverVolumesHashCode: serverVolumesHashCode ?? _serverVolumesHashCode!, + ); +} diff --git a/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart b/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart deleted file mode 100644 index 3432fe7e..00000000 --- a/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart +++ /dev/null @@ -1,147 +0,0 @@ -import 'dart:async'; - -import 'package:easy_localization/easy_localization.dart'; -import 'package:selfprivacy/config/get_it_config.dart'; -import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart'; -import 'package:selfprivacy/logic/common_enum/common_enum.dart'; -import 'package:selfprivacy/logic/cubit/server_connection_dependent/server_connection_dependent_cubit.dart'; -import 'package:selfprivacy/logic/models/disk_size.dart'; -import 'package:selfprivacy/logic/models/disk_status.dart'; -import 'package:selfprivacy/logic/models/hive/server_details.dart'; -import 'package:selfprivacy/logic/models/price.dart'; -import 'package:selfprivacy/logic/providers/providers_controller.dart'; - -part 'provider_volume_state.dart'; - -class ProviderVolumeCubit - extends ServerConnectionDependentCubit { - ProviderVolumeCubit() : super(const ProviderVolumeState.initial()); - final ServerApi serverApi = ServerApi(); - - @override - Future load() async { - unawaited(_refetch()); - } - - Future getPricePerGb() async { - Price? price; - final pricingResult = - await ProvidersController.currentServerProvider!.getAdditionalPricing(); - if (pricingResult.data == null || !pricingResult.success) { - getIt().showSnackBar('server.pricing_error'.tr()); - return price; - } - price = pricingResult.data!.perVolumeGb; - return price; - } - - Future refresh() async { - emit(const ProviderVolumeState([], LoadingStatus.refreshing, false)); - unawaited(_refetch()); - } - - Future _refetch() async { - if (ProvidersController.currentServerProvider == null) { - return emit(const ProviderVolumeState([], LoadingStatus.error, false)); - } - - final volumesResult = - await ProvidersController.currentServerProvider!.getVolumes(); - - if (!volumesResult.success || volumesResult.data.isEmpty) { - return emit(const ProviderVolumeState([], LoadingStatus.error, false)); - } - - emit( - ProviderVolumeState( - volumesResult.data, - LoadingStatus.success, - false, - ), - ); - } - - // Future attachVolume(final DiskVolume volume) async { - // final ServerHostingDetails server = getIt().serverDetails!; - // await ProvidersController.currentServerProvider! - // .attachVolume(volume.providerVolume!, server.id); - // unawaited(refresh()); - // } - // - // Future detachVolume(final DiskVolume volume) async { - // await ProvidersController.currentServerProvider! - // .detachVolume(volume.providerVolume!); - // unawaited(refresh()); - // } - - Future resizeVolume( - final DiskVolume volume, - final DiskSize newSize, - final Function() callback, - ) async { - getIt().showSnackBar( - 'Starting resize', - ); - emit(state.copyWith(isResizing: true)); - final resizedResult = - await ProvidersController.currentServerProvider!.resizeVolume( - volume.providerVolume!, - newSize, - ); - - if (!resizedResult.success || !resizedResult.data) { - getIt().showSnackBar( - 'storage.extending_volume_error'.tr(), - ); - emit(state.copyWith(isResizing: false)); - return false; - } - - getIt().showSnackBar( - 'Provider volume resized, waiting 10 seconds', - ); - await Future.delayed(const Duration(seconds: 10)); - - await ServerApi().resizeVolume(volume.name); - getIt().showSnackBar( - 'Server volume resized, waiting 20 seconds', - ); - - await Future.delayed(const Duration(seconds: 20)); - getIt().showSnackBar( - 'Restarting server', - ); - - await refresh(); - emit(state.copyWith(isResizing: false)); - await callback(); - await serverApi.reboot(); - return true; - } - - // Future createVolume(final DiskSize size) async { - // final ServerProviderVolume? volume = (await ProvidersController - // .currentServerProvider! - // .createVolume(size.gibibyte.toInt())) - // .data; - // - // final diskVolume = DiskVolume(providerVolume: volume); - // await attachVolume(diskVolume); - // - // await Future.delayed(const Duration(seconds: 10)); - // - // await ServerApi().mountVolume(volume!.name); - // unawaited(refresh()); - // } - // - // Future deleteVolume(final DiskVolume volume) async { - // await ProvidersController.currentServerProvider! - // .deleteVolume(volume.providerVolume!); - // unawaited(refresh()); - // } - - @override - void clear() { - emit(const ProviderVolumeState.initial()); - } -} diff --git a/lib/logic/cubit/provider_volumes/provider_volume_state.dart b/lib/logic/cubit/provider_volumes/provider_volume_state.dart deleted file mode 100644 index 89603a5a..00000000 --- a/lib/logic/cubit/provider_volumes/provider_volume_state.dart +++ /dev/null @@ -1,27 +0,0 @@ -part of 'provider_volume_cubit.dart'; - -class ProviderVolumeState extends ServerInstallationDependendState { - const ProviderVolumeState(this._volumes, this.status, this.isResizing); - - const ProviderVolumeState.initial() - : this(const [], LoadingStatus.uninitialized, false); - final List _volumes; - final LoadingStatus status; - final bool isResizing; - - List get volumes => _volumes; - - ProviderVolumeState copyWith({ - final List? volumes, - final LoadingStatus? status, - final bool? isResizing, - }) => - ProviderVolumeState( - volumes ?? _volumes, - status ?? this.status, - isResizing ?? this.isResizing, - ); - - @override - List get props => [_volumes, status, isResizing]; -} diff --git a/lib/ui/pages/server_storage/extending_volume.dart b/lib/ui/pages/server_storage/extending_volume.dart index 60ac7379..e6961a95 100644 --- a/lib/ui/pages/server_storage/extending_volume.dart +++ b/lib/ui/pages/server_storage/extending_volume.dart @@ -3,7 +3,6 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:selfprivacy/logic/bloc/volumes/volumes_bloc.dart'; -import 'package:selfprivacy/logic/cubit/provider_volumes/provider_volume_cubit.dart'; import 'package:selfprivacy/logic/models/disk_size.dart'; import 'package:selfprivacy/logic/models/disk_status.dart'; import 'package:selfprivacy/logic/models/price.dart'; @@ -59,7 +58,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, @@ -92,7 +91,7 @@ class _ExtendingVolumePageState extends State { } final isAlreadyResizing = - context.watch().state.isResizing; + context.watch().state is VolumesResizing; return BrandHeroScreen( hasBackButton: true, @@ -163,12 +162,15 @@ class _ExtendingVolumePageState extends State { ), actionButtonTitle: 'basis.continue'.tr(), actionButtonOnPressed: () { - context.read().resizeVolume( - widget.diskVolumeToResize, - DiskSize.fromGibibyte( - _currentSliderGbValue.truncate().toDouble(), + context.read().add( + VolumeResize( + widget.diskVolumeToResize, + DiskSize.fromGibibyte( + _currentSliderGbValue + .truncate() + .toDouble(), + ), ), - context.read().invalidateCache, ); context.router.popUntilRoot(); }, From 1daf957245d4edc078d4ac307625a4861904b842 Mon Sep 17 00:00:00 2001 From: Inex Code Date: Thu, 8 Feb 2024 16:58:45 +0300 Subject: [PATCH 20/34] chore: Move ConnectionStatus bloc to bloc folder --- lib/config/bloc_config.dart | 2 +- .../connection_status/connection_status_bloc.dart | 0 .../connection_status/connection_status_event.dart | 0 .../connection_status/connection_status_state.dart | 0 4 files changed, 1 insertion(+), 1 deletion(-) rename lib/logic/{cubit => bloc}/connection_status/connection_status_bloc.dart (100%) rename lib/logic/{cubit => bloc}/connection_status/connection_status_event.dart (100%) rename lib/logic/{cubit => bloc}/connection_status/connection_status_state.dart (100%) diff --git a/lib/config/bloc_config.dart b/lib/config/bloc_config.dart index 8c635a80..ecb54025 100644 --- a/lib/config/bloc_config.dart +++ b/lib/config/bloc_config.dart @@ -1,12 +1,12 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart'; +import 'package:selfprivacy/logic/bloc/connection_status/connection_status_bloc.dart'; import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart'; import 'package:selfprivacy/logic/bloc/services/services_bloc.dart'; import 'package:selfprivacy/logic/bloc/volumes/volumes_bloc.dart'; import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart'; import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart'; -import 'package:selfprivacy/logic/cubit/connection_status/connection_status_bloc.dart'; import 'package:selfprivacy/logic/cubit/devices/devices_cubit.dart'; import 'package:selfprivacy/logic/cubit/dns_records/dns_records_cubit.dart'; import 'package:selfprivacy/logic/cubit/recovery_key/recovery_key_cubit.dart'; diff --git a/lib/logic/cubit/connection_status/connection_status_bloc.dart b/lib/logic/bloc/connection_status/connection_status_bloc.dart similarity index 100% rename from lib/logic/cubit/connection_status/connection_status_bloc.dart rename to lib/logic/bloc/connection_status/connection_status_bloc.dart diff --git a/lib/logic/cubit/connection_status/connection_status_event.dart b/lib/logic/bloc/connection_status/connection_status_event.dart similarity index 100% rename from lib/logic/cubit/connection_status/connection_status_event.dart rename to lib/logic/bloc/connection_status/connection_status_event.dart diff --git a/lib/logic/cubit/connection_status/connection_status_state.dart b/lib/logic/bloc/connection_status/connection_status_state.dart similarity index 100% rename from lib/logic/cubit/connection_status/connection_status_state.dart rename to lib/logic/bloc/connection_status/connection_status_state.dart From 3a525f0d11468140f51111edcf084bbe6876b366 Mon Sep 17 00:00:00 2001 From: Inex Code Date: Thu, 8 Feb 2024 18:08:29 +0300 Subject: [PATCH 21/34] refactor: Replace RecoveryKeyCubit with RecoveryKeyBloc --- lib/config/bloc_config.dart | 8 +- .../bloc/recovery_key/recovery_key_bloc.dart | 94 +++++++++++++++++++ .../bloc/recovery_key/recovery_key_event.dart | 41 ++++++++ .../bloc/recovery_key/recovery_key_state.dart | 67 +++++++++++++ .../recovery_key/recovery_key_cubit.dart | 80 ---------------- .../recovery_key/recovery_key_state.dart | 39 -------- .../get_it/api_connection_repository.dart | 12 +++ .../more/app_settings/developer_settings.dart | 14 --- lib/ui/pages/recovery_key/recovery_key.dart | 69 +++++--------- .../recovery_key/recovery_key_receiving.dart | 75 ++++++++++----- 10 files changed, 294 insertions(+), 205 deletions(-) create mode 100644 lib/logic/bloc/recovery_key/recovery_key_bloc.dart create mode 100644 lib/logic/bloc/recovery_key/recovery_key_event.dart create mode 100644 lib/logic/bloc/recovery_key/recovery_key_state.dart delete mode 100644 lib/logic/cubit/recovery_key/recovery_key_cubit.dart delete mode 100644 lib/logic/cubit/recovery_key/recovery_key_state.dart diff --git a/lib/config/bloc_config.dart b/lib/config/bloc_config.dart index ecb54025..55fca0fe 100644 --- a/lib/config/bloc_config.dart +++ b/lib/config/bloc_config.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart'; import 'package:selfprivacy/logic/bloc/connection_status/connection_status_bloc.dart'; +import 'package:selfprivacy/logic/bloc/recovery_key/recovery_key_bloc.dart'; import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart'; import 'package:selfprivacy/logic/bloc/services/services_bloc.dart'; import 'package:selfprivacy/logic/bloc/volumes/volumes_bloc.dart'; @@ -9,7 +10,6 @@ import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart'; import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart'; import 'package:selfprivacy/logic/cubit/devices/devices_cubit.dart'; import 'package:selfprivacy/logic/cubit/dns_records/dns_records_cubit.dart'; -import 'package:selfprivacy/logic/cubit/recovery_key/recovery_key_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_detailed_info/server_detailed_info_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/logic/cubit/support_system/support_system_cubit.dart'; @@ -31,7 +31,7 @@ class BlocAndProviderConfigState extends State { late final ServicesBloc servicesBloc; late final BackupsBloc backupsBloc; late final DnsRecordsCubit dnsRecordsCubit; - late final RecoveryKeyCubit recoveryKeyCubit; + late final RecoveryKeyBloc recoveryKeyBloc; late final ApiDevicesCubit apiDevicesCubit; late final ServerJobsBloc serverJobsBloc; late final ConnectionStatusBloc connectionStatusBloc; @@ -47,7 +47,7 @@ class BlocAndProviderConfigState extends State { servicesBloc = ServicesBloc(); backupsBloc = BackupsBloc(); dnsRecordsCubit = DnsRecordsCubit(); - recoveryKeyCubit = RecoveryKeyCubit(); + recoveryKeyBloc = RecoveryKeyBloc(); apiDevicesCubit = ApiDevicesCubit(); serverJobsBloc = ServerJobsBloc(); connectionStatusBloc = ConnectionStatusBloc(); @@ -90,7 +90,7 @@ class BlocAndProviderConfigState extends State { create: (final _) => dnsRecordsCubit, ), BlocProvider( - create: (final _) => recoveryKeyCubit, + create: (final _) => recoveryKeyBloc, ), BlocProvider( create: (final _) => apiDevicesCubit, diff --git a/lib/logic/bloc/recovery_key/recovery_key_bloc.dart b/lib/logic/bloc/recovery_key/recovery_key_bloc.dart new file mode 100644 index 00000000..905d6594 --- /dev/null +++ b/lib/logic/bloc/recovery_key/recovery_key_bloc.dart @@ -0,0 +1,94 @@ +import 'dart:async'; + +import 'package:bloc_concurrency/bloc_concurrency.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:selfprivacy/config/get_it_config.dart'; +import 'package:selfprivacy/logic/api_maps/generic_result.dart'; +import 'package:selfprivacy/logic/models/json/recovery_token_status.dart'; + +part 'recovery_key_event.dart'; +part 'recovery_key_state.dart'; + +class RecoveryKeyBloc extends Bloc { + RecoveryKeyBloc() : super(RecoveryKeyInitial()) { + on( + _mapRecoveryKeyStatusChangedToState, + transformer: sequential(), + ); + on( + _mapCreateNewRecoveryKeyToState, + transformer: sequential(), + ); + on( + _mapRecoveryKeyStatusRefreshToState, + transformer: sequential(), + ); + on( + _mapRecoveryKeyStatusRefreshToState, + transformer: droppable(), + ); + + final apiConnectionRepository = getIt(); + _apiDataSubscription = apiConnectionRepository.dataStream.listen( + (final ApiData apiData) { + add( + RecoveryKeyStatusChanged(apiData.recoveryKeyStatus.data), + ); + }, + ); + } + + StreamSubscription? _apiDataSubscription; + + Future _mapRecoveryKeyStatusChangedToState( + final RecoveryKeyStatusChanged event, + final Emitter emit, + ) async { + if (state is RecoveryKeyCreating) { + return; + } + if (event.recoveryKeyStatus == null) { + emit(RecoveryKeyError()); + return; + } + emit(RecoveryKeyLoaded(keyStatus: event.recoveryKeyStatus)); + } + + Future _mapCreateNewRecoveryKeyToState( + final CreateNewRecoveryKey event, + final Emitter emit, + ) async { + emit(RecoveryKeyCreating()); + final GenericResult response = + await getIt().api.generateRecoveryToken( + event.expirationDate, + event.numberOfUses, + ); + if (response.success) { + emit(RecoveryKeyCreating(recoveryKey: response.data)); + } else { + emit(RecoveryKeyCreating(error: response.message ?? 'Unknown error')); + } + } + + Future _mapRecoveryKeyStatusRefreshToState( + final RecoveryKeyEvent event, + final Emitter emit, + ) async { + emit(RecoveryKeyRefreshing(keyStatus: state._status)); + getIt().apiData.recoveryKeyStatus.invalidate(); + await getIt().reload(null); + } + + @override + void onChange(final Change change) { + super.onChange(change); + } + + @override + Future close() { + _apiDataSubscription?.cancel(); + return super.close(); + } +} diff --git a/lib/logic/bloc/recovery_key/recovery_key_event.dart b/lib/logic/bloc/recovery_key/recovery_key_event.dart new file mode 100644 index 00000000..80104ada --- /dev/null +++ b/lib/logic/bloc/recovery_key/recovery_key_event.dart @@ -0,0 +1,41 @@ +part of 'recovery_key_bloc.dart'; + +sealed class RecoveryKeyEvent extends Equatable { + const RecoveryKeyEvent(); +} + +class RecoveryKeyStatusChanged extends RecoveryKeyEvent { + const RecoveryKeyStatusChanged(this.recoveryKeyStatus); + + final RecoveryKeyStatus? recoveryKeyStatus; + + @override + List get props => [recoveryKeyStatus]; +} + +class CreateNewRecoveryKey extends RecoveryKeyEvent { + const CreateNewRecoveryKey({ + this.expirationDate, + this.numberOfUses, + }); + + final DateTime? expirationDate; + final int? numberOfUses; + + @override + List get props => [expirationDate, numberOfUses]; +} + +class ConsumedNewRecoveryKey extends RecoveryKeyEvent { + const ConsumedNewRecoveryKey(); + + @override + List get props => []; +} + +class RecoveryKeyStatusRefresh extends RecoveryKeyEvent { + const RecoveryKeyStatusRefresh(); + + @override + List get props => []; +} diff --git a/lib/logic/bloc/recovery_key/recovery_key_state.dart b/lib/logic/bloc/recovery_key/recovery_key_state.dart new file mode 100644 index 00000000..1e22ac9f --- /dev/null +++ b/lib/logic/bloc/recovery_key/recovery_key_state.dart @@ -0,0 +1,67 @@ +part of 'recovery_key_bloc.dart'; + +sealed class RecoveryKeyState extends Equatable { + RecoveryKeyState({ + required final RecoveryKeyStatus? keyStatus, + }) : _hashCode = keyStatus.hashCode; + + final int _hashCode; + + RecoveryKeyStatus get _status => + getIt().apiData.recoveryKeyStatus.data ?? + const RecoveryKeyStatus(exists: false, valid: false); + + bool get exists => _status.exists; + bool get isValid => _status.valid; + DateTime? get generatedAt => _status.date; + DateTime? get expiresAt => _status.expiration; + int? get usesLeft => _status.usesLeft; + + bool get isInvalidBecauseExpired => + _status.expiration != null && + _status.expiration!.isBefore(DateTime.now()); + + bool get isInvalidBecauseUsed => + _status.usesLeft != null && _status.usesLeft == 0; +} + +class RecoveryKeyInitial extends RecoveryKeyState { + RecoveryKeyInitial() + : super(keyStatus: const RecoveryKeyStatus(exists: false, valid: false)); + + @override + List get props => [_hashCode]; +} + +class RecoveryKeyRefreshing extends RecoveryKeyState { + RecoveryKeyRefreshing({required super.keyStatus}); + + @override + List get props => [_hashCode]; +} + +class RecoveryKeyLoaded extends RecoveryKeyState { + RecoveryKeyLoaded({required super.keyStatus}); + + @override + List get props => [_hashCode]; +} + +class RecoveryKeyError extends RecoveryKeyState { + RecoveryKeyError() + : super(keyStatus: const RecoveryKeyStatus(exists: false, valid: false)); + + @override + List get props => [_hashCode]; +} + +class RecoveryKeyCreating extends RecoveryKeyState { + RecoveryKeyCreating({this.recoveryKey, this.error}) + : super(keyStatus: const RecoveryKeyStatus(exists: false, valid: false)); + + final String? recoveryKey; + final String? error; + + @override + List get props => [_hashCode, recoveryKey, error]; +} diff --git a/lib/logic/cubit/recovery_key/recovery_key_cubit.dart b/lib/logic/cubit/recovery_key/recovery_key_cubit.dart deleted file mode 100644 index 42811b85..00000000 --- a/lib/logic/cubit/recovery_key/recovery_key_cubit.dart +++ /dev/null @@ -1,80 +0,0 @@ -import 'dart:async'; - -import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart'; -import 'package:selfprivacy/logic/common_enum/common_enum.dart'; -import 'package:selfprivacy/logic/cubit/server_connection_dependent/server_connection_dependent_cubit.dart'; -import 'package:selfprivacy/logic/models/json/recovery_token_status.dart'; - -part 'recovery_key_state.dart'; - -class RecoveryKeyCubit - extends ServerConnectionDependentCubit { - RecoveryKeyCubit() : super(const RecoveryKeyState.initial()); - - final ServerApi api = ServerApi(); - - @override - void load() async { - // if (serverInstallationCubit.state is ServerInstallationFinished) { - final RecoveryKeyStatus? status = await _getRecoveryKeyStatus(); - if (status == null) { - emit(state.copyWith(loadingStatus: LoadingStatus.error)); - } else { - emit( - state.copyWith( - status: status, - loadingStatus: LoadingStatus.success, - ), - ); - } - // } else { - // emit(state.copyWith(loadingStatus: LoadingStatus.uninitialized)); - // } - } - - Future _getRecoveryKeyStatus() async { - final GenericResult response = - await api.getRecoveryTokenStatus(); - if (response.success) { - return response.data; - } else { - return null; - } - } - - Future refresh() async { - emit(state.copyWith(loadingStatus: LoadingStatus.refreshing)); - final RecoveryKeyStatus? status = await _getRecoveryKeyStatus(); - if (status == null) { - emit(state.copyWith(loadingStatus: LoadingStatus.error)); - } else { - emit( - state.copyWith(status: status, loadingStatus: LoadingStatus.success), - ); - } - } - - Future generateRecoveryKey({ - final DateTime? expirationDate, - final int? numberOfUses, - }) async { - final GenericResult response = - await api.generateRecoveryToken(expirationDate, numberOfUses); - if (response.success) { - unawaited(refresh()); - return response.data; - } else { - throw GenerationError(response.message ?? 'Unknown error'); - } - } - - @override - void clear() { - emit(state.copyWith(loadingStatus: LoadingStatus.uninitialized)); - } -} - -class GenerationError extends Error { - GenerationError(this.message); - final String message; -} diff --git a/lib/logic/cubit/recovery_key/recovery_key_state.dart b/lib/logic/cubit/recovery_key/recovery_key_state.dart deleted file mode 100644 index b35ae9a3..00000000 --- a/lib/logic/cubit/recovery_key/recovery_key_state.dart +++ /dev/null @@ -1,39 +0,0 @@ -part of 'recovery_key_cubit.dart'; - -class RecoveryKeyState extends ServerInstallationDependendState { - const RecoveryKeyState(this._status, this.loadingStatus); - - const RecoveryKeyState.initial() - : this( - const RecoveryKeyStatus(exists: false, valid: false), - LoadingStatus.refreshing, - ); - - final RecoveryKeyStatus _status; - final LoadingStatus loadingStatus; - - bool get exists => _status.exists; - bool get isValid => _status.valid; - DateTime? get generatedAt => _status.date; - DateTime? get expiresAt => _status.expiration; - int? get usesLeft => _status.usesLeft; - - bool get isInvalidBecauseExpired => - _status.expiration != null && - _status.expiration!.isBefore(DateTime.now()); - - bool get isInvalidBecauseUsed => - _status.usesLeft != null && _status.usesLeft == 0; - - @override - List get props => [_status, loadingStatus]; - - RecoveryKeyState copyWith({ - final RecoveryKeyStatus? status, - final LoadingStatus? loadingStatus, - }) => - RecoveryKeyState( - status ?? _status, - loadingStatus ?? this.loadingStatus, - ); -} diff --git a/lib/logic/get_it/api_connection_repository.dart b/lib/logic/get_it/api_connection_repository.dart index 6f0210ee..6ed4ba79 100644 --- a/lib/logic/get_it/api_connection_repository.dart +++ b/lib/logic/get_it/api_connection_repository.dart @@ -8,6 +8,7 @@ import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.da import 'package:selfprivacy/logic/models/backup.dart'; import 'package:selfprivacy/logic/models/hive/server_details.dart'; import 'package:selfprivacy/logic/models/hive/server_domain.dart'; +import 'package:selfprivacy/logic/models/json/recovery_token_status.dart'; import 'package:selfprivacy/logic/models/json/server_disk_volume.dart'; import 'package:selfprivacy/logic/models/json/server_job.dart'; import 'package:selfprivacy/logic/models/service.dart'; @@ -101,6 +102,8 @@ class ApiConnectionRepository { _apiData.backups.data = await _apiData.backups.fetchData(); _apiData.services.data = await _apiData.services.fetchData(); _apiData.volumes.data = await _apiData.volumes.fetchData(); + _apiData.recoveryKeyStatus.data = + await _apiData.recoveryKeyStatus.fetchData(); _dataStream.add(_apiData); connectionStatus = ConnectionStatus.connected; @@ -140,6 +143,8 @@ class ApiConnectionRepository { .refetchData(version, () => _dataStream.add(_apiData)); await _apiData.volumes .refetchData(version, () => _dataStream.add(_apiData)); + await _apiData.recoveryKeyStatus + .refetchData(version, () => _dataStream.add(_apiData)); } void emitData() { @@ -159,10 +164,12 @@ class ApiData { backupConfig = ApiDataElement( fetchData: () async => api.getBackupsConfiguration(), requiredApiVersion: '>=2.4.2', + ttl: 120, ), backups = ApiDataElement>( fetchData: () async => api.getBackups(), requiredApiVersion: '>=2.4.2', + ttl: 120, ), services = ApiDataElement>( fetchData: () async => api.getAllServices(), @@ -170,6 +177,10 @@ class ApiData { ), volumes = ApiDataElement>( fetchData: () async => api.getServerDiskVolumes(), + ), + recoveryKeyStatus = ApiDataElement( + fetchData: () async => (await api.getRecoveryTokenStatus()).data, + ttl: 300, ); ApiDataElement> serverJobs; @@ -178,6 +189,7 @@ class ApiData { ApiDataElement> backups; ApiDataElement> services; ApiDataElement> volumes; + ApiDataElement recoveryKeyStatus; } enum ConnectionStatus { diff --git a/lib/ui/pages/more/app_settings/developer_settings.dart b/lib/ui/pages/more/app_settings/developer_settings.dart index 015aa3d2..8acf16a4 100644 --- a/lib/ui/pages/more/app_settings/developer_settings.dart +++ b/lib/ui/pages/more/app_settings/developer_settings.dart @@ -4,8 +4,6 @@ import 'package:flutter/material.dart'; import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/logic/api_maps/tls_options.dart'; import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart'; -import 'package:selfprivacy/logic/cubit/devices/devices_cubit.dart'; -import 'package:selfprivacy/logic/cubit/recovery_key/recovery_key_cubit.dart'; import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; @RoutePage() @@ -89,18 +87,6 @@ class _DeveloperSettingsPageState extends State { ), ), ), - ListTile( - title: const Text('ApiDevicesCubit'), - subtitle: Text( - context.watch().state.status.toString(), - ), - ), - ListTile( - title: const Text('RecoveryKeyCubit'), - subtitle: Text( - context.watch().state.loadingStatus.toString(), - ), - ), ListTile( title: const Text('ApiConnectionRepository status'), subtitle: Text( diff --git a/lib/ui/pages/recovery_key/recovery_key.dart b/lib/ui/pages/recovery_key/recovery_key.dart index ff449377..a8a47593 100644 --- a/lib/ui/pages/recovery_key/recovery_key.dart +++ b/lib/ui/pages/recovery_key/recovery_key.dart @@ -2,9 +2,7 @@ import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:selfprivacy/config/get_it_config.dart'; -import 'package:selfprivacy/logic/common_enum/common_enum.dart'; -import 'package:selfprivacy/logic/cubit/recovery_key/recovery_key_cubit.dart'; +import 'package:selfprivacy/logic/bloc/recovery_key/recovery_key_bloc.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/ui/components/buttons/brand_button.dart'; import 'package:selfprivacy/ui/components/cards/filled_card.dart'; @@ -21,34 +19,29 @@ class RecoveryKeyPage extends StatefulWidget { } class _RecoveryKeyPageState extends State { - @override - void initState() { - super.initState(); - context.read().load(); - } - @override Widget build(final BuildContext context) { - final RecoveryKeyState keyStatus = context.watch().state; + final RecoveryKeyState keyStatus = context.watch().state; final List widgets; String? subtitle = keyStatus.exists ? null : 'recovery_key.key_main_description'.tr(); - switch (keyStatus.loadingStatus) { - case LoadingStatus.refreshing: + switch (keyStatus) { + case RecoveryKeyRefreshing(): subtitle = 'recovery_key.key_synchronizing'.tr(); widgets = [ const Center(child: CircularProgressIndicator()), ]; break; - case LoadingStatus.success: + case RecoveryKeyLoaded(): widgets = [ const RecoveryKeyContent(), ]; break; - case LoadingStatus.uninitialized: - case LoadingStatus.error: + case RecoveryKeyInitial(): + case RecoveryKeyError(): + case RecoveryKeyCreating(): subtitle = 'recovery_key.key_connection_error'.tr(); widgets = [ const Icon(Icons.sentiment_dissatisfied_outlined), @@ -58,7 +51,7 @@ class _RecoveryKeyPageState extends State { return RefreshIndicator( onRefresh: () async { - context.read().load(); + context.read().add(const RecoveryKeyStatusRefresh()); }, child: BrandHeroScreen( heroTitle: 'recovery_key.key_main_header'.tr(), @@ -83,7 +76,7 @@ class _RecoveryKeyContentState extends State { @override Widget build(final BuildContext context) { - final RecoveryKeyState keyStatus = context.watch().state; + final RecoveryKeyState keyStatus = context.watch().state; return Column( children: [ @@ -241,34 +234,24 @@ class _RecoveryKeyConfigurationState extends State { setState(() { _isLoading = true; }); - try { - final String token = - await context.read().generateRecoveryKey( - numberOfUses: _isAmountToggled - ? int.tryParse(_amountController.text) - : null, - expirationDate: _isExpirationToggled ? _selectedDate : null, - ); - if (!mounted) { - return; - } - setState(() { - _isLoading = false; - }); - await Navigator.of(context).push( - materialRoute( - RecoveryKeyReceiving(recoveryKey: token), // TO DO - ), - ); - } on GenerationError catch (e) { - setState(() { - _isLoading = false; - }); - getIt().showSnackBar( - 'recovery_key.generation_error'.tr(args: [e.message]), - ); + context.read().add( + CreateNewRecoveryKey( + expirationDate: _isExpirationToggled ? _selectedDate : null, + numberOfUses: + _isAmountToggled ? int.tryParse(_amountController.text) : null, + ), + ); + if (!mounted) { return; } + setState(() { + _isLoading = false; + }); + await Navigator.of(context).push( + materialRoute( + const RecoveryKeyReceiving(), + ), + ); } void _updateErrorStatuses() { diff --git a/lib/ui/pages/recovery_key/recovery_key_receiving.dart b/lib/ui/pages/recovery_key/recovery_key_receiving.dart index 334ba308..8167ac1e 100644 --- a/lib/ui/pages/recovery_key/recovery_key_receiving.dart +++ b/lib/ui/pages/recovery_key/recovery_key_receiving.dart @@ -1,23 +1,36 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:selfprivacy/logic/bloc/recovery_key/recovery_key_bloc.dart'; import 'package:selfprivacy/ui/components/buttons/brand_button.dart'; import 'package:selfprivacy/ui/components/info_box/info_box.dart'; import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; class RecoveryKeyReceiving extends StatelessWidget { - const RecoveryKeyReceiving({required this.recoveryKey, super.key}); - - final String recoveryKey; + const RecoveryKeyReceiving({super.key}); @override - Widget build(final BuildContext context) => BrandHeroScreen( - heroTitle: 'recovery_key.key_main_header'.tr(), - heroSubtitle: 'recovery_key.key_receiving_description'.tr(), - hasBackButton: true, - hasFlashButton: false, - children: [ - const Divider(), - const SizedBox(height: 16), + Widget build(final BuildContext context) { + final recoveryKeyState = context.watch().state; + + final String? recoveryKey = recoveryKeyState is RecoveryKeyCreating + ? recoveryKeyState.recoveryKey + : null; + + final String? error = + recoveryKeyState is RecoveryKeyCreating ? recoveryKeyState.error : null; + + return BrandHeroScreen( + heroTitle: 'recovery_key.key_main_header'.tr(), + heroSubtitle: 'recovery_key.key_receiving_description'.tr(), + hasBackButton: true, + hasFlashButton: false, + children: [ + const Divider(), + const SizedBox(height: 16), + if (recoveryKey == null && error == null) + const Center(child: CircularProgressIndicator()), + if (recoveryKey != null) Text( recoveryKey, style: Theme.of(context).textTheme.bodyLarge!.copyWith( @@ -26,19 +39,31 @@ class RecoveryKeyReceiving extends StatelessWidget { ), textAlign: TextAlign.center, ), - const SizedBox(height: 16), - const Divider(), - const SizedBox(height: 16), - InfoBox( - text: 'recovery_key.key_receiving_info'.tr(), + if (error != null) + Text( + 'recovery_key.generation_error'.tr(args: [error]), + style: Theme.of(context).textTheme.bodyLarge!.copyWith( + fontSize: 24, + fontFamily: 'RobotoMono', + color: Theme.of(context).colorScheme.error, + ), + textAlign: TextAlign.center, ), - const SizedBox(height: 16), - BrandButton.filled( - child: Text('recovery_key.key_receiving_done'.tr()), - onPressed: () { - Navigator.of(context).popUntil((final route) => route.isFirst); - }, - ), - ], - ); + const SizedBox(height: 16), + const Divider(), + const SizedBox(height: 16), + InfoBox( + text: 'recovery_key.key_receiving_info'.tr(), + ), + const SizedBox(height: 16), + BrandButton.filled( + child: Text('recovery_key.key_receiving_done'.tr()), + onPressed: () { + context.read().add(const ConsumedNewRecoveryKey()); + Navigator.of(context).popUntil((final route) => route.isFirst); + }, + ), + ], + ); + } } From 710b9b53ddd2dbbbe8cbcd90a85f94c7b2d4edbf Mon Sep 17 00:00:00 2001 From: Inex Code Date: Fri, 9 Feb 2024 14:07:03 +0300 Subject: [PATCH 22/34] refactor: Replace ApiDevicesCubit with DevicesBloc --- lib/config/bloc_config.dart | 8 +- lib/logic/bloc/devices/devices_bloc.dart | 114 ++++++++++++++++++ lib/logic/bloc/devices/devices_event.dart | 23 ++++ lib/logic/bloc/devices/devices_state.dart | 53 ++++++++ .../bloc/recovery_key/recovery_key_bloc.dart | 38 +++--- .../bloc/recovery_key/recovery_key_event.dart | 20 --- .../bloc/recovery_key/recovery_key_state.dart | 11 -- lib/logic/cubit/devices/devices_cubit.dart | 75 ------------ lib/logic/cubit/devices/devices_state.dart | 34 ------ lib/logic/cubit/users/users_cubit.dart | 2 +- .../get_it/api_connection_repository.dart | 8 ++ lib/logic/models/json/api_token.dart | 8 +- lib/ui/pages/devices/devices.dart | 32 ++--- lib/ui/pages/devices/new_device.dart | 4 +- lib/ui/pages/recovery_key/recovery_key.dart | 44 ++++--- .../recovery_key/recovery_key_receiving.dart | 75 ++++-------- 16 files changed, 297 insertions(+), 252 deletions(-) create mode 100644 lib/logic/bloc/devices/devices_bloc.dart create mode 100644 lib/logic/bloc/devices/devices_event.dart create mode 100644 lib/logic/bloc/devices/devices_state.dart delete mode 100644 lib/logic/cubit/devices/devices_cubit.dart delete mode 100644 lib/logic/cubit/devices/devices_state.dart diff --git a/lib/config/bloc_config.dart b/lib/config/bloc_config.dart index 55fca0fe..881159a6 100644 --- a/lib/config/bloc_config.dart +++ b/lib/config/bloc_config.dart @@ -2,13 +2,13 @@ import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart'; import 'package:selfprivacy/logic/bloc/connection_status/connection_status_bloc.dart'; +import 'package:selfprivacy/logic/bloc/devices/devices_bloc.dart'; import 'package:selfprivacy/logic/bloc/recovery_key/recovery_key_bloc.dart'; import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart'; import 'package:selfprivacy/logic/bloc/services/services_bloc.dart'; import 'package:selfprivacy/logic/bloc/volumes/volumes_bloc.dart'; import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart'; import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart'; -import 'package:selfprivacy/logic/cubit/devices/devices_cubit.dart'; import 'package:selfprivacy/logic/cubit/dns_records/dns_records_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_detailed_info/server_detailed_info_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; @@ -32,7 +32,7 @@ class BlocAndProviderConfigState extends State { late final BackupsBloc backupsBloc; late final DnsRecordsCubit dnsRecordsCubit; late final RecoveryKeyBloc recoveryKeyBloc; - late final ApiDevicesCubit apiDevicesCubit; + late final DevicesBloc devicesBloc; late final ServerJobsBloc serverJobsBloc; late final ConnectionStatusBloc connectionStatusBloc; late final ServerDetailsCubit serverDetailsCubit; @@ -48,7 +48,7 @@ class BlocAndProviderConfigState extends State { backupsBloc = BackupsBloc(); dnsRecordsCubit = DnsRecordsCubit(); recoveryKeyBloc = RecoveryKeyBloc(); - apiDevicesCubit = ApiDevicesCubit(); + devicesBloc = DevicesBloc(); serverJobsBloc = ServerJobsBloc(); connectionStatusBloc = ConnectionStatusBloc(); serverDetailsCubit = ServerDetailsCubit(); @@ -93,7 +93,7 @@ class BlocAndProviderConfigState extends State { create: (final _) => recoveryKeyBloc, ), BlocProvider( - create: (final _) => apiDevicesCubit, + create: (final _) => devicesBloc, ), BlocProvider( create: (final _) => serverJobsBloc, diff --git a/lib/logic/bloc/devices/devices_bloc.dart b/lib/logic/bloc/devices/devices_bloc.dart new file mode 100644 index 00000000..e1d248c7 --- /dev/null +++ b/lib/logic/bloc/devices/devices_bloc.dart @@ -0,0 +1,114 @@ +import 'dart:async'; + +import 'package:bloc_concurrency/bloc_concurrency.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:selfprivacy/config/get_it_config.dart'; +import 'package:selfprivacy/logic/api_maps/generic_result.dart'; +import 'package:selfprivacy/logic/models/json/api_token.dart'; + +part 'devices_event.dart'; +part 'devices_state.dart'; + +class DevicesBloc extends Bloc { + DevicesBloc() : super(DevicesInitial()) { + on( + _mapDevicesListChangedToState, + transformer: sequential(), + ); + on( + _mapDeleteDeviceToState, + transformer: sequential(), + ); + + final apiConnectionRepository = getIt(); + _apiDataSubscription = apiConnectionRepository.dataStream.listen( + (final ApiData apiData) { + print('============'); + print(apiData.devices.data); + add( + DevicesListChanged(apiData.devices.data), + ); + }, + ); + } + + StreamSubscription? _apiDataSubscription; + + Future _mapDevicesListChangedToState( + final DevicesListChanged event, + final Emitter emit, + ) async { + if (state is DevicesDeleting) { + return; + } + print(event.devices); + if (event.devices == null) { + emit(DevicesError()); + return; + } + emit(DevicesLoaded(devices: event.devices!)); + } + + Future refresh() async { + getIt().apiData.devices.invalidate(); + await getIt().reload(null); + } + + Future _mapDeleteDeviceToState( + final DeleteDevice event, + final Emitter emit, + ) async { + // Optimistically remove the device from the list + emit( + DevicesDeleting( + devices: state.devices + .where((final d) => d.name != event.device.name) + .toList(), + ), + ); + + final GenericResult response = await getIt() + .api + .deleteApiToken(event.device.name); + if (response.success) { + getIt().apiData.devices.invalidate(); + emit( + DevicesLoaded( + devices: state.devices + .where((final d) => d.name != event.device.name) + .toList(), + ), + ); + } else { + getIt() + .showSnackBar(response.message ?? 'Error deleting device'); + emit(DevicesLoaded(devices: state.devices)); + } + } + + Future getNewDeviceKey() async { + final GenericResult response = + await getIt().api.createDeviceToken(); + if (response.success) { + return response.data; + } else { + getIt().showSnackBar( + response.message ?? 'Error getting new device key', + ); + return null; + } + } + + @override + void onChange(final Change change) { + super.onChange(change); + print(change); + } + + @override + Future close() { + _apiDataSubscription?.cancel(); + return super.close(); + } +} diff --git a/lib/logic/bloc/devices/devices_event.dart b/lib/logic/bloc/devices/devices_event.dart new file mode 100644 index 00000000..655cca39 --- /dev/null +++ b/lib/logic/bloc/devices/devices_event.dart @@ -0,0 +1,23 @@ +part of 'devices_bloc.dart'; + +sealed class DevicesEvent extends Equatable { + const DevicesEvent(); +} + +class DevicesListChanged extends DevicesEvent { + const DevicesListChanged(this.devices); + + final List? devices; + + @override + List get props => []; +} + +class DeleteDevice extends DevicesEvent { + const DeleteDevice(this.device); + + final ApiToken device; + + @override + List get props => [device]; +} diff --git a/lib/logic/bloc/devices/devices_state.dart b/lib/logic/bloc/devices/devices_state.dart new file mode 100644 index 00000000..85e932df --- /dev/null +++ b/lib/logic/bloc/devices/devices_state.dart @@ -0,0 +1,53 @@ +part of 'devices_bloc.dart'; + +sealed class DevicesState extends Equatable { + DevicesState({ + required final List devices, + }) : _hashCode = devices.hashCode; + + final int _hashCode; + + List get _devices => + getIt().apiData.devices.data ?? const []; + + List get devices => _devices; + ApiToken get thisDevice => _devices.firstWhere( + (final device) => device.isCaller, + orElse: () => ApiToken( + name: 'Error fetching device', + isCaller: true, + date: DateTime.now(), + ), + ); + + List get otherDevices => + _devices.where((final device) => !device.isCaller).toList(); +} + +class DevicesInitial extends DevicesState { + DevicesInitial() : super(devices: const []); + + @override + List get props => [_hashCode]; +} + +class DevicesLoaded extends DevicesState { + DevicesLoaded({required super.devices}); + + @override + List get props => [_hashCode]; +} + +class DevicesError extends DevicesState { + DevicesError() : super(devices: const []); + + @override + List get props => [_hashCode]; +} + +class DevicesDeleting extends DevicesState { + DevicesDeleting({required super.devices}); + + @override + List get props => [_hashCode]; +} diff --git a/lib/logic/bloc/recovery_key/recovery_key_bloc.dart b/lib/logic/bloc/recovery_key/recovery_key_bloc.dart index 905d6594..eedcdaff 100644 --- a/lib/logic/bloc/recovery_key/recovery_key_bloc.dart +++ b/lib/logic/bloc/recovery_key/recovery_key_bloc.dart @@ -16,14 +16,6 @@ class RecoveryKeyBloc extends Bloc { _mapRecoveryKeyStatusChangedToState, transformer: sequential(), ); - on( - _mapCreateNewRecoveryKeyToState, - transformer: sequential(), - ); - on( - _mapRecoveryKeyStatusRefreshToState, - transformer: sequential(), - ); on( _mapRecoveryKeyStatusRefreshToState, transformer: droppable(), @@ -45,9 +37,6 @@ class RecoveryKeyBloc extends Bloc { final RecoveryKeyStatusChanged event, final Emitter emit, ) async { - if (state is RecoveryKeyCreating) { - return; - } if (event.recoveryKeyStatus == null) { emit(RecoveryKeyError()); return; @@ -55,20 +44,20 @@ class RecoveryKeyBloc extends Bloc { emit(RecoveryKeyLoaded(keyStatus: event.recoveryKeyStatus)); } - Future _mapCreateNewRecoveryKeyToState( - final CreateNewRecoveryKey event, - final Emitter emit, - ) async { - emit(RecoveryKeyCreating()); + Future generateRecoveryKey({ + final DateTime? expirationDate, + final int? numberOfUses, + }) async { final GenericResult response = - await getIt().api.generateRecoveryToken( - event.expirationDate, - event.numberOfUses, - ); + await getIt() + .api + .generateRecoveryToken(expirationDate, numberOfUses); if (response.success) { - emit(RecoveryKeyCreating(recoveryKey: response.data)); + getIt().apiData.recoveryKeyStatus.invalidate(); + unawaited(getIt().reload(null)); + return response.data; } else { - emit(RecoveryKeyCreating(error: response.message ?? 'Unknown error')); + throw GenerationError(response.message ?? 'Unknown error'); } } @@ -92,3 +81,8 @@ class RecoveryKeyBloc extends Bloc { return super.close(); } } + +class GenerationError extends Error { + GenerationError(this.message); + final String message; +} diff --git a/lib/logic/bloc/recovery_key/recovery_key_event.dart b/lib/logic/bloc/recovery_key/recovery_key_event.dart index 80104ada..bd54ff06 100644 --- a/lib/logic/bloc/recovery_key/recovery_key_event.dart +++ b/lib/logic/bloc/recovery_key/recovery_key_event.dart @@ -13,26 +13,6 @@ class RecoveryKeyStatusChanged extends RecoveryKeyEvent { List get props => [recoveryKeyStatus]; } -class CreateNewRecoveryKey extends RecoveryKeyEvent { - const CreateNewRecoveryKey({ - this.expirationDate, - this.numberOfUses, - }); - - final DateTime? expirationDate; - final int? numberOfUses; - - @override - List get props => [expirationDate, numberOfUses]; -} - -class ConsumedNewRecoveryKey extends RecoveryKeyEvent { - const ConsumedNewRecoveryKey(); - - @override - List get props => []; -} - class RecoveryKeyStatusRefresh extends RecoveryKeyEvent { const RecoveryKeyStatusRefresh(); diff --git a/lib/logic/bloc/recovery_key/recovery_key_state.dart b/lib/logic/bloc/recovery_key/recovery_key_state.dart index 1e22ac9f..5a5730b2 100644 --- a/lib/logic/bloc/recovery_key/recovery_key_state.dart +++ b/lib/logic/bloc/recovery_key/recovery_key_state.dart @@ -54,14 +54,3 @@ class RecoveryKeyError extends RecoveryKeyState { @override List get props => [_hashCode]; } - -class RecoveryKeyCreating extends RecoveryKeyState { - RecoveryKeyCreating({this.recoveryKey, this.error}) - : super(keyStatus: const RecoveryKeyStatus(exists: false, valid: false)); - - final String? recoveryKey; - final String? error; - - @override - List get props => [_hashCode, recoveryKey, error]; -} diff --git a/lib/logic/cubit/devices/devices_cubit.dart b/lib/logic/cubit/devices/devices_cubit.dart deleted file mode 100644 index 699764dd..00000000 --- a/lib/logic/cubit/devices/devices_cubit.dart +++ /dev/null @@ -1,75 +0,0 @@ -import 'package:selfprivacy/config/get_it_config.dart'; -import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart'; -import 'package:selfprivacy/logic/common_enum/common_enum.dart'; -import 'package:selfprivacy/logic/cubit/server_connection_dependent/server_connection_dependent_cubit.dart'; -import 'package:selfprivacy/logic/models/json/api_token.dart'; - -part 'devices_state.dart'; - -class ApiDevicesCubit extends ServerConnectionDependentCubit { - ApiDevicesCubit() : super(const ApiDevicesState.initial()); - - final ServerApi api = ServerApi(); - - @override - void load() async { - // if (serverInstallationCubit.state is ServerInstallationFinished) { - _refetch(); - // } - } - - Future refresh() async { - emit(ApiDevicesState([state.thisDevice], LoadingStatus.refreshing)); - _refetch(); - } - - void _refetch() async { - final List? devices = await _getApiTokens(); - if (devices != null) { - emit(ApiDevicesState(devices, LoadingStatus.success)); - } else { - emit(const ApiDevicesState([], LoadingStatus.error)); - } - } - - Future?> _getApiTokens() async { - final GenericResult> response = await api.getApiTokens(); - if (response.success) { - return response.data; - } else { - return null; - } - } - - Future deleteDevice(final ApiToken device) async { - final GenericResult response = await api.deleteApiToken(device.name); - if (response.success) { - emit( - ApiDevicesState( - state.devices.where((final d) => d.name != device.name).toList(), - LoadingStatus.success, - ), - ); - } else { - getIt() - .showSnackBar(response.message ?? 'Error deleting device'); - } - } - - Future getNewDeviceKey() async { - final GenericResult response = await api.createDeviceToken(); - if (response.success) { - return response.data; - } else { - getIt().showSnackBar( - response.message ?? 'Error getting new device key', - ); - return null; - } - } - - @override - void clear() { - emit(const ApiDevicesState.initial()); - } -} diff --git a/lib/logic/cubit/devices/devices_state.dart b/lib/logic/cubit/devices/devices_state.dart deleted file mode 100644 index 86fd53c2..00000000 --- a/lib/logic/cubit/devices/devices_state.dart +++ /dev/null @@ -1,34 +0,0 @@ -part of 'devices_cubit.dart'; - -class ApiDevicesState extends ServerInstallationDependendState { - const ApiDevicesState(this._devices, this.status); - - const ApiDevicesState.initial() : this(const [], LoadingStatus.uninitialized); - final List _devices; - final LoadingStatus status; - - List get devices => _devices; - ApiToken get thisDevice => _devices.firstWhere( - (final device) => device.isCaller, - orElse: () => ApiToken( - name: 'Error fetching device', - isCaller: true, - date: DateTime.now(), - ), - ); - - List get otherDevices => - _devices.where((final device) => !device.isCaller).toList(); - - ApiDevicesState copyWith({ - final List? devices, - final LoadingStatus? status, - }) => - ApiDevicesState( - devices ?? _devices, - status ?? this.status, - ); - - @override - List get props => [_devices]; -} diff --git a/lib/logic/cubit/users/users_cubit.dart b/lib/logic/cubit/users/users_cubit.dart index 1c242adb..30185e60 100644 --- a/lib/logic/cubit/users/users_cubit.dart +++ b/lib/logic/cubit/users/users_cubit.dart @@ -41,7 +41,7 @@ class UsersCubit extends ServerConnectionDependentCubit { } Future refresh() async { - if (getIt().connectionStatus != + if (getIt().connectionStatus == ConnectionStatus.nonexistent) { return; } diff --git a/lib/logic/get_it/api_connection_repository.dart b/lib/logic/get_it/api_connection_repository.dart index 6ed4ba79..c504e06a 100644 --- a/lib/logic/get_it/api_connection_repository.dart +++ b/lib/logic/get_it/api_connection_repository.dart @@ -8,6 +8,7 @@ import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.da import 'package:selfprivacy/logic/models/backup.dart'; import 'package:selfprivacy/logic/models/hive/server_details.dart'; import 'package:selfprivacy/logic/models/hive/server_domain.dart'; +import 'package:selfprivacy/logic/models/json/api_token.dart'; import 'package:selfprivacy/logic/models/json/recovery_token_status.dart'; import 'package:selfprivacy/logic/models/json/server_disk_volume.dart'; import 'package:selfprivacy/logic/models/json/server_job.dart'; @@ -104,6 +105,7 @@ class ApiConnectionRepository { _apiData.volumes.data = await _apiData.volumes.fetchData(); _apiData.recoveryKeyStatus.data = await _apiData.recoveryKeyStatus.fetchData(); + _apiData.devices.data = await _apiData.devices.fetchData(); _dataStream.add(_apiData); connectionStatus = ConnectionStatus.connected; @@ -145,6 +147,8 @@ class ApiConnectionRepository { .refetchData(version, () => _dataStream.add(_apiData)); await _apiData.recoveryKeyStatus .refetchData(version, () => _dataStream.add(_apiData)); + await _apiData.devices + .refetchData(version, () => _dataStream.add(_apiData)); } void emitData() { @@ -181,6 +185,9 @@ class ApiData { recoveryKeyStatus = ApiDataElement( fetchData: () async => (await api.getRecoveryTokenStatus()).data, ttl: 300, + ), + devices = ApiDataElement>( + fetchData: () async => (await api.getApiTokens()).data, ); ApiDataElement> serverJobs; @@ -190,6 +197,7 @@ class ApiData { ApiDataElement> services; ApiDataElement> volumes; ApiDataElement recoveryKeyStatus; + ApiDataElement> devices; } enum ConnectionStatus { diff --git a/lib/logic/models/json/api_token.dart b/lib/logic/models/json/api_token.dart index f53f7f02..d5f4050e 100644 --- a/lib/logic/models/json/api_token.dart +++ b/lib/logic/models/json/api_token.dart @@ -1,13 +1,14 @@ +import 'package:equatable/equatable.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/schema/server_api.graphql.dart'; part 'api_token.g.dart'; @JsonSerializable() -class ApiToken { +class ApiToken extends Equatable { factory ApiToken.fromJson(final Map json) => _$ApiTokenFromJson(json); - ApiToken({ + const ApiToken({ required this.name, required this.date, required this.isCaller, @@ -25,4 +26,7 @@ class ApiToken { final DateTime date; @JsonKey(name: 'is_caller') final bool isCaller; + + @override + List get props => [name, date, isCaller]; } diff --git a/lib/ui/pages/devices/devices.dart b/lib/ui/pages/devices/devices.dart index d39eb31d..4d92cd56 100644 --- a/lib/ui/pages/devices/devices.dart +++ b/lib/ui/pages/devices/devices.dart @@ -2,8 +2,7 @@ import 'package:auto_route/auto_route.dart'; import 'package:cubit_form/cubit_form.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:selfprivacy/logic/common_enum/common_enum.dart'; -import 'package:selfprivacy/logic/cubit/devices/devices_cubit.dart'; +import 'package:selfprivacy/logic/bloc/devices/devices_bloc.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/logic/models/json/api_token.dart'; import 'package:selfprivacy/ui/components/info_box/info_box.dart'; @@ -22,12 +21,11 @@ class DevicesScreen extends StatefulWidget { class _DevicesScreenState extends State { @override Widget build(final BuildContext context) { - final ApiDevicesState devicesStatus = - context.watch().state; + final DevicesState devicesStatus = context.watch().state; return RefreshIndicator( onRefresh: () async { - await context.read().refresh(); + await context.read().refresh(); }, child: BrandHeroScreen( heroTitle: 'devices.main_screen.header'.tr(), @@ -35,13 +33,13 @@ class _DevicesScreenState extends State { hasBackButton: true, hasFlashButton: false, children: [ - if (devicesStatus.status == LoadingStatus.uninitialized) ...[ + if (devicesStatus is DevicesInitial) ...[ const Center( heightFactor: 8, child: CircularProgressIndicator(), ), ], - if (devicesStatus.status != LoadingStatus.uninitialized) ...[ + if (devicesStatus is! DevicesInitial) ...[ _DevicesInfo( devicesStatus: devicesStatus, ), @@ -70,7 +68,7 @@ class _DevicesInfo extends StatelessWidget { required this.devicesStatus, }); - final ApiDevicesState devicesStatus; + final DevicesState devicesStatus; @override Widget build(final BuildContext context) => Column( @@ -82,7 +80,9 @@ class _DevicesInfo extends StatelessWidget { color: Theme.of(context).colorScheme.secondary, ), ), - _DeviceTile(device: devicesStatus.thisDevice), + _DeviceTile( + device: devicesStatus.thisDevice, + ), const Divider(height: 1), const SizedBox(height: 16), Text( @@ -91,14 +91,18 @@ class _DevicesInfo extends StatelessWidget { color: Theme.of(context).colorScheme.secondary, ), ), - if (devicesStatus.status == LoadingStatus.refreshing) ...[ + if (devicesStatus is DevicesDeleting) ...[ const Center( heightFactor: 4, child: CircularProgressIndicator(), ), ], - ...devicesStatus.otherDevices - .map((final device) => _DeviceTile(device: device)), + if (devicesStatus is! DevicesDeleting) + ...devicesStatus.otherDevices.map( + (final device) => _DeviceTile( + device: device, + ), + ), ], ); } @@ -110,7 +114,7 @@ class _DeviceTile extends StatelessWidget { @override Widget build(final BuildContext context) => ListTile( - contentPadding: const EdgeInsets.symmetric(horizontal: 0, vertical: 4), + contentPadding: EdgeInsets.zero, title: Text(device.name), subtitle: Text( 'devices.main_screen.access_granted_on' @@ -161,7 +165,7 @@ class _DeviceTile extends StatelessWidget { TextButton( child: Text('devices.revoke_device_alert.yes'.tr()), onPressed: () { - context.read().deleteDevice(device); + context.read().add(DeleteDevice(device)); Navigator.of(context).pop(); }, ), diff --git a/lib/ui/pages/devices/new_device.dart b/lib/ui/pages/devices/new_device.dart index 9a64fa72..6f343b22 100644 --- a/lib/ui/pages/devices/new_device.dart +++ b/lib/ui/pages/devices/new_device.dart @@ -1,7 +1,7 @@ import 'package:cubit_form/cubit_form.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:selfprivacy/logic/cubit/devices/devices_cubit.dart'; +import 'package:selfprivacy/logic/bloc/devices/devices_bloc.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/ui/components/buttons/brand_button.dart'; import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; @@ -17,7 +17,7 @@ class NewDeviceScreen extends StatelessWidget { hasFlashButton: false, children: [ FutureBuilder( - future: context.read().getNewDeviceKey(), + future: context.read().getNewDeviceKey(), builder: ( final BuildContext context, final AsyncSnapshot snapshot, diff --git a/lib/ui/pages/recovery_key/recovery_key.dart b/lib/ui/pages/recovery_key/recovery_key.dart index a8a47593..b211bad7 100644 --- a/lib/ui/pages/recovery_key/recovery_key.dart +++ b/lib/ui/pages/recovery_key/recovery_key.dart @@ -2,6 +2,7 @@ import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/logic/bloc/recovery_key/recovery_key_bloc.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/ui/components/buttons/brand_button.dart'; @@ -41,7 +42,6 @@ class _RecoveryKeyPageState extends State { break; case RecoveryKeyInitial(): case RecoveryKeyError(): - case RecoveryKeyCreating(): subtitle = 'recovery_key.key_connection_error'.tr(); widgets = [ const Icon(Icons.sentiment_dissatisfied_outlined), @@ -234,24 +234,34 @@ class _RecoveryKeyConfigurationState extends State { setState(() { _isLoading = true; }); - context.read().add( - CreateNewRecoveryKey( - expirationDate: _isExpirationToggled ? _selectedDate : null, - numberOfUses: - _isAmountToggled ? int.tryParse(_amountController.text) : null, - ), - ); - if (!mounted) { + try { + final String token = + await context.read().generateRecoveryKey( + numberOfUses: _isAmountToggled + ? int.tryParse(_amountController.text) + : null, + expirationDate: _isExpirationToggled ? _selectedDate : null, + ); + if (!mounted) { + return; + } + setState(() { + _isLoading = false; + }); + await Navigator.of(context).push( + materialRoute( + RecoveryKeyReceiving(recoveryKey: token), + ), + ); + } on GenerationError catch (e) { + setState(() { + _isLoading = false; + }); + getIt().showSnackBar( + 'recovery_key.generation_error'.tr(args: [e.message]), + ); return; } - setState(() { - _isLoading = false; - }); - await Navigator.of(context).push( - materialRoute( - const RecoveryKeyReceiving(), - ), - ); } void _updateErrorStatuses() { diff --git a/lib/ui/pages/recovery_key/recovery_key_receiving.dart b/lib/ui/pages/recovery_key/recovery_key_receiving.dart index 8167ac1e..e7983bb3 100644 --- a/lib/ui/pages/recovery_key/recovery_key_receiving.dart +++ b/lib/ui/pages/recovery_key/recovery_key_receiving.dart @@ -1,36 +1,23 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:selfprivacy/logic/bloc/recovery_key/recovery_key_bloc.dart'; import 'package:selfprivacy/ui/components/buttons/brand_button.dart'; import 'package:selfprivacy/ui/components/info_box/info_box.dart'; import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; class RecoveryKeyReceiving extends StatelessWidget { - const RecoveryKeyReceiving({super.key}); + const RecoveryKeyReceiving({required this.recoveryKey, super.key}); + + final String recoveryKey; @override - Widget build(final BuildContext context) { - final recoveryKeyState = context.watch().state; - - final String? recoveryKey = recoveryKeyState is RecoveryKeyCreating - ? recoveryKeyState.recoveryKey - : null; - - final String? error = - recoveryKeyState is RecoveryKeyCreating ? recoveryKeyState.error : null; - - return BrandHeroScreen( - heroTitle: 'recovery_key.key_main_header'.tr(), - heroSubtitle: 'recovery_key.key_receiving_description'.tr(), - hasBackButton: true, - hasFlashButton: false, - children: [ - const Divider(), - const SizedBox(height: 16), - if (recoveryKey == null && error == null) - const Center(child: CircularProgressIndicator()), - if (recoveryKey != null) + Widget build(final BuildContext context) => BrandHeroScreen( + heroTitle: 'recovery_key.key_main_header'.tr(), + heroSubtitle: 'recovery_key.key_receiving_description'.tr(), + hasBackButton: false, + hasFlashButton: false, + children: [ + const Divider(), + const SizedBox(height: 16), Text( recoveryKey, style: Theme.of(context).textTheme.bodyLarge!.copyWith( @@ -39,31 +26,19 @@ class RecoveryKeyReceiving extends StatelessWidget { ), textAlign: TextAlign.center, ), - if (error != null) - Text( - 'recovery_key.generation_error'.tr(args: [error]), - style: Theme.of(context).textTheme.bodyLarge!.copyWith( - fontSize: 24, - fontFamily: 'RobotoMono', - color: Theme.of(context).colorScheme.error, - ), - textAlign: TextAlign.center, + const SizedBox(height: 16), + const Divider(), + const SizedBox(height: 16), + InfoBox( + text: 'recovery_key.key_receiving_info'.tr(), ), - const SizedBox(height: 16), - const Divider(), - const SizedBox(height: 16), - InfoBox( - text: 'recovery_key.key_receiving_info'.tr(), - ), - const SizedBox(height: 16), - BrandButton.filled( - child: Text('recovery_key.key_receiving_done'.tr()), - onPressed: () { - context.read().add(const ConsumedNewRecoveryKey()); - Navigator.of(context).popUntil((final route) => route.isFirst); - }, - ), - ], - ); - } + const SizedBox(height: 16), + BrandButton.filled( + child: Text('recovery_key.key_receiving_done'.tr()), + onPressed: () { + Navigator.of(context).popUntil((final route) => route.isFirst); + }, + ), + ], + ); } From e5f00f87708192989e0010ed34fd8bbcdf24adbf Mon Sep 17 00:00:00 2001 From: Inex Code Date: Fri, 9 Feb 2024 16:54:04 +0300 Subject: [PATCH 23/34] refactor: Make sure that blocs use sealed classes --- lib/logic/bloc/services/services_event.dart | 2 +- lib/logic/bloc/services/services_state.dart | 2 +- lib/logic/bloc/volumes/volumes_event.dart | 2 +- lib/logic/bloc/volumes/volumes_state.dart | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/logic/bloc/services/services_event.dart b/lib/logic/bloc/services/services_event.dart index c8239152..8e45a395 100644 --- a/lib/logic/bloc/services/services_event.dart +++ b/lib/logic/bloc/services/services_event.dart @@ -1,6 +1,6 @@ part of 'services_bloc.dart'; -abstract class ServicesEvent extends Equatable { +sealed class ServicesEvent extends Equatable { const ServicesEvent(); } diff --git a/lib/logic/bloc/services/services_state.dart b/lib/logic/bloc/services/services_state.dart index 05d6bc89..9c1e3f32 100644 --- a/lib/logic/bloc/services/services_state.dart +++ b/lib/logic/bloc/services/services_state.dart @@ -1,6 +1,6 @@ part of 'services_bloc.dart'; -abstract class ServicesState extends Equatable { +sealed class ServicesState extends Equatable { ServicesState({final List lockedServices = const []}) : _lockedServices = lockedServices.where((final lock) => lock.isLocked).toList(); diff --git a/lib/logic/bloc/volumes/volumes_event.dart b/lib/logic/bloc/volumes/volumes_event.dart index a4346ce5..2531b08f 100644 --- a/lib/logic/bloc/volumes/volumes_event.dart +++ b/lib/logic/bloc/volumes/volumes_event.dart @@ -1,6 +1,6 @@ part of 'volumes_bloc.dart'; -abstract class VolumesEvent extends Equatable { +sealed class VolumesEvent extends Equatable { const VolumesEvent(); } diff --git a/lib/logic/bloc/volumes/volumes_state.dart b/lib/logic/bloc/volumes/volumes_state.dart index 6fc4cb45..04c745e4 100644 --- a/lib/logic/bloc/volumes/volumes_state.dart +++ b/lib/logic/bloc/volumes/volumes_state.dart @@ -1,6 +1,6 @@ part of 'volumes_bloc.dart'; -abstract class VolumesState extends Equatable { +sealed class VolumesState extends Equatable { const VolumesState({ required this.diskStatus, required final serverVolumesHashCode, From 455b1ed7f9c7f9d1967166359eca213952799c1e Mon Sep 17 00:00:00 2001 From: Inex Code Date: Fri, 9 Feb 2024 18:01:05 +0300 Subject: [PATCH 24/34] refactor: Replace UsersCubit with UsersBloc --- lib/config/bloc_config.dart | 9 +- .../connection_status_bloc.dart | 19 +- lib/logic/bloc/devices/devices_state.dart | 2 +- lib/logic/bloc/users/users_bloc.dart | 105 ++++++++++ lib/logic/bloc/users/users_event.dart | 30 +++ .../{cubit => bloc}/users/users_state.dart | 47 +++-- .../cubit/client_jobs/client_jobs_cubit.dart | 3 - .../forms/factories/field_cubit_factory.dart | 4 +- lib/logic/cubit/users/users_cubit.dart | 183 ------------------ .../get_it/api_connection_repository.dart | 118 +++++++++++ lib/logic/models/job.dart | 12 +- lib/ui/pages/users/new_user.dart | 2 +- lib/ui/pages/users/user_details.dart | 2 +- lib/ui/pages/users/users.dart | 12 +- 14 files changed, 320 insertions(+), 228 deletions(-) create mode 100644 lib/logic/bloc/users/users_bloc.dart create mode 100644 lib/logic/bloc/users/users_event.dart rename lib/logic/{cubit => bloc}/users/users_state.dart (63%) delete mode 100644 lib/logic/cubit/users/users_cubit.dart diff --git a/lib/config/bloc_config.dart b/lib/config/bloc_config.dart index 881159a6..4f5baeed 100644 --- a/lib/config/bloc_config.dart +++ b/lib/config/bloc_config.dart @@ -6,6 +6,7 @@ import 'package:selfprivacy/logic/bloc/devices/devices_bloc.dart'; import 'package:selfprivacy/logic/bloc/recovery_key/recovery_key_bloc.dart'; import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart'; import 'package:selfprivacy/logic/bloc/services/services_bloc.dart'; +import 'package:selfprivacy/logic/bloc/users/users_bloc.dart'; import 'package:selfprivacy/logic/bloc/volumes/volumes_bloc.dart'; import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart'; import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart'; @@ -13,7 +14,6 @@ import 'package:selfprivacy/logic/cubit/dns_records/dns_records_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_detailed_info/server_detailed_info_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/logic/cubit/support_system/support_system_cubit.dart'; -import 'package:selfprivacy/logic/cubit/users/users_cubit.dart'; class BlocAndProviderConfig extends StatefulWidget { const BlocAndProviderConfig({super.key, this.child}); @@ -27,7 +27,7 @@ class BlocAndProviderConfig extends StatefulWidget { class BlocAndProviderConfigState extends State { late final ServerInstallationCubit serverInstallationCubit; late final SupportSystemCubit supportSystemCubit; - late final UsersCubit usersCubit; + late final UsersBloc usersBloc; late final ServicesBloc servicesBloc; late final BackupsBloc backupsBloc; late final DnsRecordsCubit dnsRecordsCubit; @@ -43,7 +43,7 @@ class BlocAndProviderConfigState extends State { super.initState(); serverInstallationCubit = ServerInstallationCubit()..load(); supportSystemCubit = SupportSystemCubit(); - usersCubit = UsersCubit(); + usersBloc = UsersBloc(); servicesBloc = ServicesBloc(); backupsBloc = BackupsBloc(); dnsRecordsCubit = DnsRecordsCubit(); @@ -77,7 +77,7 @@ class BlocAndProviderConfigState extends State { lazy: false, ), BlocProvider( - create: (final _) => usersCubit, + create: (final _) => usersBloc, lazy: false, ), BlocProvider( @@ -105,7 +105,6 @@ class BlocAndProviderConfigState extends State { BlocProvider(create: (final _) => volumesBloc), BlocProvider( create: (final _) => JobsCubit( - usersCubit: usersCubit, servicesBloc: servicesBloc, ), ), diff --git a/lib/logic/bloc/connection_status/connection_status_bloc.dart b/lib/logic/bloc/connection_status/connection_status_bloc.dart index f44288ed..868f05d0 100644 --- a/lib/logic/bloc/connection_status/connection_status_bloc.dart +++ b/lib/logic/bloc/connection_status/connection_status_bloc.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:selfprivacy/config/get_it_config.dart'; @@ -13,16 +15,25 @@ class ConnectionStatusBloc connectionStatus: ConnectionStatus.nonexistent, ), ) { + on((final event, final emit) { + emit(ConnectionStatusState(connectionStatus: event.connectionStatus)); + }); final apiConnectionRepository = getIt(); - apiConnectionRepository.connectionStatusStream.listen( + _apiConnectionStatusSubscription = + apiConnectionRepository.connectionStatusStream.listen( (final ConnectionStatus connectionStatus) { add( ConnectionStatusChanged(connectionStatus), ); }, ); - on((final event, final emit) { - emit(ConnectionStatusState(connectionStatus: event.connectionStatus)); - }); + } + + StreamSubscription? _apiConnectionStatusSubscription; + + @override + Future close() { + _apiConnectionStatusSubscription?.cancel(); + return super.close(); } } diff --git a/lib/logic/bloc/devices/devices_state.dart b/lib/logic/bloc/devices/devices_state.dart index 85e932df..b31bdf19 100644 --- a/lib/logic/bloc/devices/devices_state.dart +++ b/lib/logic/bloc/devices/devices_state.dart @@ -3,7 +3,7 @@ part of 'devices_bloc.dart'; sealed class DevicesState extends Equatable { DevicesState({ required final List devices, - }) : _hashCode = devices.hashCode; + }) : _hashCode = Object.hashAll(devices); final int _hashCode; diff --git a/lib/logic/bloc/users/users_bloc.dart b/lib/logic/bloc/users/users_bloc.dart new file mode 100644 index 00000000..32ca7a26 --- /dev/null +++ b/lib/logic/bloc/users/users_bloc.dart @@ -0,0 +1,105 @@ +import 'dart:async'; + +import 'package:bloc_concurrency/bloc_concurrency.dart'; +import 'package:equatable/equatable.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:selfprivacy/config/get_it_config.dart'; +import 'package:selfprivacy/logic/models/hive/user.dart'; + +part 'users_event.dart'; +part 'users_state.dart'; + +class UsersBloc extends Bloc { + UsersBloc() : super(UsersInitial()) { + on( + _updateList, + transformer: sequential(), + ); + on( + _reload, + transformer: droppable(), + ); + on( + _mapConnectionStatusChangedToState, + transformer: sequential(), + ); + + final apiConnectionRepository = getIt(); + _apiConnectionStatusSubscription = + apiConnectionRepository.connectionStatusStream.listen( + (final ConnectionStatus connectionStatus) { + add( + UsersConnectionStatusChanged(connectionStatus), + ); + }, + ); + _apiDataSubscription = apiConnectionRepository.dataStream.listen( + (final ApiData apiData) { + add( + UsersListChanged(apiData.users.data ?? []), + ); + }, + ); + } + + Future _updateList( + final UsersListChanged event, + final Emitter emit, + ) async { + if (event.users.isEmpty) { + emit(UsersInitial()); + return; + } + final newState = UsersLoaded( + users: event.users, + ); + emit(newState); + } + + Future refresh() async { + getIt().apiData.users.invalidate(); + await getIt().reload(null); + } + + Future _reload( + final UsersListRefresh event, + final Emitter emit, + ) async { + emit(UsersRefreshing(users: state.users)); + await refresh(); + } + + Future _mapConnectionStatusChangedToState( + final UsersConnectionStatusChanged event, + final Emitter emit, + ) async { + switch (event.connectionStatus) { + case ConnectionStatus.nonexistent: + emit(UsersInitial()); + break; + case ConnectionStatus.connected: + if (state is! UsersLoaded) { + emit(UsersRefreshing(users: state.users)); + } + case ConnectionStatus.reconnecting: + case ConnectionStatus.offline: + case ConnectionStatus.unauthorized: + break; + } + } + + StreamSubscription? _apiDataSubscription; + StreamSubscription? _apiConnectionStatusSubscription; + + @override + void onChange(final Change change) { + super.onChange(change); + } + + @override + Future close() { + _apiDataSubscription?.cancel(); + _apiConnectionStatusSubscription?.cancel(); + return super.close(); + } +} diff --git a/lib/logic/bloc/users/users_event.dart b/lib/logic/bloc/users/users_event.dart new file mode 100644 index 00000000..7fdd1431 --- /dev/null +++ b/lib/logic/bloc/users/users_event.dart @@ -0,0 +1,30 @@ +part of 'users_bloc.dart'; + +sealed class UsersEvent extends Equatable { + const UsersEvent(); +} + +class UsersListChanged extends UsersEvent { + const UsersListChanged(this.users); + + final List users; + + @override + List get props => [users]; +} + +class UsersListRefresh extends UsersEvent { + const UsersListRefresh(); + + @override + List get props => []; +} + +class UsersConnectionStatusChanged extends UsersEvent { + const UsersConnectionStatusChanged(this.connectionStatus); + + final ConnectionStatus connectionStatus; + + @override + List get props => [connectionStatus]; +} diff --git a/lib/logic/cubit/users/users_state.dart b/lib/logic/bloc/users/users_state.dart similarity index 63% rename from lib/logic/cubit/users/users_state.dart rename to lib/logic/bloc/users/users_state.dart index 4e2ed42e..69c4917b 100644 --- a/lib/logic/cubit/users/users_state.dart +++ b/lib/logic/bloc/users/users_state.dart @@ -1,10 +1,14 @@ -part of 'users_cubit.dart'; +part of 'users_bloc.dart'; -class UsersState extends ServerInstallationDependendState { - const UsersState(this.users, this.isLoading); +sealed class UsersState extends Equatable { + UsersState({ + required final List users, + }) : _hashCode = Object.hashAll(users); - final List users; - final bool isLoading; + final int _hashCode; + + List get users => + getIt().apiData.users.data ?? const []; User get rootUser => users.firstWhere((final user) => user.type == UserType.root); @@ -15,9 +19,6 @@ class UsersState extends ServerInstallationDependendState { List get normalUsers => users.where((final user) => user.type == UserType.normal).toList(); - @override - List get props => [users, isLoading]; - /// Makes a copy of existing users list, but places 'primary' /// to the beginning and sorts the rest alphabetically /// @@ -44,17 +45,29 @@ class UsersState extends ServerInstallationDependendState { return primaryUser == null ? normalUsers : [primaryUser] + normalUsers; } - UsersState copyWith({ - final List? users, - final bool? isLoading, - }) => - UsersState( - users ?? this.users, - isLoading ?? this.isLoading, - ); - bool isLoginRegistered(final String login) => users.any((final User user) => user.login == login); bool get isEmpty => users.isEmpty; } + +class UsersInitial extends UsersState { + UsersInitial() : super(users: const []); + + @override + List get props => [_hashCode]; +} + +class UsersRefreshing extends UsersState { + UsersRefreshing({required super.users}); + + @override + List get props => [_hashCode]; +} + +class UsersLoaded extends UsersState { + UsersLoaded({required super.users}); + + @override + List get props => [_hashCode]; +} diff --git a/lib/logic/cubit/client_jobs/client_jobs_cubit.dart b/lib/logic/cubit/client_jobs/client_jobs_cubit.dart index f9188985..16b87c77 100644 --- a/lib/logic/cubit/client_jobs/client_jobs_cubit.dart +++ b/lib/logic/cubit/client_jobs/client_jobs_cubit.dart @@ -6,7 +6,6 @@ import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart'; import 'package:selfprivacy/logic/bloc/services/services_bloc.dart'; -import 'package:selfprivacy/logic/cubit/users/users_cubit.dart'; import 'package:selfprivacy/logic/models/job.dart'; export 'package:provider/provider.dart'; @@ -15,12 +14,10 @@ part 'client_jobs_state.dart'; class JobsCubit extends Cubit { JobsCubit({ - required this.usersCubit, required this.servicesBloc, }) : super(JobsStateEmpty()); final ServerApi api = ServerApi(); - final UsersCubit usersCubit; final ServicesBloc servicesBloc; void addJob(final ClientJob job) { diff --git a/lib/logic/cubit/forms/factories/field_cubit_factory.dart b/lib/logic/cubit/forms/factories/field_cubit_factory.dart index 42359d1e..0aabc0e0 100644 --- a/lib/logic/cubit/forms/factories/field_cubit_factory.dart +++ b/lib/logic/cubit/forms/factories/field_cubit_factory.dart @@ -1,8 +1,8 @@ import 'package:cubit_form/cubit_form.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:selfprivacy/logic/bloc/users/users_bloc.dart'; import 'package:selfprivacy/logic/cubit/forms/validations/validations.dart'; -import 'package:selfprivacy/logic/cubit/users/users_cubit.dart'; class FieldCubitFactory { FieldCubitFactory(this.context); @@ -27,7 +27,7 @@ class FieldCubitFactory { ), ValidationModel( (final String login) => - context.read().state.isLoginRegistered(login), + context.read().state.isLoginRegistered(login), 'validations.already_exist'.tr(), ), RequiredStringValidation('validations.required'.tr()), diff --git a/lib/logic/cubit/users/users_cubit.dart b/lib/logic/cubit/users/users_cubit.dart deleted file mode 100644 index 30185e60..00000000 --- a/lib/logic/cubit/users/users_cubit.dart +++ /dev/null @@ -1,183 +0,0 @@ -import 'dart:async'; - -import 'package:easy_localization/easy_localization.dart'; -import 'package:hive/hive.dart'; -import 'package:selfprivacy/config/get_it_config.dart'; -import 'package:selfprivacy/config/hive_config.dart'; -import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart'; -import 'package:selfprivacy/logic/cubit/server_connection_dependent/server_connection_dependent_cubit.dart'; -import 'package:selfprivacy/logic/models/hive/user.dart'; - -export 'package:provider/provider.dart'; - -part 'users_state.dart'; - -class UsersCubit extends ServerConnectionDependentCubit { - UsersCubit() - : super( - const UsersState( - [], - false, - ), - ); - Box box = Hive.box(BNames.usersBox); - Box serverInstallationBox = Hive.box(BNames.serverInstallationBox); - - final ServerApi api = ServerApi(); - - @override - Future load() async { - final List loadedUsers = box.values.toList(); - if (loadedUsers.isNotEmpty) { - emit( - UsersState( - loadedUsers, - false, - ), - ); - } - - unawaited(refresh()); - } - - Future refresh() async { - if (getIt().connectionStatus == - ConnectionStatus.nonexistent) { - return; - } - emit(state.copyWith(isLoading: true)); - final List usersFromServer = await api.getAllUsers(); - if (usersFromServer.isNotEmpty) { - emit( - UsersState( - usersFromServer, - false, - ), - ); - // Update the users it the box - await box.clear(); - await box.addAll(usersFromServer); - } else { - getIt() - .showSnackBar('users.could_not_fetch_users'.tr()); - emit(state.copyWith(isLoading: false)); - } - } - - Future createUser(final User user) async { - // If user exists on server, do nothing - if (state.users - .any((final User u) => u.login == user.login && u.isFoundOnServer)) { - return; - } - final String? password = user.password; - if (password == null) { - getIt() - .showSnackBar('users.could_not_create_user'.tr()); - return; - } - // If API returned error, do nothing - final GenericResult result = - await api.createUser(user.login, password); - if (result.data == null) { - getIt() - .showSnackBar(result.message ?? 'users.could_not_create_user'.tr()); - return; - } - - final List loadedUsers = List.from(state.users); - loadedUsers.add(result.data!); - await box.clear(); - await box.addAll(loadedUsers); - emit(state.copyWith(users: loadedUsers)); - } - - Future deleteUser(final User user) async { - // If user is primary or root, don't delete - if (user.type != UserType.normal) { - getIt() - .showSnackBar('users.could_not_delete_user'.tr()); - return; - } - final List loadedUsers = List.from(state.users); - final GenericResult result = await api.deleteUser(user.login); - if (result.success && result.data) { - loadedUsers.removeWhere((final User u) => u.login == user.login); - await box.clear(); - await box.addAll(loadedUsers); - emit(state.copyWith(users: loadedUsers)); - } - - if (!result.success) { - getIt().showSnackBar('jobs.generic_error'.tr()); - } - - if (!result.data) { - getIt() - .showSnackBar(result.message ?? 'jobs.generic_error'.tr()); - } - } - - Future changeUserPassword( - final User user, - final String newPassword, - ) async { - if (user.type == UserType.root) { - getIt() - .showSnackBar('users.could_not_change_password'.tr()); - return; - } - final GenericResult result = - await api.updateUser(user.login, newPassword); - if (result.data == null) { - getIt().showSnackBar( - result.message ?? 'users.could_not_change_password'.tr(), - ); - } - } - - Future addSshKey(final User user, final String publicKey) async { - final GenericResult result = - await api.addSshKey(user.login, publicKey); - if (result.data != null) { - final User updatedUser = result.data!; - final int index = - state.users.indexWhere((final User u) => u.login == user.login); - await box.putAt(index, updatedUser); - emit( - state.copyWith( - users: box.values.toList(), - ), - ); - } else { - getIt() - .showSnackBar(result.message ?? 'users.could_not_add_ssh_key'.tr()); - } - } - - Future deleteSshKey(final User user, final String publicKey) async { - final GenericResult result = - await api.removeSshKey(user.login, publicKey); - if (result.data != null) { - final User updatedUser = result.data!; - final int index = - state.users.indexWhere((final User u) => u.login == user.login); - await box.putAt(index, updatedUser); - emit( - state.copyWith( - users: box.values.toList(), - ), - ); - } - } - - @override - void clear() async { - emit( - const UsersState( - [], - false, - ), - ); - } -} diff --git a/lib/logic/get_it/api_connection_repository.dart b/lib/logic/get_it/api_connection_repository.dart index c504e06a..13229442 100644 --- a/lib/logic/get_it/api_connection_repository.dart +++ b/lib/logic/get_it/api_connection_repository.dart @@ -1,5 +1,6 @@ import 'dart:async'; +import 'package:easy_localization/easy_localization.dart'; import 'package:hive/hive.dart'; import 'package:pub_semver/pub_semver.dart'; import 'package:selfprivacy/config/get_it_config.dart'; @@ -8,6 +9,7 @@ import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.da import 'package:selfprivacy/logic/models/backup.dart'; import 'package:selfprivacy/logic/models/hive/server_details.dart'; import 'package:selfprivacy/logic/models/hive/server_domain.dart'; +import 'package:selfprivacy/logic/models/hive/user.dart'; import 'package:selfprivacy/logic/models/json/api_token.dart'; import 'package:selfprivacy/logic/models/json/recovery_token_status.dart'; import 'package:selfprivacy/logic/models/json/server_disk_volume.dart'; @@ -68,6 +70,116 @@ class ApiConnectionRepository { ); } + Future createUser(final User user) async { + final List? loadedUsers = _apiData.users.data; + if (loadedUsers == null) { + return; + } + // If user exists on server, do nothing + if (loadedUsers + .any((final User u) => u.login == user.login && u.isFoundOnServer)) { + return; + } + final String? password = user.password; + if (password == null) { + getIt() + .showSnackBar('users.could_not_create_user'.tr()); + return; + } + // If API returned error, do nothing + final GenericResult result = + await api.createUser(user.login, password); + if (result.data == null) { + getIt() + .showSnackBar(result.message ?? 'users.could_not_create_user'.tr()); + return; + } + + _apiData.users.data?.add(result.data!); + _apiData.users.invalidate(); + } + + Future deleteUser(final User user) async { + final List? loadedUsers = _apiData.users.data; + if (loadedUsers == null) { + return; + } + // If user is primary or root, don't delete + if (user.type != UserType.normal) { + getIt() + .showSnackBar('users.could_not_delete_user'.tr()); + return; + } + final GenericResult result = await api.deleteUser(user.login); + if (result.success && result.data) { + _apiData.users.data?.removeWhere((final User u) => u.login == user.login); + _apiData.users.invalidate(); + } + + if (!result.success || !result.data) { + getIt() + .showSnackBar(result.message ?? 'jobs.generic_error'.tr()); + } + } + + Future changeUserPassword( + final User user, + final String newPassword, + ) async { + if (user.type == UserType.root) { + getIt() + .showSnackBar('users.could_not_change_password'.tr()); + return; + } + final GenericResult result = await api.updateUser( + user.login, + newPassword, + ); + if (result.data == null) { + getIt().showSnackBar( + result.message ?? 'users.could_not_change_password'.tr(), + ); + } + } + + Future addSshKey(final User user, final String publicKey) async { + final List? loadedUsers = _apiData.users.data; + if (loadedUsers == null) { + return; + } + final GenericResult result = + await api.addSshKey(user.login, publicKey); + if (result.data != null) { + final User updatedUser = result.data!; + final int index = + loadedUsers.indexWhere((final User u) => u.login == user.login); + loadedUsers[index] = updatedUser; + _apiData.users.invalidate(); + } else { + getIt() + .showSnackBar(result.message ?? 'users.could_not_add_ssh_key'.tr()); + } + } + + Future deleteSshKey(final User user, final String publicKey) async { + final List? loadedUsers = _apiData.users.data; + if (loadedUsers == null) { + return; + } + final GenericResult result = + await api.removeSshKey(user.login, publicKey); + if (result.data != null) { + final User updatedUser = result.data!; + final int index = + loadedUsers.indexWhere((final User u) => u.login == user.login); + loadedUsers[index] = updatedUser; + _apiData.users.invalidate(); + } else { + getIt() + .showSnackBar(result.message ?? 'jobs.generic_error'.tr()); + } + } + void dispose() { _dataStream.close(); _connectionStatusStream.close(); @@ -106,6 +218,7 @@ class ApiConnectionRepository { _apiData.recoveryKeyStatus.data = await _apiData.recoveryKeyStatus.fetchData(); _apiData.devices.data = await _apiData.devices.fetchData(); + _apiData.users.data = await _apiData.users.fetchData(); _dataStream.add(_apiData); connectionStatus = ConnectionStatus.connected; @@ -149,6 +262,7 @@ class ApiConnectionRepository { .refetchData(version, () => _dataStream.add(_apiData)); await _apiData.devices .refetchData(version, () => _dataStream.add(_apiData)); + await _apiData.users.refetchData(version, () => _dataStream.add(_apiData)); } void emitData() { @@ -188,6 +302,9 @@ class ApiData { ), devices = ApiDataElement>( fetchData: () async => (await api.getApiTokens()).data, + ), + users = ApiDataElement>( + fetchData: () async => api.getAllUsers(), ); ApiDataElement> serverJobs; @@ -198,6 +315,7 @@ class ApiData { ApiDataElement> volumes; ApiDataElement recoveryKeyStatus; ApiDataElement> devices; + ApiDataElement> users; } enum ConnectionStatus { diff --git a/lib/logic/models/job.dart b/lib/logic/models/job.dart index e1dd9354..6e3d2a1d 100644 --- a/lib/logic/models/job.dart +++ b/lib/logic/models/job.dart @@ -1,6 +1,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; +import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart'; import 'package:selfprivacy/logic/models/hive/user.dart'; import 'package:selfprivacy/logic/models/service.dart'; @@ -48,7 +49,7 @@ class CreateUserJob extends ClientJob { @override void execute(final JobsCubit cubit) async { - await cubit.usersCubit.createUser(user); + await getIt().createUser(user); } @override @@ -64,7 +65,8 @@ class ResetUserPasswordJob extends ClientJob { @override void execute(final JobsCubit cubit) async { - await cubit.usersCubit.changeUserPassword(user, user.password!); + await getIt() + .changeUserPassword(user, user.password!); } @override @@ -85,7 +87,7 @@ class DeleteUserJob extends ClientJob { @override void execute(final JobsCubit cubit) async { - await cubit.usersCubit.deleteUser(user); + await getIt().deleteUser(user); } @override @@ -129,7 +131,7 @@ class CreateSSHKeyJob extends ClientJob { @override void execute(final JobsCubit cubit) async { - await cubit.usersCubit.addSshKey(user, publicKey); + await getIt().addSshKey(user, publicKey); } @override @@ -155,7 +157,7 @@ class DeleteSSHKeyJob extends ClientJob { @override void execute(final JobsCubit cubit) async { - await cubit.usersCubit.deleteSshKey(user, publicKey); + await getIt().deleteSshKey(user, publicKey); } @override diff --git a/lib/ui/pages/users/new_user.dart b/lib/ui/pages/users/new_user.dart index 2315bdb1..25ba5f98 100644 --- a/lib/ui/pages/users/new_user.dart +++ b/lib/ui/pages/users/new_user.dart @@ -16,7 +16,7 @@ class NewUserPage extends StatelessWidget { final jobCubit = context.read(); final jobState = jobCubit.state; final users = []; - users.addAll(context.read().state.users); + users.addAll(context.read().state.users); if (jobState is JobsStateWithJobs) { final jobs = jobState.clientJobList; for (final job in jobs) { diff --git a/lib/ui/pages/users/user_details.dart b/lib/ui/pages/users/user_details.dart index 0dd2c6d8..105a8362 100644 --- a/lib/ui/pages/users/user_details.dart +++ b/lib/ui/pages/users/user_details.dart @@ -15,7 +15,7 @@ class UserDetailsPage extends StatelessWidget { final String domainName = UiHelpers.getDomainName(config); - final User user = context.watch().state.users.firstWhere( + final User user = context.watch().state.users.firstWhere( (final User user) => user.login == login, orElse: () => const User( type: UserType.normal, diff --git a/lib/ui/pages/users/users.dart b/lib/ui/pages/users/users.dart index c2fb5214..f7edce9e 100644 --- a/lib/ui/pages/users/users.dart +++ b/lib/ui/pages/users/users.dart @@ -4,12 +4,12 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:selfprivacy/config/brand_theme.dart'; import 'package:selfprivacy/config/get_it_config.dart'; +import 'package:selfprivacy/logic/bloc/users/users_bloc.dart'; import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart'; import 'package:selfprivacy/logic/cubit/forms/factories/field_cubit_factory.dart'; import 'package:selfprivacy/logic/cubit/forms/user/ssh_form_cubit.dart'; import 'package:selfprivacy/logic/cubit/forms/user/user_form_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; -import 'package:selfprivacy/logic/cubit/users/users_cubit.dart'; import 'package:selfprivacy/logic/models/hive/user.dart'; import 'package:selfprivacy/logic/models/job.dart'; import 'package:selfprivacy/ui/components/brand_header/brand_header.dart'; @@ -49,18 +49,18 @@ class UsersPage extends StatelessWidget { iconData: BrandIcons.users, ); } else { - child = BlocBuilder( + child = BlocBuilder( builder: (final BuildContext context, final UsersState state) { final users = state.orderedUsers; if (users.isEmpty) { - if (state.isLoading) { + if (state is UsersRefreshing) { return const Center( child: CircularProgressIndicator(), ); } return RefreshIndicator( onRefresh: () async { - await context.read().refresh(); + await context.read().refresh(); }, child: Container( padding: const EdgeInsets.symmetric(horizontal: 15), @@ -76,7 +76,7 @@ class UsersPage extends StatelessWidget { const SizedBox(height: 18), BrandOutlinedButton( onPressed: () { - context.read().refresh(); + context.read().refresh(); }, title: 'users.refresh_users'.tr(), ), @@ -88,7 +88,7 @@ class UsersPage extends StatelessWidget { } return RefreshIndicator( onRefresh: () async { - await context.read().refresh(); + await context.read().refresh(); }, child: Column( children: [ From 9a1f47711c6d4ce88fe7699ebcda02dfdb3a1f6b Mon Sep 17 00:00:00 2001 From: Inex Code Date: Mon, 12 Feb 2024 20:20:30 +0300 Subject: [PATCH 25/34] chore: Update GraphQL schema with experimental system rebuild tracking --- .../graphql_maps/schema/schema.graphql | 20 +- .../graphql_maps/schema/schema.graphql.dart | 130 +++++++ .../graphql_maps/schema/server_api.graphql | 6 + .../schema/server_api.graphql.dart | 317 +++++++++++++++++- 4 files changed, 466 insertions(+), 7 deletions(-) diff --git a/lib/logic/api_maps/graphql_maps/schema/schema.graphql b/lib/logic/api_maps/graphql_maps/schema/schema.graphql index d36f6a0f..66627055 100644 --- a/lib/logic/api_maps/graphql_maps/schema/schema.graphql +++ b/lib/logic/api_maps/graphql_maps/schema/schema.graphql @@ -150,9 +150,9 @@ type DnsRecord { recordType: String! name: String! content: String! - displayName: String! ttl: Int! priority: Int + displayName: String! } type GenericBackupConfigReturn implements MutationReturnInterface { @@ -272,6 +272,19 @@ enum RestoreStrategy { DOWNLOAD_VERIFY_OVERWRITE } +input SSHSettingsInput { + enable: Boolean! + passwordAuthentication: Boolean! +} + +type SSHSettingsMutationReturn implements MutationReturnInterface { + success: Boolean! + message: String! + code: Int! + enable: Boolean! + passwordAuthentication: Boolean! +} + enum ServerProvider { HETZNER DIGITALOCEAN @@ -424,9 +437,10 @@ type SystemInfo { type SystemMutations { changeTimezone(timezone: String!): TimezoneMutationReturn! changeAutoUpgradeSettings(settings: AutoUpgradeSettingsInput!): AutoUpgradeSettingsMutationReturn! - runSystemRebuild: GenericMutationReturn! + changeSshSettings(settings: SSHSettingsInput!): SSHSettingsMutationReturn! + runSystemRebuild: GenericJobMutationReturn! runSystemRollback: GenericMutationReturn! - runSystemUpgrade: GenericMutationReturn! + runSystemUpgrade: GenericJobMutationReturn! rebootSystem: GenericMutationReturn! pullRepositoryChanges: GenericMutationReturn! } diff --git a/lib/logic/api_maps/graphql_maps/schema/schema.graphql.dart b/lib/logic/api_maps/graphql_maps/schema/schema.graphql.dart index c33b740b..d9304efb 100644 --- a/lib/logic/api_maps/graphql_maps/schema/schema.graphql.dart +++ b/lib/logic/api_maps/graphql_maps/schema/schema.graphql.dart @@ -982,6 +982,135 @@ class _CopyWithStubImpl$Input$RecoveryKeyLimitsInput _res; } +class Input$SSHSettingsInput { + factory Input$SSHSettingsInput({ + required bool enable, + required bool passwordAuthentication, + }) => + Input$SSHSettingsInput._({ + r'enable': enable, + r'passwordAuthentication': passwordAuthentication, + }); + + Input$SSHSettingsInput._(this._$data); + + factory Input$SSHSettingsInput.fromJson(Map data) { + final result$data = {}; + final l$enable = data['enable']; + result$data['enable'] = (l$enable as bool); + final l$passwordAuthentication = data['passwordAuthentication']; + result$data['passwordAuthentication'] = (l$passwordAuthentication as bool); + return Input$SSHSettingsInput._(result$data); + } + + Map _$data; + + bool get enable => (_$data['enable'] as bool); + + bool get passwordAuthentication => (_$data['passwordAuthentication'] as bool); + + Map toJson() { + final result$data = {}; + final l$enable = enable; + result$data['enable'] = l$enable; + final l$passwordAuthentication = passwordAuthentication; + result$data['passwordAuthentication'] = l$passwordAuthentication; + return result$data; + } + + CopyWith$Input$SSHSettingsInput get copyWith => + CopyWith$Input$SSHSettingsInput( + this, + (i) => i, + ); + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (!(other is Input$SSHSettingsInput) || + runtimeType != other.runtimeType) { + return false; + } + final l$enable = enable; + final lOther$enable = other.enable; + if (l$enable != lOther$enable) { + return false; + } + final l$passwordAuthentication = passwordAuthentication; + final lOther$passwordAuthentication = other.passwordAuthentication; + if (l$passwordAuthentication != lOther$passwordAuthentication) { + return false; + } + return true; + } + + @override + int get hashCode { + final l$enable = enable; + final l$passwordAuthentication = passwordAuthentication; + return Object.hashAll([ + l$enable, + l$passwordAuthentication, + ]); + } +} + +abstract class CopyWith$Input$SSHSettingsInput { + factory CopyWith$Input$SSHSettingsInput( + Input$SSHSettingsInput instance, + TRes Function(Input$SSHSettingsInput) then, + ) = _CopyWithImpl$Input$SSHSettingsInput; + + factory CopyWith$Input$SSHSettingsInput.stub(TRes res) = + _CopyWithStubImpl$Input$SSHSettingsInput; + + TRes call({ + bool? enable, + bool? passwordAuthentication, + }); +} + +class _CopyWithImpl$Input$SSHSettingsInput + implements CopyWith$Input$SSHSettingsInput { + _CopyWithImpl$Input$SSHSettingsInput( + this._instance, + this._then, + ); + + final Input$SSHSettingsInput _instance; + + final TRes Function(Input$SSHSettingsInput) _then; + + static const _undefined = {}; + + TRes call({ + Object? enable = _undefined, + Object? passwordAuthentication = _undefined, + }) => + _then(Input$SSHSettingsInput._({ + ..._instance._$data, + if (enable != _undefined && enable != null) 'enable': (enable as bool), + if (passwordAuthentication != _undefined && + passwordAuthentication != null) + 'passwordAuthentication': (passwordAuthentication as bool), + })); +} + +class _CopyWithStubImpl$Input$SSHSettingsInput + implements CopyWith$Input$SSHSettingsInput { + _CopyWithStubImpl$Input$SSHSettingsInput(this._res); + + TRes _res; + + call({ + bool? enable, + bool? passwordAuthentication, + }) => + _res; +} + class Input$SshMutationInput { factory Input$SshMutationInput({ required String username, @@ -1928,6 +2057,7 @@ const possibleTypesMap = >{ 'GenericBackupConfigReturn', 'GenericJobMutationReturn', 'GenericMutationReturn', + 'SSHSettingsMutationReturn', 'ServiceJobMutationReturn', 'ServiceMutationReturn', 'TimezoneMutationReturn', diff --git a/lib/logic/api_maps/graphql_maps/schema/server_api.graphql b/lib/logic/api_maps/graphql_maps/schema/server_api.graphql index 05ecba97..e66d8143 100644 --- a/lib/logic/api_maps/graphql_maps/schema/server_api.graphql +++ b/lib/logic/api_maps/graphql_maps/schema/server_api.graphql @@ -45,6 +45,9 @@ mutation RunSystemRebuild { system { runSystemRebuild { ...basicMutationReturnFields + job { + ...basicApiJobsFields + } } } } @@ -61,6 +64,9 @@ mutation RunSystemUpgrade { system { runSystemUpgrade { ...basicMutationReturnFields + job { + ...basicApiJobsFields + } } } } diff --git a/lib/logic/api_maps/graphql_maps/schema/server_api.graphql.dart b/lib/logic/api_maps/graphql_maps/schema/server_api.graphql.dart index 4092829e..5241a6c9 100644 --- a/lib/logic/api_maps/graphql_maps/schema/server_api.graphql.dart +++ b/lib/logic/api_maps/graphql_maps/schema/server_api.graphql.dart @@ -39,6 +39,10 @@ class Fragment$basicMutationReturnFields { return Fragment$basicMutationReturnFields$$GenericMutationReturn .fromJson(json); + case "SSHSettingsMutationReturn": + return Fragment$basicMutationReturnFields$$SSHSettingsMutationReturn + .fromJson(json); + case "ServiceJobMutationReturn": return Fragment$basicMutationReturnFields$$ServiceJobMutationReturn .fromJson(json); @@ -164,6 +168,9 @@ extension UtilityExtension$Fragment$basicMutationReturnFields required _T Function( Fragment$basicMutationReturnFields$$GenericMutationReturn) genericMutationReturn, + required _T Function( + Fragment$basicMutationReturnFields$$SSHSettingsMutationReturn) + sSHSettingsMutationReturn, required _T Function( Fragment$basicMutationReturnFields$$ServiceJobMutationReturn) serviceJobMutationReturn, @@ -202,6 +209,10 @@ extension UtilityExtension$Fragment$basicMutationReturnFields return genericMutationReturn( this as Fragment$basicMutationReturnFields$$GenericMutationReturn); + case "SSHSettingsMutationReturn": + return sSHSettingsMutationReturn(this + as Fragment$basicMutationReturnFields$$SSHSettingsMutationReturn); + case "ServiceJobMutationReturn": return serviceJobMutationReturn(this as Fragment$basicMutationReturnFields$$ServiceJobMutationReturn); @@ -238,6 +249,8 @@ extension UtilityExtension$Fragment$basicMutationReturnFields genericJobMutationReturn, _T Function(Fragment$basicMutationReturnFields$$GenericMutationReturn)? genericMutationReturn, + _T Function(Fragment$basicMutationReturnFields$$SSHSettingsMutationReturn)? + sSHSettingsMutationReturn, _T Function(Fragment$basicMutationReturnFields$$ServiceJobMutationReturn)? serviceJobMutationReturn, _T Function(Fragment$basicMutationReturnFields$$ServiceMutationReturn)? @@ -297,6 +310,14 @@ extension UtilityExtension$Fragment$basicMutationReturnFields return orElse(); } + case "SSHSettingsMutationReturn": + if (sSHSettingsMutationReturn != null) { + return sSHSettingsMutationReturn(this + as Fragment$basicMutationReturnFields$$SSHSettingsMutationReturn); + } else { + return orElse(); + } + case "ServiceJobMutationReturn": if (serviceJobMutationReturn != null) { return serviceJobMutationReturn(this @@ -1568,6 +1589,186 @@ class _CopyWithStubImpl$Fragment$basicMutationReturnFields$$GenericMutationRetur _res; } +class Fragment$basicMutationReturnFields$$SSHSettingsMutationReturn + implements Fragment$basicMutationReturnFields { + Fragment$basicMutationReturnFields$$SSHSettingsMutationReturn({ + required this.code, + required this.message, + required this.success, + this.$__typename = 'SSHSettingsMutationReturn', + }); + + factory Fragment$basicMutationReturnFields$$SSHSettingsMutationReturn.fromJson( + Map json) { + final l$code = json['code']; + final l$message = json['message']; + final l$success = json['success']; + final l$$__typename = json['__typename']; + return Fragment$basicMutationReturnFields$$SSHSettingsMutationReturn( + code: (l$code as int), + message: (l$message as String), + success: (l$success as bool), + $__typename: (l$$__typename as String), + ); + } + + final int code; + + final String message; + + final bool success; + + final String $__typename; + + Map toJson() { + final _resultData = {}; + final l$code = code; + _resultData['code'] = l$code; + final l$message = message; + _resultData['message'] = l$message; + final l$success = success; + _resultData['success'] = l$success; + final l$$__typename = $__typename; + _resultData['__typename'] = l$$__typename; + return _resultData; + } + + @override + int get hashCode { + final l$code = code; + final l$message = message; + final l$success = success; + final l$$__typename = $__typename; + return Object.hashAll([ + l$code, + l$message, + l$success, + l$$__typename, + ]); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (!(other + is Fragment$basicMutationReturnFields$$SSHSettingsMutationReturn) || + runtimeType != other.runtimeType) { + return false; + } + final l$code = code; + final lOther$code = other.code; + if (l$code != lOther$code) { + return false; + } + final l$message = message; + final lOther$message = other.message; + if (l$message != lOther$message) { + return false; + } + final l$success = success; + final lOther$success = other.success; + if (l$success != lOther$success) { + return false; + } + final l$$__typename = $__typename; + final lOther$$__typename = other.$__typename; + if (l$$__typename != lOther$$__typename) { + return false; + } + return true; + } +} + +extension UtilityExtension$Fragment$basicMutationReturnFields$$SSHSettingsMutationReturn + on Fragment$basicMutationReturnFields$$SSHSettingsMutationReturn { + CopyWith$Fragment$basicMutationReturnFields$$SSHSettingsMutationReturn< + Fragment$basicMutationReturnFields$$SSHSettingsMutationReturn> + get copyWith => + CopyWith$Fragment$basicMutationReturnFields$$SSHSettingsMutationReturn( + this, + (i) => i, + ); +} + +abstract class CopyWith$Fragment$basicMutationReturnFields$$SSHSettingsMutationReturn< + TRes> { + factory CopyWith$Fragment$basicMutationReturnFields$$SSHSettingsMutationReturn( + Fragment$basicMutationReturnFields$$SSHSettingsMutationReturn instance, + TRes Function(Fragment$basicMutationReturnFields$$SSHSettingsMutationReturn) + then, + ) = _CopyWithImpl$Fragment$basicMutationReturnFields$$SSHSettingsMutationReturn; + + factory CopyWith$Fragment$basicMutationReturnFields$$SSHSettingsMutationReturn.stub( + TRes res) = + _CopyWithStubImpl$Fragment$basicMutationReturnFields$$SSHSettingsMutationReturn; + + TRes call({ + int? code, + String? message, + bool? success, + String? $__typename, + }); +} + +class _CopyWithImpl$Fragment$basicMutationReturnFields$$SSHSettingsMutationReturn< + TRes> + implements + CopyWith$Fragment$basicMutationReturnFields$$SSHSettingsMutationReturn< + TRes> { + _CopyWithImpl$Fragment$basicMutationReturnFields$$SSHSettingsMutationReturn( + this._instance, + this._then, + ); + + final Fragment$basicMutationReturnFields$$SSHSettingsMutationReturn _instance; + + final TRes Function( + Fragment$basicMutationReturnFields$$SSHSettingsMutationReturn) _then; + + static const _undefined = {}; + + TRes call({ + Object? code = _undefined, + Object? message = _undefined, + Object? success = _undefined, + Object? $__typename = _undefined, + }) => + _then(Fragment$basicMutationReturnFields$$SSHSettingsMutationReturn( + code: + code == _undefined || code == null ? _instance.code : (code as int), + message: message == _undefined || message == null + ? _instance.message + : (message as String), + success: success == _undefined || success == null + ? _instance.success + : (success as bool), + $__typename: $__typename == _undefined || $__typename == null + ? _instance.$__typename + : ($__typename as String), + )); +} + +class _CopyWithStubImpl$Fragment$basicMutationReturnFields$$SSHSettingsMutationReturn< + TRes> + implements + CopyWith$Fragment$basicMutationReturnFields$$SSHSettingsMutationReturn< + TRes> { + _CopyWithStubImpl$Fragment$basicMutationReturnFields$$SSHSettingsMutationReturn( + this._res); + + TRes _res; + + call({ + int? code, + String? message, + bool? success, + String? $__typename, + }) => + _res; +} + class Fragment$basicMutationReturnFields$$ServiceJobMutationReturn implements Fragment$basicMutationReturnFields { Fragment$basicMutationReturnFields$$ServiceJobMutationReturn({ @@ -4480,6 +4681,25 @@ const documentNodeMutationRunSystemRebuild = DocumentNode(definitions: [ name: NameNode(value: 'basicMutationReturnFields'), directives: [], ), + FieldNode( + name: NameNode(value: 'job'), + alias: null, + arguments: [], + directives: [], + selectionSet: SelectionSetNode(selections: [ + FragmentSpreadNode( + name: NameNode(value: 'basicApiJobsFields'), + directives: [], + ), + FieldNode( + name: NameNode(value: '__typename'), + alias: null, + arguments: [], + directives: [], + selectionSet: null, + ), + ]), + ), FieldNode( name: NameNode(value: '__typename'), alias: null, @@ -4508,6 +4728,7 @@ const documentNodeMutationRunSystemRebuild = DocumentNode(definitions: [ ]), ), fragmentDefinitionbasicMutationReturnFields, + fragmentDefinitionbasicApiJobsFields, ]); Mutation$RunSystemRebuild _parserFn$Mutation$RunSystemRebuild( Map data) => @@ -4747,12 +4968,13 @@ class _CopyWithStubImpl$Mutation$RunSystemRebuild$system } class Mutation$RunSystemRebuild$system$runSystemRebuild - implements Fragment$basicMutationReturnFields$$GenericMutationReturn { + implements Fragment$basicMutationReturnFields$$GenericJobMutationReturn { Mutation$RunSystemRebuild$system$runSystemRebuild({ required this.code, required this.message, required this.success, - this.$__typename = 'GenericMutationReturn', + this.$__typename = 'GenericJobMutationReturn', + this.job, }); factory Mutation$RunSystemRebuild$system$runSystemRebuild.fromJson( @@ -4761,11 +4983,16 @@ class Mutation$RunSystemRebuild$system$runSystemRebuild final l$message = json['message']; final l$success = json['success']; final l$$__typename = json['__typename']; + final l$job = json['job']; return Mutation$RunSystemRebuild$system$runSystemRebuild( code: (l$code as int), message: (l$message as String), success: (l$success as bool), $__typename: (l$$__typename as String), + job: l$job == null + ? null + : Fragment$basicApiJobsFields.fromJson( + (l$job as Map)), ); } @@ -4777,6 +5004,8 @@ class Mutation$RunSystemRebuild$system$runSystemRebuild final String $__typename; + final Fragment$basicApiJobsFields? job; + Map toJson() { final _resultData = {}; final l$code = code; @@ -4787,6 +5016,8 @@ class Mutation$RunSystemRebuild$system$runSystemRebuild _resultData['success'] = l$success; final l$$__typename = $__typename; _resultData['__typename'] = l$$__typename; + final l$job = job; + _resultData['job'] = l$job?.toJson(); return _resultData; } @@ -4796,11 +5027,13 @@ class Mutation$RunSystemRebuild$system$runSystemRebuild final l$message = message; final l$success = success; final l$$__typename = $__typename; + final l$job = job; return Object.hashAll([ l$code, l$message, l$success, l$$__typename, + l$job, ]); } @@ -4833,6 +5066,11 @@ class Mutation$RunSystemRebuild$system$runSystemRebuild if (l$$__typename != lOther$$__typename) { return false; } + final l$job = job; + final lOther$job = other.job; + if (l$job != lOther$job) { + return false; + } return true; } } @@ -4864,7 +5102,9 @@ abstract class CopyWith$Mutation$RunSystemRebuild$system$runSystemRebuild< String? message, bool? success, String? $__typename, + Fragment$basicApiJobsFields? job, }); + CopyWith$Fragment$basicApiJobsFields get job; } class _CopyWithImpl$Mutation$RunSystemRebuild$system$runSystemRebuild @@ -4886,6 +5126,7 @@ class _CopyWithImpl$Mutation$RunSystemRebuild$system$runSystemRebuild Object? message = _undefined, Object? success = _undefined, Object? $__typename = _undefined, + Object? job = _undefined, }) => _then(Mutation$RunSystemRebuild$system$runSystemRebuild( code: @@ -4899,7 +5140,17 @@ class _CopyWithImpl$Mutation$RunSystemRebuild$system$runSystemRebuild $__typename: $__typename == _undefined || $__typename == null ? _instance.$__typename : ($__typename as String), + job: job == _undefined + ? _instance.job + : (job as Fragment$basicApiJobsFields?), )); + + CopyWith$Fragment$basicApiJobsFields get job { + final local$job = _instance.job; + return local$job == null + ? CopyWith$Fragment$basicApiJobsFields.stub(_then(_instance)) + : CopyWith$Fragment$basicApiJobsFields(local$job, (e) => call(job: e)); + } } class _CopyWithStubImpl$Mutation$RunSystemRebuild$system$runSystemRebuild @@ -4915,8 +5166,12 @@ class _CopyWithStubImpl$Mutation$RunSystemRebuild$system$runSystemRebuild String? message, bool? success, String? $__typename, + Fragment$basicApiJobsFields? job, }) => _res; + + CopyWith$Fragment$basicApiJobsFields get job => + CopyWith$Fragment$basicApiJobsFields.stub(_res); } class Mutation$RunSystemRollback { @@ -5681,6 +5936,25 @@ const documentNodeMutationRunSystemUpgrade = DocumentNode(definitions: [ name: NameNode(value: 'basicMutationReturnFields'), directives: [], ), + FieldNode( + name: NameNode(value: 'job'), + alias: null, + arguments: [], + directives: [], + selectionSet: SelectionSetNode(selections: [ + FragmentSpreadNode( + name: NameNode(value: 'basicApiJobsFields'), + directives: [], + ), + FieldNode( + name: NameNode(value: '__typename'), + alias: null, + arguments: [], + directives: [], + selectionSet: null, + ), + ]), + ), FieldNode( name: NameNode(value: '__typename'), alias: null, @@ -5709,6 +5983,7 @@ const documentNodeMutationRunSystemUpgrade = DocumentNode(definitions: [ ]), ), fragmentDefinitionbasicMutationReturnFields, + fragmentDefinitionbasicApiJobsFields, ]); Mutation$RunSystemUpgrade _parserFn$Mutation$RunSystemUpgrade( Map data) => @@ -5948,12 +6223,13 @@ class _CopyWithStubImpl$Mutation$RunSystemUpgrade$system } class Mutation$RunSystemUpgrade$system$runSystemUpgrade - implements Fragment$basicMutationReturnFields$$GenericMutationReturn { + implements Fragment$basicMutationReturnFields$$GenericJobMutationReturn { Mutation$RunSystemUpgrade$system$runSystemUpgrade({ required this.code, required this.message, required this.success, - this.$__typename = 'GenericMutationReturn', + this.$__typename = 'GenericJobMutationReturn', + this.job, }); factory Mutation$RunSystemUpgrade$system$runSystemUpgrade.fromJson( @@ -5962,11 +6238,16 @@ class Mutation$RunSystemUpgrade$system$runSystemUpgrade final l$message = json['message']; final l$success = json['success']; final l$$__typename = json['__typename']; + final l$job = json['job']; return Mutation$RunSystemUpgrade$system$runSystemUpgrade( code: (l$code as int), message: (l$message as String), success: (l$success as bool), $__typename: (l$$__typename as String), + job: l$job == null + ? null + : Fragment$basicApiJobsFields.fromJson( + (l$job as Map)), ); } @@ -5978,6 +6259,8 @@ class Mutation$RunSystemUpgrade$system$runSystemUpgrade final String $__typename; + final Fragment$basicApiJobsFields? job; + Map toJson() { final _resultData = {}; final l$code = code; @@ -5988,6 +6271,8 @@ class Mutation$RunSystemUpgrade$system$runSystemUpgrade _resultData['success'] = l$success; final l$$__typename = $__typename; _resultData['__typename'] = l$$__typename; + final l$job = job; + _resultData['job'] = l$job?.toJson(); return _resultData; } @@ -5997,11 +6282,13 @@ class Mutation$RunSystemUpgrade$system$runSystemUpgrade final l$message = message; final l$success = success; final l$$__typename = $__typename; + final l$job = job; return Object.hashAll([ l$code, l$message, l$success, l$$__typename, + l$job, ]); } @@ -6034,6 +6321,11 @@ class Mutation$RunSystemUpgrade$system$runSystemUpgrade if (l$$__typename != lOther$$__typename) { return false; } + final l$job = job; + final lOther$job = other.job; + if (l$job != lOther$job) { + return false; + } return true; } } @@ -6065,7 +6357,9 @@ abstract class CopyWith$Mutation$RunSystemUpgrade$system$runSystemUpgrade< String? message, bool? success, String? $__typename, + Fragment$basicApiJobsFields? job, }); + CopyWith$Fragment$basicApiJobsFields get job; } class _CopyWithImpl$Mutation$RunSystemUpgrade$system$runSystemUpgrade @@ -6087,6 +6381,7 @@ class _CopyWithImpl$Mutation$RunSystemUpgrade$system$runSystemUpgrade Object? message = _undefined, Object? success = _undefined, Object? $__typename = _undefined, + Object? job = _undefined, }) => _then(Mutation$RunSystemUpgrade$system$runSystemUpgrade( code: @@ -6100,7 +6395,17 @@ class _CopyWithImpl$Mutation$RunSystemUpgrade$system$runSystemUpgrade $__typename: $__typename == _undefined || $__typename == null ? _instance.$__typename : ($__typename as String), + job: job == _undefined + ? _instance.job + : (job as Fragment$basicApiJobsFields?), )); + + CopyWith$Fragment$basicApiJobsFields get job { + final local$job = _instance.job; + return local$job == null + ? CopyWith$Fragment$basicApiJobsFields.stub(_then(_instance)) + : CopyWith$Fragment$basicApiJobsFields(local$job, (e) => call(job: e)); + } } class _CopyWithStubImpl$Mutation$RunSystemUpgrade$system$runSystemUpgrade @@ -6116,8 +6421,12 @@ class _CopyWithStubImpl$Mutation$RunSystemUpgrade$system$runSystemUpgrade String? message, bool? success, String? $__typename, + Fragment$basicApiJobsFields? job, }) => _res; + + CopyWith$Fragment$basicApiJobsFields get job => + CopyWith$Fragment$basicApiJobsFields.stub(_res); } class Mutation$PullRepositoryChanges { From fdb40fccd7947d7b51e24532dcb644727a82f367 Mon Sep 17 00:00:00 2001 From: Inex Code Date: Wed, 14 Feb 2024 15:59:01 +0300 Subject: [PATCH 26/34] fix: Init ApiConnectionRepository after server access recovery --- .../cubit/server_installation/server_installation_cubit.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/logic/cubit/server_installation/server_installation_cubit.dart b/lib/logic/cubit/server_installation/server_installation_cubit.dart index c6613a71..5476a755 100644 --- a/lib/logic/cubit/server_installation/server_installation_cubit.dart +++ b/lib/logic/cubit/server_installation/server_installation_cubit.dart @@ -803,6 +803,7 @@ class ServerInstallationCubit extends Cubit { serverTypeIdentificator: serverType.data!.identifier, ); emit(updatedState.finish()); + getIt().init(); } @override From 16094a3257d13fa16788f81db5ff79287f2b7c61 Mon Sep 17 00:00:00 2001 From: Inex Code Date: Tue, 20 Feb 2024 19:33:24 +0300 Subject: [PATCH 27/34] refactor: Rework ClientJobs cubit so it doesn't depend on other cubits Also implemented tracking of the jobs and rebuild status --- assets/translations/en.json | 13 +- lib/config/bloc_config.dart | 4 +- .../graphql_maps/schema/server_api.graphql | 16 + .../schema/server_api.graphql.dart | 1228 +++++++++++++++++ .../server_api/server_actions_api.dart | 91 +- lib/logic/bloc/devices/devices_bloc.dart | 4 - .../cubit/client_jobs/client_jobs_cubit.dart | 206 ++- .../cubit/client_jobs/client_jobs_state.dart | 136 +- .../server_detailed_info_repository.dart | 14 - .../get_it/api_connection_repository.dart | 66 +- lib/logic/models/job.dart | 306 +++- .../components/jobs_content/jobs_content.dart | 356 ++++- .../server_details/server_details_screen.dart | 1 - .../pages/server_details/server_settings.dart | 55 +- .../server_details/time_zone/time_zone.dart | 6 +- pubspec.lock | 2 +- pubspec.yaml | 1 + 17 files changed, 2313 insertions(+), 192 deletions(-) diff --git a/assets/translations/en.json b/assets/translations/en.json index 1410a950..14861d32 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -36,7 +36,8 @@ "continue": "Continue", "alert": "Alert", "copied_to_clipboard": "Copied to clipboard!", - "please_connect": "Please connect your server, domain and DNS provider to dive in!" + "please_connect": "Please connect your server, domain and DNS provider to dive in!", + "network_error": "Network error" }, "more_page": { "configuration_wizard": "Setup wizard", @@ -394,7 +395,8 @@ "could_not_add_ssh_key": "Couldn't add SSH key", "username_rule": "Username must contain only lowercase latin letters, digits and underscores, should not start with a digit", "email_login": "Email login", - "no_ssh_notice": "Only email and SSH accounts are created for this user. Single Sign On for all services is coming soon." + "no_ssh_notice": "Only email and SSH accounts are created for this user. Single Sign On for all services is coming soon.", + "user_already_exists": "User with such username already exists" }, "initializing": { "server_provider_description": "A place where your data and SelfPrivacy services will reside:", @@ -594,6 +596,7 @@ "service_turn_off": "Turn off", "service_turn_on": "Turn on", "job_added": "Job added", + "job_postponed": "Job added, but you will be able to launch it after current jobs are finished", "run_jobs": "Run jobs", "reboot_success": "Server is rebooting", "reboot_failed": "Couldn't reboot the server. Check the app logs.", @@ -606,7 +609,11 @@ "delete_ssh_key": "Delete SSH key for {}", "server_jobs": "Jobs on the server", "reset_user_password": "Reset password of user", - "generic_error": "Couldn't connect to the server!" + "generic_error": "Couldn't connect to the server!", + "rebuild_system": "Rebuild system", + "start_server_upgrade": "Start the server upgrade", + "change_auto_upgrade_settings": "Change auto-upgrade settings", + "change_server_timezone": "Change server timezone" }, "validations": { "required": "Required", diff --git a/lib/config/bloc_config.dart b/lib/config/bloc_config.dart index 4f5baeed..06cb7244 100644 --- a/lib/config/bloc_config.dart +++ b/lib/config/bloc_config.dart @@ -104,9 +104,7 @@ class BlocAndProviderConfigState extends State { ), BlocProvider(create: (final _) => volumesBloc), BlocProvider( - create: (final _) => JobsCubit( - servicesBloc: servicesBloc, - ), + create: (final _) => JobsCubit(), ), ], child: widget.child, diff --git a/lib/logic/api_maps/graphql_maps/schema/server_api.graphql b/lib/logic/api_maps/graphql_maps/schema/server_api.graphql index e66d8143..d8e21485 100644 --- a/lib/logic/api_maps/graphql_maps/schema/server_api.graphql +++ b/lib/logic/api_maps/graphql_maps/schema/server_api.graphql @@ -52,6 +52,14 @@ mutation RunSystemRebuild { } } +mutation RunSystemRebuildFallback { + system { + runSystemRebuild { + ...basicMutationReturnFields + } + } +} + mutation RunSystemRollback { system { runSystemRollback { @@ -71,6 +79,14 @@ mutation RunSystemUpgrade { } } +mutation RunSystemUpgradeFallback { + system { + runSystemUpgrade { + ...basicMutationReturnFields + } + } +} + mutation PullRepositoryChanges { system { pullRepositoryChanges { diff --git a/lib/logic/api_maps/graphql_maps/schema/server_api.graphql.dart b/lib/logic/api_maps/graphql_maps/schema/server_api.graphql.dart index 5241a6c9..7fcc3d4d 100644 --- a/lib/logic/api_maps/graphql_maps/schema/server_api.graphql.dart +++ b/lib/logic/api_maps/graphql_maps/schema/server_api.graphql.dart @@ -5174,6 +5174,620 @@ class _CopyWithStubImpl$Mutation$RunSystemRebuild$system$runSystemRebuild CopyWith$Fragment$basicApiJobsFields.stub(_res); } +class Mutation$RunSystemRebuildFallback { + Mutation$RunSystemRebuildFallback({ + required this.system, + this.$__typename = 'Mutation', + }); + + factory Mutation$RunSystemRebuildFallback.fromJson( + Map json) { + final l$system = json['system']; + final l$$__typename = json['__typename']; + return Mutation$RunSystemRebuildFallback( + system: Mutation$RunSystemRebuildFallback$system.fromJson( + (l$system as Map)), + $__typename: (l$$__typename as String), + ); + } + + final Mutation$RunSystemRebuildFallback$system system; + + final String $__typename; + + Map toJson() { + final _resultData = {}; + final l$system = system; + _resultData['system'] = l$system.toJson(); + final l$$__typename = $__typename; + _resultData['__typename'] = l$$__typename; + return _resultData; + } + + @override + int get hashCode { + final l$system = system; + final l$$__typename = $__typename; + return Object.hashAll([ + l$system, + l$$__typename, + ]); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (!(other is Mutation$RunSystemRebuildFallback) || + runtimeType != other.runtimeType) { + return false; + } + final l$system = system; + final lOther$system = other.system; + if (l$system != lOther$system) { + return false; + } + final l$$__typename = $__typename; + final lOther$$__typename = other.$__typename; + if (l$$__typename != lOther$$__typename) { + return false; + } + return true; + } +} + +extension UtilityExtension$Mutation$RunSystemRebuildFallback + on Mutation$RunSystemRebuildFallback { + CopyWith$Mutation$RunSystemRebuildFallback + get copyWith => CopyWith$Mutation$RunSystemRebuildFallback( + this, + (i) => i, + ); +} + +abstract class CopyWith$Mutation$RunSystemRebuildFallback { + factory CopyWith$Mutation$RunSystemRebuildFallback( + Mutation$RunSystemRebuildFallback instance, + TRes Function(Mutation$RunSystemRebuildFallback) then, + ) = _CopyWithImpl$Mutation$RunSystemRebuildFallback; + + factory CopyWith$Mutation$RunSystemRebuildFallback.stub(TRes res) = + _CopyWithStubImpl$Mutation$RunSystemRebuildFallback; + + TRes call({ + Mutation$RunSystemRebuildFallback$system? system, + String? $__typename, + }); + CopyWith$Mutation$RunSystemRebuildFallback$system get system; +} + +class _CopyWithImpl$Mutation$RunSystemRebuildFallback + implements CopyWith$Mutation$RunSystemRebuildFallback { + _CopyWithImpl$Mutation$RunSystemRebuildFallback( + this._instance, + this._then, + ); + + final Mutation$RunSystemRebuildFallback _instance; + + final TRes Function(Mutation$RunSystemRebuildFallback) _then; + + static const _undefined = {}; + + TRes call({ + Object? system = _undefined, + Object? $__typename = _undefined, + }) => + _then(Mutation$RunSystemRebuildFallback( + system: system == _undefined || system == null + ? _instance.system + : (system as Mutation$RunSystemRebuildFallback$system), + $__typename: $__typename == _undefined || $__typename == null + ? _instance.$__typename + : ($__typename as String), + )); + + CopyWith$Mutation$RunSystemRebuildFallback$system get system { + final local$system = _instance.system; + return CopyWith$Mutation$RunSystemRebuildFallback$system( + local$system, (e) => call(system: e)); + } +} + +class _CopyWithStubImpl$Mutation$RunSystemRebuildFallback + implements CopyWith$Mutation$RunSystemRebuildFallback { + _CopyWithStubImpl$Mutation$RunSystemRebuildFallback(this._res); + + TRes _res; + + call({ + Mutation$RunSystemRebuildFallback$system? system, + String? $__typename, + }) => + _res; + + CopyWith$Mutation$RunSystemRebuildFallback$system get system => + CopyWith$Mutation$RunSystemRebuildFallback$system.stub(_res); +} + +const documentNodeMutationRunSystemRebuildFallback = DocumentNode(definitions: [ + OperationDefinitionNode( + type: OperationType.mutation, + name: NameNode(value: 'RunSystemRebuildFallback'), + variableDefinitions: [], + directives: [], + selectionSet: SelectionSetNode(selections: [ + FieldNode( + name: NameNode(value: 'system'), + alias: null, + arguments: [], + directives: [], + selectionSet: SelectionSetNode(selections: [ + FieldNode( + name: NameNode(value: 'runSystemRebuild'), + alias: null, + arguments: [], + directives: [], + selectionSet: SelectionSetNode(selections: [ + FragmentSpreadNode( + name: NameNode(value: 'basicMutationReturnFields'), + directives: [], + ), + FieldNode( + name: NameNode(value: '__typename'), + alias: null, + arguments: [], + directives: [], + selectionSet: null, + ), + ]), + ), + FieldNode( + name: NameNode(value: '__typename'), + alias: null, + arguments: [], + directives: [], + selectionSet: null, + ), + ]), + ), + FieldNode( + name: NameNode(value: '__typename'), + alias: null, + arguments: [], + directives: [], + selectionSet: null, + ), + ]), + ), + fragmentDefinitionbasicMutationReturnFields, +]); +Mutation$RunSystemRebuildFallback _parserFn$Mutation$RunSystemRebuildFallback( + Map data) => + Mutation$RunSystemRebuildFallback.fromJson(data); +typedef OnMutationCompleted$Mutation$RunSystemRebuildFallback = FutureOr + Function( + Map?, + Mutation$RunSystemRebuildFallback?, +); + +class Options$Mutation$RunSystemRebuildFallback + extends graphql.MutationOptions { + Options$Mutation$RunSystemRebuildFallback({ + String? operationName, + graphql.FetchPolicy? fetchPolicy, + graphql.ErrorPolicy? errorPolicy, + graphql.CacheRereadPolicy? cacheRereadPolicy, + Object? optimisticResult, + Mutation$RunSystemRebuildFallback? typedOptimisticResult, + graphql.Context? context, + OnMutationCompleted$Mutation$RunSystemRebuildFallback? onCompleted, + graphql.OnMutationUpdate? update, + graphql.OnError? onError, + }) : onCompletedWithParsed = onCompleted, + super( + operationName: operationName, + fetchPolicy: fetchPolicy, + errorPolicy: errorPolicy, + cacheRereadPolicy: cacheRereadPolicy, + optimisticResult: optimisticResult ?? typedOptimisticResult?.toJson(), + context: context, + onCompleted: onCompleted == null + ? null + : (data) => onCompleted( + data, + data == null + ? null + : _parserFn$Mutation$RunSystemRebuildFallback(data), + ), + update: update, + onError: onError, + document: documentNodeMutationRunSystemRebuildFallback, + parserFn: _parserFn$Mutation$RunSystemRebuildFallback, + ); + + final OnMutationCompleted$Mutation$RunSystemRebuildFallback? + onCompletedWithParsed; + + @override + List get properties => [ + ...super.onCompleted == null + ? super.properties + : super.properties.where((property) => property != onCompleted), + onCompletedWithParsed, + ]; +} + +class WatchOptions$Mutation$RunSystemRebuildFallback + extends graphql.WatchQueryOptions { + WatchOptions$Mutation$RunSystemRebuildFallback({ + String? operationName, + graphql.FetchPolicy? fetchPolicy, + graphql.ErrorPolicy? errorPolicy, + graphql.CacheRereadPolicy? cacheRereadPolicy, + Object? optimisticResult, + Mutation$RunSystemRebuildFallback? typedOptimisticResult, + graphql.Context? context, + Duration? pollInterval, + bool? eagerlyFetchResults, + bool carryForwardDataOnException = true, + bool fetchResults = false, + }) : super( + operationName: operationName, + fetchPolicy: fetchPolicy, + errorPolicy: errorPolicy, + cacheRereadPolicy: cacheRereadPolicy, + optimisticResult: optimisticResult ?? typedOptimisticResult?.toJson(), + context: context, + document: documentNodeMutationRunSystemRebuildFallback, + pollInterval: pollInterval, + eagerlyFetchResults: eagerlyFetchResults, + carryForwardDataOnException: carryForwardDataOnException, + fetchResults: fetchResults, + parserFn: _parserFn$Mutation$RunSystemRebuildFallback, + ); +} + +extension ClientExtension$Mutation$RunSystemRebuildFallback + on graphql.GraphQLClient { + Future> + mutate$RunSystemRebuildFallback( + [Options$Mutation$RunSystemRebuildFallback? options]) async => + await this + .mutate(options ?? Options$Mutation$RunSystemRebuildFallback()); + graphql.ObservableQuery + watchMutation$RunSystemRebuildFallback( + [WatchOptions$Mutation$RunSystemRebuildFallback? options]) => + this.watchMutation( + options ?? WatchOptions$Mutation$RunSystemRebuildFallback()); +} + +class Mutation$RunSystemRebuildFallback$system { + Mutation$RunSystemRebuildFallback$system({ + required this.runSystemRebuild, + this.$__typename = 'SystemMutations', + }); + + factory Mutation$RunSystemRebuildFallback$system.fromJson( + Map json) { + final l$runSystemRebuild = json['runSystemRebuild']; + final l$$__typename = json['__typename']; + return Mutation$RunSystemRebuildFallback$system( + runSystemRebuild: + Mutation$RunSystemRebuildFallback$system$runSystemRebuild.fromJson( + (l$runSystemRebuild as Map)), + $__typename: (l$$__typename as String), + ); + } + + final Mutation$RunSystemRebuildFallback$system$runSystemRebuild + runSystemRebuild; + + final String $__typename; + + Map toJson() { + final _resultData = {}; + final l$runSystemRebuild = runSystemRebuild; + _resultData['runSystemRebuild'] = l$runSystemRebuild.toJson(); + final l$$__typename = $__typename; + _resultData['__typename'] = l$$__typename; + return _resultData; + } + + @override + int get hashCode { + final l$runSystemRebuild = runSystemRebuild; + final l$$__typename = $__typename; + return Object.hashAll([ + l$runSystemRebuild, + l$$__typename, + ]); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (!(other is Mutation$RunSystemRebuildFallback$system) || + runtimeType != other.runtimeType) { + return false; + } + final l$runSystemRebuild = runSystemRebuild; + final lOther$runSystemRebuild = other.runSystemRebuild; + if (l$runSystemRebuild != lOther$runSystemRebuild) { + return false; + } + final l$$__typename = $__typename; + final lOther$$__typename = other.$__typename; + if (l$$__typename != lOther$$__typename) { + return false; + } + return true; + } +} + +extension UtilityExtension$Mutation$RunSystemRebuildFallback$system + on Mutation$RunSystemRebuildFallback$system { + CopyWith$Mutation$RunSystemRebuildFallback$system< + Mutation$RunSystemRebuildFallback$system> + get copyWith => CopyWith$Mutation$RunSystemRebuildFallback$system( + this, + (i) => i, + ); +} + +abstract class CopyWith$Mutation$RunSystemRebuildFallback$system { + factory CopyWith$Mutation$RunSystemRebuildFallback$system( + Mutation$RunSystemRebuildFallback$system instance, + TRes Function(Mutation$RunSystemRebuildFallback$system) then, + ) = _CopyWithImpl$Mutation$RunSystemRebuildFallback$system; + + factory CopyWith$Mutation$RunSystemRebuildFallback$system.stub(TRes res) = + _CopyWithStubImpl$Mutation$RunSystemRebuildFallback$system; + + TRes call({ + Mutation$RunSystemRebuildFallback$system$runSystemRebuild? runSystemRebuild, + String? $__typename, + }); + CopyWith$Mutation$RunSystemRebuildFallback$system$runSystemRebuild + get runSystemRebuild; +} + +class _CopyWithImpl$Mutation$RunSystemRebuildFallback$system + implements CopyWith$Mutation$RunSystemRebuildFallback$system { + _CopyWithImpl$Mutation$RunSystemRebuildFallback$system( + this._instance, + this._then, + ); + + final Mutation$RunSystemRebuildFallback$system _instance; + + final TRes Function(Mutation$RunSystemRebuildFallback$system) _then; + + static const _undefined = {}; + + TRes call({ + Object? runSystemRebuild = _undefined, + Object? $__typename = _undefined, + }) => + _then(Mutation$RunSystemRebuildFallback$system( + runSystemRebuild: runSystemRebuild == _undefined || + runSystemRebuild == null + ? _instance.runSystemRebuild + : (runSystemRebuild + as Mutation$RunSystemRebuildFallback$system$runSystemRebuild), + $__typename: $__typename == _undefined || $__typename == null + ? _instance.$__typename + : ($__typename as String), + )); + + CopyWith$Mutation$RunSystemRebuildFallback$system$runSystemRebuild + get runSystemRebuild { + final local$runSystemRebuild = _instance.runSystemRebuild; + return CopyWith$Mutation$RunSystemRebuildFallback$system$runSystemRebuild( + local$runSystemRebuild, (e) => call(runSystemRebuild: e)); + } +} + +class _CopyWithStubImpl$Mutation$RunSystemRebuildFallback$system + implements CopyWith$Mutation$RunSystemRebuildFallback$system { + _CopyWithStubImpl$Mutation$RunSystemRebuildFallback$system(this._res); + + TRes _res; + + call({ + Mutation$RunSystemRebuildFallback$system$runSystemRebuild? runSystemRebuild, + String? $__typename, + }) => + _res; + + CopyWith$Mutation$RunSystemRebuildFallback$system$runSystemRebuild + get runSystemRebuild => + CopyWith$Mutation$RunSystemRebuildFallback$system$runSystemRebuild + .stub(_res); +} + +class Mutation$RunSystemRebuildFallback$system$runSystemRebuild + implements Fragment$basicMutationReturnFields$$GenericJobMutationReturn { + Mutation$RunSystemRebuildFallback$system$runSystemRebuild({ + required this.code, + required this.message, + required this.success, + this.$__typename = 'GenericJobMutationReturn', + }); + + factory Mutation$RunSystemRebuildFallback$system$runSystemRebuild.fromJson( + Map json) { + final l$code = json['code']; + final l$message = json['message']; + final l$success = json['success']; + final l$$__typename = json['__typename']; + return Mutation$RunSystemRebuildFallback$system$runSystemRebuild( + code: (l$code as int), + message: (l$message as String), + success: (l$success as bool), + $__typename: (l$$__typename as String), + ); + } + + final int code; + + final String message; + + final bool success; + + final String $__typename; + + Map toJson() { + final _resultData = {}; + final l$code = code; + _resultData['code'] = l$code; + final l$message = message; + _resultData['message'] = l$message; + final l$success = success; + _resultData['success'] = l$success; + final l$$__typename = $__typename; + _resultData['__typename'] = l$$__typename; + return _resultData; + } + + @override + int get hashCode { + final l$code = code; + final l$message = message; + final l$success = success; + final l$$__typename = $__typename; + return Object.hashAll([ + l$code, + l$message, + l$success, + l$$__typename, + ]); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (!(other is Mutation$RunSystemRebuildFallback$system$runSystemRebuild) || + runtimeType != other.runtimeType) { + return false; + } + final l$code = code; + final lOther$code = other.code; + if (l$code != lOther$code) { + return false; + } + final l$message = message; + final lOther$message = other.message; + if (l$message != lOther$message) { + return false; + } + final l$success = success; + final lOther$success = other.success; + if (l$success != lOther$success) { + return false; + } + final l$$__typename = $__typename; + final lOther$$__typename = other.$__typename; + if (l$$__typename != lOther$$__typename) { + return false; + } + return true; + } +} + +extension UtilityExtension$Mutation$RunSystemRebuildFallback$system$runSystemRebuild + on Mutation$RunSystemRebuildFallback$system$runSystemRebuild { + CopyWith$Mutation$RunSystemRebuildFallback$system$runSystemRebuild< + Mutation$RunSystemRebuildFallback$system$runSystemRebuild> + get copyWith => + CopyWith$Mutation$RunSystemRebuildFallback$system$runSystemRebuild( + this, + (i) => i, + ); +} + +abstract class CopyWith$Mutation$RunSystemRebuildFallback$system$runSystemRebuild< + TRes> { + factory CopyWith$Mutation$RunSystemRebuildFallback$system$runSystemRebuild( + Mutation$RunSystemRebuildFallback$system$runSystemRebuild instance, + TRes Function(Mutation$RunSystemRebuildFallback$system$runSystemRebuild) + then, + ) = _CopyWithImpl$Mutation$RunSystemRebuildFallback$system$runSystemRebuild; + + factory CopyWith$Mutation$RunSystemRebuildFallback$system$runSystemRebuild.stub( + TRes res) = + _CopyWithStubImpl$Mutation$RunSystemRebuildFallback$system$runSystemRebuild; + + TRes call({ + int? code, + String? message, + bool? success, + String? $__typename, + }); +} + +class _CopyWithImpl$Mutation$RunSystemRebuildFallback$system$runSystemRebuild< + TRes> + implements + CopyWith$Mutation$RunSystemRebuildFallback$system$runSystemRebuild< + TRes> { + _CopyWithImpl$Mutation$RunSystemRebuildFallback$system$runSystemRebuild( + this._instance, + this._then, + ); + + final Mutation$RunSystemRebuildFallback$system$runSystemRebuild _instance; + + final TRes Function(Mutation$RunSystemRebuildFallback$system$runSystemRebuild) + _then; + + static const _undefined = {}; + + TRes call({ + Object? code = _undefined, + Object? message = _undefined, + Object? success = _undefined, + Object? $__typename = _undefined, + }) => + _then(Mutation$RunSystemRebuildFallback$system$runSystemRebuild( + code: + code == _undefined || code == null ? _instance.code : (code as int), + message: message == _undefined || message == null + ? _instance.message + : (message as String), + success: success == _undefined || success == null + ? _instance.success + : (success as bool), + $__typename: $__typename == _undefined || $__typename == null + ? _instance.$__typename + : ($__typename as String), + )); +} + +class _CopyWithStubImpl$Mutation$RunSystemRebuildFallback$system$runSystemRebuild< + TRes> + implements + CopyWith$Mutation$RunSystemRebuildFallback$system$runSystemRebuild< + TRes> { + _CopyWithStubImpl$Mutation$RunSystemRebuildFallback$system$runSystemRebuild( + this._res); + + TRes _res; + + call({ + int? code, + String? message, + bool? success, + String? $__typename, + }) => + _res; +} + class Mutation$RunSystemRollback { Mutation$RunSystemRollback({ required this.system, @@ -6429,6 +7043,620 @@ class _CopyWithStubImpl$Mutation$RunSystemUpgrade$system$runSystemUpgrade CopyWith$Fragment$basicApiJobsFields.stub(_res); } +class Mutation$RunSystemUpgradeFallback { + Mutation$RunSystemUpgradeFallback({ + required this.system, + this.$__typename = 'Mutation', + }); + + factory Mutation$RunSystemUpgradeFallback.fromJson( + Map json) { + final l$system = json['system']; + final l$$__typename = json['__typename']; + return Mutation$RunSystemUpgradeFallback( + system: Mutation$RunSystemUpgradeFallback$system.fromJson( + (l$system as Map)), + $__typename: (l$$__typename as String), + ); + } + + final Mutation$RunSystemUpgradeFallback$system system; + + final String $__typename; + + Map toJson() { + final _resultData = {}; + final l$system = system; + _resultData['system'] = l$system.toJson(); + final l$$__typename = $__typename; + _resultData['__typename'] = l$$__typename; + return _resultData; + } + + @override + int get hashCode { + final l$system = system; + final l$$__typename = $__typename; + return Object.hashAll([ + l$system, + l$$__typename, + ]); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (!(other is Mutation$RunSystemUpgradeFallback) || + runtimeType != other.runtimeType) { + return false; + } + final l$system = system; + final lOther$system = other.system; + if (l$system != lOther$system) { + return false; + } + final l$$__typename = $__typename; + final lOther$$__typename = other.$__typename; + if (l$$__typename != lOther$$__typename) { + return false; + } + return true; + } +} + +extension UtilityExtension$Mutation$RunSystemUpgradeFallback + on Mutation$RunSystemUpgradeFallback { + CopyWith$Mutation$RunSystemUpgradeFallback + get copyWith => CopyWith$Mutation$RunSystemUpgradeFallback( + this, + (i) => i, + ); +} + +abstract class CopyWith$Mutation$RunSystemUpgradeFallback { + factory CopyWith$Mutation$RunSystemUpgradeFallback( + Mutation$RunSystemUpgradeFallback instance, + TRes Function(Mutation$RunSystemUpgradeFallback) then, + ) = _CopyWithImpl$Mutation$RunSystemUpgradeFallback; + + factory CopyWith$Mutation$RunSystemUpgradeFallback.stub(TRes res) = + _CopyWithStubImpl$Mutation$RunSystemUpgradeFallback; + + TRes call({ + Mutation$RunSystemUpgradeFallback$system? system, + String? $__typename, + }); + CopyWith$Mutation$RunSystemUpgradeFallback$system get system; +} + +class _CopyWithImpl$Mutation$RunSystemUpgradeFallback + implements CopyWith$Mutation$RunSystemUpgradeFallback { + _CopyWithImpl$Mutation$RunSystemUpgradeFallback( + this._instance, + this._then, + ); + + final Mutation$RunSystemUpgradeFallback _instance; + + final TRes Function(Mutation$RunSystemUpgradeFallback) _then; + + static const _undefined = {}; + + TRes call({ + Object? system = _undefined, + Object? $__typename = _undefined, + }) => + _then(Mutation$RunSystemUpgradeFallback( + system: system == _undefined || system == null + ? _instance.system + : (system as Mutation$RunSystemUpgradeFallback$system), + $__typename: $__typename == _undefined || $__typename == null + ? _instance.$__typename + : ($__typename as String), + )); + + CopyWith$Mutation$RunSystemUpgradeFallback$system get system { + final local$system = _instance.system; + return CopyWith$Mutation$RunSystemUpgradeFallback$system( + local$system, (e) => call(system: e)); + } +} + +class _CopyWithStubImpl$Mutation$RunSystemUpgradeFallback + implements CopyWith$Mutation$RunSystemUpgradeFallback { + _CopyWithStubImpl$Mutation$RunSystemUpgradeFallback(this._res); + + TRes _res; + + call({ + Mutation$RunSystemUpgradeFallback$system? system, + String? $__typename, + }) => + _res; + + CopyWith$Mutation$RunSystemUpgradeFallback$system get system => + CopyWith$Mutation$RunSystemUpgradeFallback$system.stub(_res); +} + +const documentNodeMutationRunSystemUpgradeFallback = DocumentNode(definitions: [ + OperationDefinitionNode( + type: OperationType.mutation, + name: NameNode(value: 'RunSystemUpgradeFallback'), + variableDefinitions: [], + directives: [], + selectionSet: SelectionSetNode(selections: [ + FieldNode( + name: NameNode(value: 'system'), + alias: null, + arguments: [], + directives: [], + selectionSet: SelectionSetNode(selections: [ + FieldNode( + name: NameNode(value: 'runSystemUpgrade'), + alias: null, + arguments: [], + directives: [], + selectionSet: SelectionSetNode(selections: [ + FragmentSpreadNode( + name: NameNode(value: 'basicMutationReturnFields'), + directives: [], + ), + FieldNode( + name: NameNode(value: '__typename'), + alias: null, + arguments: [], + directives: [], + selectionSet: null, + ), + ]), + ), + FieldNode( + name: NameNode(value: '__typename'), + alias: null, + arguments: [], + directives: [], + selectionSet: null, + ), + ]), + ), + FieldNode( + name: NameNode(value: '__typename'), + alias: null, + arguments: [], + directives: [], + selectionSet: null, + ), + ]), + ), + fragmentDefinitionbasicMutationReturnFields, +]); +Mutation$RunSystemUpgradeFallback _parserFn$Mutation$RunSystemUpgradeFallback( + Map data) => + Mutation$RunSystemUpgradeFallback.fromJson(data); +typedef OnMutationCompleted$Mutation$RunSystemUpgradeFallback = FutureOr + Function( + Map?, + Mutation$RunSystemUpgradeFallback?, +); + +class Options$Mutation$RunSystemUpgradeFallback + extends graphql.MutationOptions { + Options$Mutation$RunSystemUpgradeFallback({ + String? operationName, + graphql.FetchPolicy? fetchPolicy, + graphql.ErrorPolicy? errorPolicy, + graphql.CacheRereadPolicy? cacheRereadPolicy, + Object? optimisticResult, + Mutation$RunSystemUpgradeFallback? typedOptimisticResult, + graphql.Context? context, + OnMutationCompleted$Mutation$RunSystemUpgradeFallback? onCompleted, + graphql.OnMutationUpdate? update, + graphql.OnError? onError, + }) : onCompletedWithParsed = onCompleted, + super( + operationName: operationName, + fetchPolicy: fetchPolicy, + errorPolicy: errorPolicy, + cacheRereadPolicy: cacheRereadPolicy, + optimisticResult: optimisticResult ?? typedOptimisticResult?.toJson(), + context: context, + onCompleted: onCompleted == null + ? null + : (data) => onCompleted( + data, + data == null + ? null + : _parserFn$Mutation$RunSystemUpgradeFallback(data), + ), + update: update, + onError: onError, + document: documentNodeMutationRunSystemUpgradeFallback, + parserFn: _parserFn$Mutation$RunSystemUpgradeFallback, + ); + + final OnMutationCompleted$Mutation$RunSystemUpgradeFallback? + onCompletedWithParsed; + + @override + List get properties => [ + ...super.onCompleted == null + ? super.properties + : super.properties.where((property) => property != onCompleted), + onCompletedWithParsed, + ]; +} + +class WatchOptions$Mutation$RunSystemUpgradeFallback + extends graphql.WatchQueryOptions { + WatchOptions$Mutation$RunSystemUpgradeFallback({ + String? operationName, + graphql.FetchPolicy? fetchPolicy, + graphql.ErrorPolicy? errorPolicy, + graphql.CacheRereadPolicy? cacheRereadPolicy, + Object? optimisticResult, + Mutation$RunSystemUpgradeFallback? typedOptimisticResult, + graphql.Context? context, + Duration? pollInterval, + bool? eagerlyFetchResults, + bool carryForwardDataOnException = true, + bool fetchResults = false, + }) : super( + operationName: operationName, + fetchPolicy: fetchPolicy, + errorPolicy: errorPolicy, + cacheRereadPolicy: cacheRereadPolicy, + optimisticResult: optimisticResult ?? typedOptimisticResult?.toJson(), + context: context, + document: documentNodeMutationRunSystemUpgradeFallback, + pollInterval: pollInterval, + eagerlyFetchResults: eagerlyFetchResults, + carryForwardDataOnException: carryForwardDataOnException, + fetchResults: fetchResults, + parserFn: _parserFn$Mutation$RunSystemUpgradeFallback, + ); +} + +extension ClientExtension$Mutation$RunSystemUpgradeFallback + on graphql.GraphQLClient { + Future> + mutate$RunSystemUpgradeFallback( + [Options$Mutation$RunSystemUpgradeFallback? options]) async => + await this + .mutate(options ?? Options$Mutation$RunSystemUpgradeFallback()); + graphql.ObservableQuery + watchMutation$RunSystemUpgradeFallback( + [WatchOptions$Mutation$RunSystemUpgradeFallback? options]) => + this.watchMutation( + options ?? WatchOptions$Mutation$RunSystemUpgradeFallback()); +} + +class Mutation$RunSystemUpgradeFallback$system { + Mutation$RunSystemUpgradeFallback$system({ + required this.runSystemUpgrade, + this.$__typename = 'SystemMutations', + }); + + factory Mutation$RunSystemUpgradeFallback$system.fromJson( + Map json) { + final l$runSystemUpgrade = json['runSystemUpgrade']; + final l$$__typename = json['__typename']; + return Mutation$RunSystemUpgradeFallback$system( + runSystemUpgrade: + Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade.fromJson( + (l$runSystemUpgrade as Map)), + $__typename: (l$$__typename as String), + ); + } + + final Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade + runSystemUpgrade; + + final String $__typename; + + Map toJson() { + final _resultData = {}; + final l$runSystemUpgrade = runSystemUpgrade; + _resultData['runSystemUpgrade'] = l$runSystemUpgrade.toJson(); + final l$$__typename = $__typename; + _resultData['__typename'] = l$$__typename; + return _resultData; + } + + @override + int get hashCode { + final l$runSystemUpgrade = runSystemUpgrade; + final l$$__typename = $__typename; + return Object.hashAll([ + l$runSystemUpgrade, + l$$__typename, + ]); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (!(other is Mutation$RunSystemUpgradeFallback$system) || + runtimeType != other.runtimeType) { + return false; + } + final l$runSystemUpgrade = runSystemUpgrade; + final lOther$runSystemUpgrade = other.runSystemUpgrade; + if (l$runSystemUpgrade != lOther$runSystemUpgrade) { + return false; + } + final l$$__typename = $__typename; + final lOther$$__typename = other.$__typename; + if (l$$__typename != lOther$$__typename) { + return false; + } + return true; + } +} + +extension UtilityExtension$Mutation$RunSystemUpgradeFallback$system + on Mutation$RunSystemUpgradeFallback$system { + CopyWith$Mutation$RunSystemUpgradeFallback$system< + Mutation$RunSystemUpgradeFallback$system> + get copyWith => CopyWith$Mutation$RunSystemUpgradeFallback$system( + this, + (i) => i, + ); +} + +abstract class CopyWith$Mutation$RunSystemUpgradeFallback$system { + factory CopyWith$Mutation$RunSystemUpgradeFallback$system( + Mutation$RunSystemUpgradeFallback$system instance, + TRes Function(Mutation$RunSystemUpgradeFallback$system) then, + ) = _CopyWithImpl$Mutation$RunSystemUpgradeFallback$system; + + factory CopyWith$Mutation$RunSystemUpgradeFallback$system.stub(TRes res) = + _CopyWithStubImpl$Mutation$RunSystemUpgradeFallback$system; + + TRes call({ + Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade? runSystemUpgrade, + String? $__typename, + }); + CopyWith$Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade + get runSystemUpgrade; +} + +class _CopyWithImpl$Mutation$RunSystemUpgradeFallback$system + implements CopyWith$Mutation$RunSystemUpgradeFallback$system { + _CopyWithImpl$Mutation$RunSystemUpgradeFallback$system( + this._instance, + this._then, + ); + + final Mutation$RunSystemUpgradeFallback$system _instance; + + final TRes Function(Mutation$RunSystemUpgradeFallback$system) _then; + + static const _undefined = {}; + + TRes call({ + Object? runSystemUpgrade = _undefined, + Object? $__typename = _undefined, + }) => + _then(Mutation$RunSystemUpgradeFallback$system( + runSystemUpgrade: runSystemUpgrade == _undefined || + runSystemUpgrade == null + ? _instance.runSystemUpgrade + : (runSystemUpgrade + as Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade), + $__typename: $__typename == _undefined || $__typename == null + ? _instance.$__typename + : ($__typename as String), + )); + + CopyWith$Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade + get runSystemUpgrade { + final local$runSystemUpgrade = _instance.runSystemUpgrade; + return CopyWith$Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade( + local$runSystemUpgrade, (e) => call(runSystemUpgrade: e)); + } +} + +class _CopyWithStubImpl$Mutation$RunSystemUpgradeFallback$system + implements CopyWith$Mutation$RunSystemUpgradeFallback$system { + _CopyWithStubImpl$Mutation$RunSystemUpgradeFallback$system(this._res); + + TRes _res; + + call({ + Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade? runSystemUpgrade, + String? $__typename, + }) => + _res; + + CopyWith$Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade + get runSystemUpgrade => + CopyWith$Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade + .stub(_res); +} + +class Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade + implements Fragment$basicMutationReturnFields$$GenericJobMutationReturn { + Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade({ + required this.code, + required this.message, + required this.success, + this.$__typename = 'GenericJobMutationReturn', + }); + + factory Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade.fromJson( + Map json) { + final l$code = json['code']; + final l$message = json['message']; + final l$success = json['success']; + final l$$__typename = json['__typename']; + return Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade( + code: (l$code as int), + message: (l$message as String), + success: (l$success as bool), + $__typename: (l$$__typename as String), + ); + } + + final int code; + + final String message; + + final bool success; + + final String $__typename; + + Map toJson() { + final _resultData = {}; + final l$code = code; + _resultData['code'] = l$code; + final l$message = message; + _resultData['message'] = l$message; + final l$success = success; + _resultData['success'] = l$success; + final l$$__typename = $__typename; + _resultData['__typename'] = l$$__typename; + return _resultData; + } + + @override + int get hashCode { + final l$code = code; + final l$message = message; + final l$success = success; + final l$$__typename = $__typename; + return Object.hashAll([ + l$code, + l$message, + l$success, + l$$__typename, + ]); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (!(other is Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade) || + runtimeType != other.runtimeType) { + return false; + } + final l$code = code; + final lOther$code = other.code; + if (l$code != lOther$code) { + return false; + } + final l$message = message; + final lOther$message = other.message; + if (l$message != lOther$message) { + return false; + } + final l$success = success; + final lOther$success = other.success; + if (l$success != lOther$success) { + return false; + } + final l$$__typename = $__typename; + final lOther$$__typename = other.$__typename; + if (l$$__typename != lOther$$__typename) { + return false; + } + return true; + } +} + +extension UtilityExtension$Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade + on Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade { + CopyWith$Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade< + Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade> + get copyWith => + CopyWith$Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade( + this, + (i) => i, + ); +} + +abstract class CopyWith$Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade< + TRes> { + factory CopyWith$Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade( + Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade instance, + TRes Function(Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade) + then, + ) = _CopyWithImpl$Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade; + + factory CopyWith$Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade.stub( + TRes res) = + _CopyWithStubImpl$Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade; + + TRes call({ + int? code, + String? message, + bool? success, + String? $__typename, + }); +} + +class _CopyWithImpl$Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade< + TRes> + implements + CopyWith$Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade< + TRes> { + _CopyWithImpl$Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade( + this._instance, + this._then, + ); + + final Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade _instance; + + final TRes Function(Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade) + _then; + + static const _undefined = {}; + + TRes call({ + Object? code = _undefined, + Object? message = _undefined, + Object? success = _undefined, + Object? $__typename = _undefined, + }) => + _then(Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade( + code: + code == _undefined || code == null ? _instance.code : (code as int), + message: message == _undefined || message == null + ? _instance.message + : (message as String), + success: success == _undefined || success == null + ? _instance.success + : (success as bool), + $__typename: $__typename == _undefined || $__typename == null + ? _instance.$__typename + : ($__typename as String), + )); +} + +class _CopyWithStubImpl$Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade< + TRes> + implements + CopyWith$Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade< + TRes> { + _CopyWithStubImpl$Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade( + this._res); + + TRes _res; + + call({ + int? code, + String? message, + bool? success, + String? $__typename, + }) => + _res; +} + class Mutation$PullRepositoryChanges { Mutation$PullRepositoryChanges({ required this.system, diff --git a/lib/logic/api_maps/graphql_maps/server_api/server_actions_api.dart b/lib/logic/api_maps/graphql_maps/server_api/server_actions_api.dart index 1d357b06..f568a064 100644 --- a/lib/logic/api_maps/graphql_maps/server_api/server_actions_api.dart +++ b/lib/logic/api_maps/graphql_maps/server_api/server_actions_api.dart @@ -29,7 +29,11 @@ mixin ServerActionsApi on GraphQLApiMap { print(response.exception.toString()); } if (response.parsedData!.system.rebootSystem.success) { - time = DateTime.now().toUtc(); + return GenericResult( + data: time, + success: true, + message: response.parsedData!.system.rebootSystem.message, + ); } } catch (e) { print(e); @@ -50,23 +54,94 @@ mixin ServerActionsApi on GraphQLApiMap { } } - Future upgrade() async { + Future> upgrade() async { try { final GraphQLClient client = await getClient(); - return _commonBoolRequest( - () async => client.mutate$RunSystemUpgrade(), - ); + final result = await client.mutate$RunSystemUpgrade(); + if (result.hasException) { + final fallbackResult = await client.mutate$RunSystemUpgradeFallback(); + if (fallbackResult.parsedData!.system.runSystemUpgrade.success) { + return GenericResult( + success: true, + data: null, + message: fallbackResult.parsedData!.system.runSystemUpgrade.message, + ); + } else { + return GenericResult( + success: false, + message: fallbackResult.parsedData!.system.runSystemUpgrade.message, + data: null, + ); + } + } else if (result.parsedData!.system.runSystemUpgrade.success && + result.parsedData!.system.runSystemUpgrade.job != null) { + return GenericResult( + success: true, + data: ServerJob.fromGraphQL( + result.parsedData!.system.runSystemUpgrade.job!, + ), + message: result.parsedData!.system.runSystemUpgrade.message, + ); + } else { + return GenericResult( + success: false, + message: result.parsedData!.system.runSystemUpgrade.message, + data: null, + ); + } } catch (e) { - return false; + return GenericResult( + success: false, + message: e.toString(), + data: null, + ); } } - Future apply() async { + Future> apply() async { try { final GraphQLClient client = await getClient(); - await client.mutate$RunSystemRebuild(); + final result = await client.mutate$RunSystemRebuild(); + if (result.hasException) { + final fallbackResult = await client.mutate$RunSystemRebuildFallback(); + if (fallbackResult.parsedData!.system.runSystemRebuild.success) { + return GenericResult( + success: true, + data: null, + message: fallbackResult.parsedData!.system.runSystemRebuild.message, + ); + } else { + return GenericResult( + success: false, + message: fallbackResult.parsedData!.system.runSystemRebuild.message, + data: null, + ); + } + } else { + if (result.parsedData!.system.runSystemRebuild.success && + result.parsedData!.system.runSystemRebuild.job != null) { + return GenericResult( + success: true, + data: ServerJob.fromGraphQL( + result.parsedData!.system.runSystemRebuild.job!, + ), + message: result.parsedData!.system.runSystemRebuild.message, + ); + } else { + return GenericResult( + success: false, + message: result.parsedData!.system.runSystemRebuild.message, + data: null, + ); + } + } } catch (e) { print(e); + return GenericResult( + success: false, + message: e.toString(), + data: null, + ); } } } diff --git a/lib/logic/bloc/devices/devices_bloc.dart b/lib/logic/bloc/devices/devices_bloc.dart index e1d248c7..fdba66e9 100644 --- a/lib/logic/bloc/devices/devices_bloc.dart +++ b/lib/logic/bloc/devices/devices_bloc.dart @@ -24,8 +24,6 @@ class DevicesBloc extends Bloc { final apiConnectionRepository = getIt(); _apiDataSubscription = apiConnectionRepository.dataStream.listen( (final ApiData apiData) { - print('============'); - print(apiData.devices.data); add( DevicesListChanged(apiData.devices.data), ); @@ -42,7 +40,6 @@ class DevicesBloc extends Bloc { if (state is DevicesDeleting) { return; } - print(event.devices); if (event.devices == null) { emit(DevicesError()); return; @@ -103,7 +100,6 @@ class DevicesBloc extends Bloc { @override void onChange(final Change change) { super.onChange(change); - print(change); } @override diff --git a/lib/logic/cubit/client_jobs/client_jobs_cubit.dart b/lib/logic/cubit/client_jobs/client_jobs_cubit.dart index 16b87c77..25ed3642 100644 --- a/lib/logic/cubit/client_jobs/client_jobs_cubit.dart +++ b/lib/logic/cubit/client_jobs/client_jobs_cubit.dart @@ -1,33 +1,55 @@ import 'dart:async'; +import 'package:collection/collection.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart'; -import 'package:selfprivacy/logic/bloc/services/services_bloc.dart'; import 'package:selfprivacy/logic/models/job.dart'; +import 'package:selfprivacy/logic/models/json/server_job.dart'; export 'package:provider/provider.dart'; part 'client_jobs_state.dart'; class JobsCubit extends Cubit { - JobsCubit({ - required this.servicesBloc, - }) : super(JobsStateEmpty()); + JobsCubit() : super(JobsStateEmpty()) { + final apiConnectionRepository = getIt(); + _apiDataSubscription = apiConnectionRepository.dataStream.listen( + (final ApiData apiData) { + if (apiData.serverJobs.data != null && + apiData.serverJobs.data!.isNotEmpty) { + _handleServerJobs(apiData.serverJobs.data!); + } + }, + ); + } + + StreamSubscription? _apiDataSubscription; final ServerApi api = ServerApi(); - final ServicesBloc servicesBloc; - void addJob(final ClientJob job) { - final jobs = currentJobList; - if (job.canAddTo(jobs)) { - _updateJobsState([ - ...jobs, - ...[job], - ]); + void _handleServerJobs(final List jobs) { + if (state is! JobsStateLoading) { + return; } + if (state.rebuildJobUid == null) { + return; + } + // Find a job with the uid of the rebuild job + final ServerJob? rebuildJob = jobs.firstWhereOrNull( + (final job) => job.uid == state.rebuildJobUid, + ); + if (rebuildJob == null || + rebuildJob.status == JobStatusEnum.error || + rebuildJob.status == JobStatusEnum.finished) { + emit((state as JobsStateLoading).finished()); + } + } + + void addJob(final ClientJob job) async { + emit(state.addJob(job)); } void removeJob(final String id) { @@ -35,61 +57,145 @@ class JobsCubit extends Cubit { emit(newState); } - List get currentJobList { - final List jobs = []; - if (state is JobsStateWithJobs) { - jobs.addAll((state as JobsStateWithJobs).clientJobList); - } - - return jobs; - } - - void _updateJobsState(final List newJobs) { - getIt().showSnackBar('jobs.job_added'.tr()); - emit(JobsStateWithJobs(newJobs)); - } - Future rebootServer() async { - emit(JobsStateLoading()); - final rebootResult = await api.reboot(); - if (rebootResult.success && rebootResult.data != null) { - getIt().showSnackBar('jobs.reboot_success'.tr()); - } else { - getIt().showSnackBar('jobs.reboot_failed'.tr()); + if (state is JobsStateEmpty) { + emit( + JobsStateLoading( + [RebootServerJob(status: JobStatusEnum.running)], + null, + const [], + ), + ); + final rebootResult = await api.reboot(); + if (rebootResult.success && rebootResult.data != null) { + emit( + JobsStateFinished( + [ + RebootServerJob( + status: JobStatusEnum.finished, + message: rebootResult.message, + ), + ], + null, + const [], + ), + ); + } else { + emit( + JobsStateFinished( + [RebootServerJob(status: JobStatusEnum.error)], + null, + const [], + ), + ); + } } - emit(JobsStateEmpty()); } Future upgradeServer() async { - emit(JobsStateLoading()); - final bool isPullSuccessful = await api.pullConfigurationUpdate(); - final bool isSuccessful = await api.upgrade(); - if (isSuccessful) { - if (!isPullSuccessful) { - getIt().showSnackBar('jobs.config_pull_failed'.tr()); + if (state is JobsStateEmpty) { + emit( + JobsStateLoading( + [UpgradeServerJob(status: JobStatusEnum.running)], + null, + const [], + ), + ); + final result = await getIt().api.upgrade(); + if (result.success && result.data != null) { + emit( + JobsStateLoading( + [UpgradeServerJob(status: JobStatusEnum.finished)], + result.data!.uid, + const [], + ), + ); } else { - getIt().showSnackBar('jobs.upgrade_success'.tr()); + emit( + JobsStateFinished( + [UpgradeServerJob(status: JobStatusEnum.error)], + null, + const [], + ), + ); } - } else { - getIt().showSnackBar('jobs.upgrade_failed'.tr()); } - emit(JobsStateEmpty()); } Future applyAll() async { if (state is JobsStateWithJobs) { final List jobs = (state as JobsStateWithJobs).clientJobList; - emit(JobsStateLoading()); + emit(JobsStateLoading(jobs, null, const [])); + + await Future.delayed(Duration.zero); + + final rebuildRequired = jobs.any((final job) => job.requiresRebuild); for (final ClientJob job in jobs) { - job.execute(this); + emit( + (state as JobsStateLoading) + .updateJobStatus(job.id, JobStatusEnum.running), + ); + final (result, message) = await job.execute(this); + if (result) { + emit( + (state as JobsStateLoading).updateJobStatus( + job.id, + JobStatusEnum.finished, + message: message, + ), + ); + } else { + emit( + (state as JobsStateLoading) + .updateJobStatus(job.id, JobStatusEnum.error, message: message), + ); + } } - await api.pullConfigurationUpdate(); - await api.apply(); - servicesBloc.add(const ServicesReload()); - - emit(JobsStateEmpty()); + if (!rebuildRequired) { + emit((state as JobsStateLoading).finished()); + return; + } + final rebuildResult = await getIt().api.apply(); + if (rebuildResult.success) { + if (rebuildResult.data != null) { + emit( + (state as JobsStateLoading) + .copyWith(rebuildJobUid: rebuildResult.data!.uid), + ); + } else { + emit((state as JobsStateLoading).finished()); + } + } else { + emit((state as JobsStateLoading).finished()); + } } } + + Future acknowledgeFinished() async { + if (state is! JobsStateFinished) { + return; + } + final rebuildJobUid = state.rebuildJobUid; + if ((state as JobsStateFinished).postponedJobs.isNotEmpty) { + emit(JobsStateWithJobs((state as JobsStateFinished).postponedJobs)); + } else { + emit(JobsStateEmpty()); + } + if (rebuildJobUid != null) { + await getIt().removeServerJob(rebuildJobUid); + } + } + + @override + void onChange(final Change change) { + super.onChange(change); + } + + @override + Future close() { + _apiDataSubscription?.cancel(); + return super.close(); + } } diff --git a/lib/logic/cubit/client_jobs/client_jobs_state.dart b/lib/logic/cubit/client_jobs/client_jobs_state.dart index 2bb31856..4f7244f5 100644 --- a/lib/logic/cubit/client_jobs/client_jobs_state.dart +++ b/lib/logic/cubit/client_jobs/client_jobs_state.dart @@ -1,17 +1,32 @@ part of 'client_jobs_cubit.dart'; -abstract class JobsState extends Equatable { +sealed class JobsState extends Equatable { + String? get rebuildJobUid => null; + + JobsState addJob(final ClientJob job); + @override List get props => []; } -class JobsStateLoading extends JobsState {} +class JobsStateEmpty extends JobsState { + @override + JobsStateWithJobs addJob(final ClientJob job) { + getIt().showSnackBar('jobs.job_added'.tr()); + return JobsStateWithJobs([job]); + } -class JobsStateEmpty extends JobsState {} + @override + List get props => []; +} class JobsStateWithJobs extends JobsState { JobsStateWithJobs(this.clientJobList); final List clientJobList; + + bool get rebuildRequired => + clientJobList.any((final job) => job.requiresRebuild); + JobsState removeById(final String id) { final List newJobsList = clientJobList.where((final element) => element.id != id).toList(); @@ -22,5 +37,118 @@ class JobsStateWithJobs extends JobsState { } @override - List get props => clientJobList; + List get props => [clientJobList]; + + @override + JobsState addJob(final ClientJob job) { + if (job is ReplaceableJob) { + final List newJobsList = clientJobList + .where((final element) => element.runtimeType != job.runtimeType) + .toList(); + newJobsList.add(job); + getIt().showSnackBar('jobs.job_added'.tr()); + return JobsStateWithJobs(newJobsList); + } + if (job.canAddTo(clientJobList)) { + final List newJobsList = [...clientJobList, job]; + getIt().showSnackBar('jobs.job_added'.tr()); + return JobsStateWithJobs(newJobsList); + } + return this; + } +} + +class JobsStateLoading extends JobsState { + JobsStateLoading(this.clientJobList, this.rebuildJobUid, this.postponedJobs); + final List clientJobList; + @override + final String? rebuildJobUid; + + bool get rebuildRequired => + clientJobList.any((final job) => job.requiresRebuild); + + final List postponedJobs; + + JobsStateLoading updateJobStatus( + final String id, + final JobStatusEnum status, { + final String? message, + }) { + final List newJobsList = clientJobList.map((final job) { + if (job.id == id) { + return job.copyWithNewStatus(status: status, message: message); + } + return job; + }).toList(); + return JobsStateLoading(newJobsList, rebuildJobUid, postponedJobs); + } + + JobsStateLoading copyWith({ + final List? clientJobList, + final String? rebuildJobUid, + final List? postponedJobs, + }) => + JobsStateLoading( + clientJobList ?? this.clientJobList, + rebuildJobUid ?? this.rebuildJobUid, + postponedJobs ?? this.postponedJobs, + ); + + JobsStateFinished finished() => + JobsStateFinished(clientJobList, rebuildJobUid, postponedJobs); + + @override + List get props => [clientJobList, rebuildJobUid, postponedJobs]; + + @override + JobsState addJob(final ClientJob job) { + // Do the same, but add jobs to the postponed list + if (job is ReplaceableJob) { + final List newPostponedJobs = postponedJobs + .where((final element) => element.runtimeType != job.runtimeType) + .toList(); + newPostponedJobs.add(job); + getIt().showSnackBar('jobs.job_postponed'.tr()); + return JobsStateLoading(clientJobList, rebuildJobUid, newPostponedJobs); + } + if (job.canAddTo(postponedJobs)) { + final List newPostponedJobs = [...postponedJobs, job]; + getIt().showSnackBar('jobs.job_postponed'.tr()); + return JobsStateLoading(clientJobList, rebuildJobUid, newPostponedJobs); + } + return this; + } +} + +class JobsStateFinished extends JobsState { + JobsStateFinished(this.clientJobList, this.rebuildJobUid, this.postponedJobs); + final List clientJobList; + @override + final String? rebuildJobUid; + + bool get rebuildRequired => + clientJobList.any((final job) => job.requiresRebuild); + + final List postponedJobs; + + @override + List get props => [clientJobList, rebuildJobUid, postponedJobs]; + + @override + JobsState addJob(final ClientJob job) { + if (job is ReplaceableJob) { + final List newPostponedJobs = postponedJobs + .where((final element) => element.runtimeType != job.runtimeType) + .toList(); + newPostponedJobs.add(job); + getIt().showSnackBar('jobs.job_added'.tr()); + return JobsStateWithJobs(newPostponedJobs); + } + if (job.canAddTo(postponedJobs)) { + final List newPostponedJobs = [...postponedJobs, job]; + getIt().showSnackBar('jobs.job_added'.tr()); + return JobsStateWithJobs(newPostponedJobs); + } + return this; + } } diff --git a/lib/logic/cubit/server_detailed_info/server_detailed_info_repository.dart b/lib/logic/cubit/server_detailed_info/server_detailed_info_repository.dart index 0d2d80e3..54540e5f 100644 --- a/lib/logic/cubit/server_detailed_info/server_detailed_info_repository.dart +++ b/lib/logic/cubit/server_detailed_info/server_detailed_info_repository.dart @@ -40,20 +40,6 @@ class ServerDetailsRepository { return data; } - - Future setAutoUpgradeSettings( - final AutoUpgradeSettings settings, - ) async { - await server.setAutoUpgradeSettings(settings); - } - - Future setTimezone( - final String timezone, - ) async { - if (timezone.isNotEmpty) { - await server.setTimezone(timezone); - } - } } class ServerDetailsRepositoryDto { diff --git a/lib/logic/get_it/api_connection_repository.dart b/lib/logic/get_it/api_connection_repository.dart index 13229442..eb818f35 100644 --- a/lib/logic/get_it/api_connection_repository.dart +++ b/lib/logic/get_it/api_connection_repository.dart @@ -70,45 +70,41 @@ class ApiConnectionRepository { ); } - Future createUser(final User user) async { + Future<(bool, String)> createUser(final User user) async { final List? loadedUsers = _apiData.users.data; if (loadedUsers == null) { - return; + return (false, 'basis.network_error'.tr()); } // If user exists on server, do nothing if (loadedUsers .any((final User u) => u.login == user.login && u.isFoundOnServer)) { - return; + return (false, 'users.user_already_exists'.tr()); } final String? password = user.password; if (password == null) { - getIt() - .showSnackBar('users.could_not_create_user'.tr()); - return; + return (false, 'users.could_not_create_user'.tr()); } // If API returned error, do nothing final GenericResult result = await api.createUser(user.login, password); if (result.data == null) { - getIt() - .showSnackBar(result.message ?? 'users.could_not_create_user'.tr()); - return; + return (false, result.message ?? 'users.could_not_create_user'.tr()); } _apiData.users.data?.add(result.data!); _apiData.users.invalidate(); + + return (true, result.message ?? 'basis.done'.tr()); } - Future deleteUser(final User user) async { + Future<(bool, String)> deleteUser(final User user) async { final List? loadedUsers = _apiData.users.data; if (loadedUsers == null) { - return; + return (false, 'basis.network_error'.tr()); } // If user is primary or root, don't delete if (user.type != UserType.normal) { - getIt() - .showSnackBar('users.could_not_delete_user'.tr()); - return; + return (false, 'users.could_not_delete_user'.tr()); } final GenericResult result = await api.deleteUser(user.login); if (result.success && result.data) { @@ -117,19 +113,18 @@ class ApiConnectionRepository { } if (!result.success || !result.data) { - getIt() - .showSnackBar(result.message ?? 'jobs.generic_error'.tr()); + return (false, result.message ?? 'jobs.generic_error'.tr()); } + + return (true, result.message ?? 'basis.done'.tr()); } - Future changeUserPassword( + Future<(bool, String)> changeUserPassword( final User user, final String newPassword, ) async { if (user.type == UserType.root) { - getIt() - .showSnackBar('users.could_not_change_password'.tr()); - return; + return (false, 'users.could_not_change_password'.tr()); } final GenericResult result = await api.updateUser( user.login, @@ -139,13 +134,21 @@ class ApiConnectionRepository { getIt().showSnackBar( result.message ?? 'users.could_not_change_password'.tr(), ); + return ( + false, + result.message ?? 'users.could_not_change_password'.tr(), + ); } + return (true, result.message ?? 'basis.done'.tr()); } - Future addSshKey(final User user, final String publicKey) async { + Future<(bool, String)> addSshKey( + final User user, + final String publicKey, + ) async { final List? loadedUsers = _apiData.users.data; if (loadedUsers == null) { - return; + return (false, 'basis.network_error'.tr()); } final GenericResult result = await api.addSshKey(user.login, publicKey); @@ -156,15 +159,19 @@ class ApiConnectionRepository { loadedUsers[index] = updatedUser; _apiData.users.invalidate(); } else { - getIt() - .showSnackBar(result.message ?? 'users.could_not_add_ssh_key'.tr()); + return (false, result.message ?? 'users.could_not_add_ssh_key'.tr()); } + + return (true, result.message ?? 'basis.done'.tr()); } - Future deleteSshKey(final User user, final String publicKey) async { + Future<(bool, String)> deleteSshKey( + final User user, + final String publicKey, + ) async { final List? loadedUsers = _apiData.users.data; if (loadedUsers == null) { - return; + return (false, 'basis.network_error'.tr()); } final GenericResult result = await api.removeSshKey(user.login, publicKey); @@ -175,9 +182,9 @@ class ApiConnectionRepository { loadedUsers[index] = updatedUser; _apiData.users.invalidate(); } else { - getIt() - .showSnackBar(result.message ?? 'jobs.generic_error'.tr()); + return (false, result.message ?? 'jobs.generic_error'.tr()); } + return (true, result.message ?? 'basis.done'.tr()); } void dispose() { @@ -345,11 +352,8 @@ class ApiDataElement { final Function callback, ) async { if (VersionConstraint.parse(requiredApiVersion).allows(version)) { - print('Fetching data for $runtimeType'); if (isExpired) { - print('Data is expired'); final newData = await fetchData(); - print(newData); if (T is List) { if (Object.hashAll(newData as Iterable) != Object.hashAll(_data as Iterable)) { diff --git a/lib/logic/models/job.dart b/lib/logic/models/job.dart index 6e3d2a1d..b88d759c 100644 --- a/lib/logic/models/job.dart +++ b/lib/logic/models/job.dart @@ -3,7 +3,9 @@ import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart'; +import 'package:selfprivacy/logic/models/auto_upgrade_settings.dart'; import 'package:selfprivacy/logic/models/hive/user.dart'; +import 'package:selfprivacy/logic/models/json/server_job.dart'; import 'package:selfprivacy/logic/models/service.dart'; import 'package:selfprivacy/utils/password_generator.dart'; @@ -12,18 +14,31 @@ abstract class ClientJob extends Equatable { ClientJob({ required this.title, final String? id, + this.requiresRebuild = true, + this.status = JobStatusEnum.created, + this.message, }) : id = id ?? StringGenerators.simpleId(); final String title; final String id; + final bool requiresRebuild; + + final JobStatusEnum status; + final String? message; bool canAddTo(final List jobs) => true; - void execute(final JobsCubit cubit); + Future<(bool, String)> execute(final JobsCubit cubit); @override - List get props => [id, title]; + List get props => [id, title, status]; + + ClientJob copyWithNewStatus({ + required final JobStatusEnum status, + final String? message, + }); } +@Deprecated('Jobs bloc should handle it itself') class RebuildServerJob extends ClientJob { RebuildServerJob({ required super.title, @@ -35,47 +50,138 @@ class RebuildServerJob extends ClientJob { !jobs.any((final job) => job is RebuildServerJob); @override - void execute(final JobsCubit cubit) async { - await cubit.upgradeServer(); + Future<(bool, String)> execute(final JobsCubit cubit) async => + (false, 'unimplemented'); + + @override + RebuildServerJob copyWithNewStatus({ + required final JobStatusEnum status, + final String? message, + }) { + throw UnimplementedError(); } } +class UpgradeServerJob extends ClientJob { + UpgradeServerJob({ + super.status, + super.message, + super.id, + }) : super(title: 'jobs.start_server_upgrade'.tr()); + + @override + bool canAddTo(final List jobs) => + !jobs.any((final job) => job is UpgradeServerJob); + + @override + Future<(bool, String)> execute(final JobsCubit cubit) async => + (false, 'unimplemented'); + + @override + UpgradeServerJob copyWithNewStatus({ + required final JobStatusEnum status, + final String? message, + }) => + UpgradeServerJob( + status: status, + message: message, + id: id, + ); +} + +class RebootServerJob extends ClientJob { + RebootServerJob({ + super.status, + super.message, + super.id, + }) : super(title: 'jobs.reboot_server'.tr(), requiresRebuild: false); + + @override + bool canAddTo(final List jobs) => + !jobs.any((final job) => job is RebootServerJob); + + @override + Future<(bool, String)> execute(final JobsCubit cubit) async => + (false, 'unimplemented'); + + @override + RebootServerJob copyWithNewStatus({ + required final JobStatusEnum status, + final String? message, + }) => + RebootServerJob( + status: status, + message: message, + id: id, + ); +} + class CreateUserJob extends ClientJob { CreateUserJob({ required this.user, + super.status, + super.message, + super.id, }) : super(title: '${"jobs.create_user".tr()} ${user.login}'); final User user; @override - void execute(final JobsCubit cubit) async { - await getIt().createUser(user); - } + Future<(bool, String)> execute(final JobsCubit cubit) async => + getIt().createUser(user); @override - List get props => [id, title, user]; + List get props => [...super.props, user]; + + @override + CreateUserJob copyWithNewStatus({ + required final JobStatusEnum status, + final String? message, + }) => + CreateUserJob( + user: user, + status: status, + message: message, + id: id, + ); } class ResetUserPasswordJob extends ClientJob { ResetUserPasswordJob({ required this.user, + super.status, + super.message, + super.id, }) : super(title: '${"jobs.reset_user_password".tr()} ${user.login}'); final User user; @override - void execute(final JobsCubit cubit) async { - await getIt() - .changeUserPassword(user, user.password!); - } + Future<(bool, String)> execute(final JobsCubit cubit) async => + getIt().changeUserPassword(user, user.password!); @override - List get props => [id, title, user]; + List get props => [...super.props, user]; + + @override + ResetUserPasswordJob copyWithNewStatus({ + required final JobStatusEnum status, + final String? message, + }) => + ResetUserPasswordJob( + user: user, + status: status, + message: message, + id: id, + ); } class DeleteUserJob extends ClientJob { DeleteUserJob({ required this.user, + super.status, + super.message, + super.id, }) : super(title: '${"jobs.delete_user".tr()} ${user.login}'); final User user; @@ -86,18 +192,32 @@ class DeleteUserJob extends ClientJob { ); @override - void execute(final JobsCubit cubit) async { - await getIt().deleteUser(user); - } + Future<(bool, String)> execute(final JobsCubit cubit) async => + getIt().deleteUser(user); @override - List get props => [id, title, user]; + List get props => [...super.props, user]; + + @override + DeleteUserJob copyWithNewStatus({ + required final JobStatusEnum status, + final String? message, + }) => + DeleteUserJob( + user: user, + status: status, + message: message, + id: id, + ); } class ServiceToggleJob extends ClientJob { ServiceToggleJob({ required this.service, required this.needToTurnOn, + super.status, + super.message, + super.id, }) : super( title: '${needToTurnOn ? "jobs.service_turn_on".tr() : "jobs.service_turn_off".tr()} ${service.displayName}', @@ -112,36 +232,68 @@ class ServiceToggleJob extends ClientJob { ); @override - void execute(final JobsCubit cubit) async { + Future<(bool, String)> execute(final JobsCubit cubit) async { await cubit.api.switchService(service.id, needToTurnOn); + return (true, 'Check not implemented'); } @override List get props => [...super.props, service]; + + @override + ServiceToggleJob copyWithNewStatus({ + required final JobStatusEnum status, + final String? message, + }) => + ServiceToggleJob( + service: service, + needToTurnOn: needToTurnOn, + status: status, + message: message, + id: id, + ); } class CreateSSHKeyJob extends ClientJob { CreateSSHKeyJob({ required this.user, required this.publicKey, + super.status, + super.message, + super.id, }) : super(title: 'jobs.create_ssh_key'.tr(args: [user.login])); final User user; final String publicKey; @override - void execute(final JobsCubit cubit) async { - await getIt().addSshKey(user, publicKey); - } + Future<(bool, String)> execute(final JobsCubit cubit) async => + getIt().addSshKey(user, publicKey); @override - List get props => [id, title, user, publicKey]; + List get props => [...super.props, user, publicKey]; + + @override + CreateSSHKeyJob copyWithNewStatus({ + required final JobStatusEnum status, + final String? message, + }) => + CreateSSHKeyJob( + user: user, + publicKey: publicKey, + status: status, + message: message, + id: id, + ); } class DeleteSSHKeyJob extends ClientJob { DeleteSSHKeyJob({ required this.user, required this.publicKey, + super.status, + super.message, + super.id, }) : super(title: 'jobs.delete_ssh_key'.tr(args: [user.login])); final User user; @@ -156,10 +308,114 @@ class DeleteSSHKeyJob extends ClientJob { ); @override - void execute(final JobsCubit cubit) async { - await getIt().deleteSshKey(user, publicKey); + Future<(bool, String)> execute(final JobsCubit cubit) async => + getIt().deleteSshKey(user, publicKey); + + @override + List get props => [...super.props, user, publicKey]; + + @override + DeleteSSHKeyJob copyWithNewStatus({ + required final JobStatusEnum status, + final String? message, + }) => + DeleteSSHKeyJob( + user: user, + publicKey: publicKey, + status: status, + message: message, + id: id, + ); +} + +abstract class ReplaceableJob extends ClientJob { + ReplaceableJob({ + required super.title, + super.id, + super.status, + super.message, + }); + + bool shouldRemoveInsteadOfAdd(final List jobs) => false; +} + +class ChangeAutoUpgradeSettingsJob extends ReplaceableJob { + ChangeAutoUpgradeSettingsJob({ + required this.enable, + required this.allowReboot, + super.status, + super.message, + super.id, + }) : super(title: 'jobs.change_auto_upgrade_settings'.tr()); + + final bool enable; + final bool allowReboot; + + @override + Future<(bool, String)> execute(final JobsCubit cubit) async { + await cubit.api.setAutoUpgradeSettings( + AutoUpgradeSettings(enable: enable, allowReboot: allowReboot), + ); + return (true, 'Check not implemented'); } @override - List get props => [id, title, user, publicKey]; + bool shouldRemoveInsteadOfAdd(final List jobs) { + // TODO: Finish this + throw UnimplementedError(); + } + + @override + List get props => [...super.props, enable, allowReboot]; + + @override + ChangeAutoUpgradeSettingsJob copyWithNewStatus({ + required final JobStatusEnum status, + final String? message, + }) => + ChangeAutoUpgradeSettingsJob( + enable: enable, + allowReboot: allowReboot, + status: status, + message: message, + id: id, + ); +} + +class ChangeServerTimezoneJob extends ReplaceableJob { + ChangeServerTimezoneJob({ + required this.timezone, + super.status, + super.message, + super.id, + }) : super(title: 'jobs.change_server_timezone'.tr()); + + final String timezone; + + @override + Future<(bool, String)> execute(final JobsCubit cubit) async { + await getIt().api.setTimezone(timezone); + return (true, 'Check not implemented'); + } + + @override + bool shouldRemoveInsteadOfAdd(final List jobs) { + // TODO: Finish this + throw UnimplementedError(); + } + + @override + List get props => [...super.props, timezone]; + + @override + ChangeServerTimezoneJob copyWithNewStatus({ + required final JobStatusEnum status, + final String? message, + }) => + ChangeServerTimezoneJob( + timezone: timezone, + status: status, + message: message, + id: id, + ); } diff --git a/lib/ui/components/jobs_content/jobs_content.dart b/lib/ui/components/jobs_content/jobs_content.dart index d583e039..e3c1c9de 100644 --- a/lib/ui/components/jobs_content/jobs_content.dart +++ b/lib/ui/components/jobs_content/jobs_content.dart @@ -1,3 +1,4 @@ +import 'package:collection/collection.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; @@ -6,7 +7,6 @@ import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart'; import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/logic/models/json/server_job.dart'; -import 'package:selfprivacy/ui/components/brand_loader/brand_loader.dart'; import 'package:selfprivacy/ui/components/buttons/brand_button.dart'; import 'package:selfprivacy/ui/components/jobs_content/server_job_card.dart'; import 'package:selfprivacy/ui/helpers/modals.dart'; @@ -19,6 +19,32 @@ class JobsContent extends StatelessWidget { final ScrollController controller; + IconData _getIcon(final JobStatusEnum status) { + switch (status) { + case JobStatusEnum.created: + return Icons.query_builder_outlined; + case JobStatusEnum.running: + return Icons.pending_outlined; + case JobStatusEnum.finished: + return Icons.check_circle_outline; + case JobStatusEnum.error: + return Icons.error_outline; + } + } + + Color _getColor(final JobStatusEnum status, final BuildContext context) { + switch (status) { + case JobStatusEnum.created: + return Theme.of(context).colorScheme.secondary; + case JobStatusEnum.running: + return Theme.of(context).colorScheme.tertiary; + case JobStatusEnum.finished: + return Theme.of(context).colorScheme.primary; + case JobStatusEnum.error: + return Theme.of(context).colorScheme.error; + } + } + @override Widget build(final BuildContext context) { final List serverJobs = @@ -68,8 +94,274 @@ class JobsContent extends StatelessWidget { } } else if (state is JobsStateLoading) { widgets = [ - const SizedBox(height: 80), - BrandLoader.horizontal(), + ...state.clientJobList.map( + (final j) => Row( + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Icon( + _getIcon(j.status), + color: _getColor(j.status, context), + ), + ), + Expanded( + child: Card( + color: Theme.of(context).colorScheme.surfaceVariant, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 15, + vertical: 10, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + j.title, + style: Theme.of(context) + .textTheme + .labelLarge + ?.copyWith( + color: Theme.of(context) + .colorScheme + .onSurfaceVariant, + ), + ), + if (j.message != null) + Text( + j.message!, + style: Theme.of(context).textTheme.labelSmall, + ), + ], + ), + ), + ), + ), + ], + ), + ), + if (state.rebuildRequired) + Builder( + builder: (final context) { + final rebuildJob = serverJobs.firstWhereOrNull( + (final job) => job.uid == state.rebuildJobUid, + ); + return Row( + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Icon( + _getIcon(rebuildJob?.status ?? JobStatusEnum.created), + color: _getColor( + rebuildJob?.status ?? JobStatusEnum.created, + context, + ), + ), + ), + Expanded( + child: Card( + color: Theme.of(context).colorScheme.surfaceVariant, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 15, + vertical: 10, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + rebuildJob?.name ?? + 'jobs.rebuild_system'.tr(), + style: Theme.of(context) + .textTheme + .labelLarge + ?.copyWith( + color: Theme.of(context) + .colorScheme + .onSurfaceVariant, + ), + ), + if (rebuildJob?.description != null) + Text( + rebuildJob!.description, + style: + Theme.of(context).textTheme.labelSmall, + ), + const SizedBox(height: 8), + LinearProgressIndicator( + value: rebuildJob?.progress == null + ? 0.0 + : ((rebuildJob!.progress ?? 0) < 1) + ? null + : rebuildJob.progress! / 100.0, + color: _getColor( + rebuildJob?.status ?? JobStatusEnum.created, + context, + ), + backgroundColor: Theme.of(context) + .colorScheme + .surfaceVariant, + minHeight: 7.0, + borderRadius: BorderRadius.circular(7.0), + ), + const SizedBox(height: 8), + if (rebuildJob?.error != null || + rebuildJob?.result != null || + rebuildJob?.statusText != null) + Text( + rebuildJob?.error ?? + rebuildJob?.result ?? + rebuildJob?.statusText ?? + '', + style: + Theme.of(context).textTheme.labelSmall, + ), + ], + ), + ), + ), + ), + ], + ); + }, + ), + ]; + } else if (state is JobsStateFinished) { + widgets = [ + ...state.clientJobList.map( + (final j) => Row( + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Icon( + _getIcon(j.status), + color: _getColor(j.status, context), + ), + ), + Expanded( + child: Card( + color: Theme.of(context).colorScheme.surfaceVariant, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 15, + vertical: 10, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + j.title, + style: Theme.of(context) + .textTheme + .labelLarge + ?.copyWith( + color: Theme.of(context) + .colorScheme + .onSurfaceVariant, + ), + ), + if (j.message != null) + Text( + j.message!, + style: Theme.of(context).textTheme.labelSmall, + ), + ], + ), + ), + ), + ), + ], + ), + ), + if (state.rebuildRequired) + Builder( + builder: (final context) { + final rebuildJob = serverJobs.firstWhereOrNull( + (final job) => job.uid == state.rebuildJobUid, + ); + if (rebuildJob == null) { + return const SizedBox(); + } + return Row( + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Icon( + _getIcon(rebuildJob.status), + color: _getColor( + rebuildJob.status, + context, + ), + ), + ), + Expanded( + child: Card( + color: Theme.of(context).colorScheme.surfaceVariant, + child: Padding( + padding: const EdgeInsets.symmetric( + horizontal: 15, + vertical: 10, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + rebuildJob.name, + style: Theme.of(context) + .textTheme + .labelLarge + ?.copyWith( + color: Theme.of(context) + .colorScheme + .onSurfaceVariant, + ), + ), + Text( + rebuildJob.description, + style: Theme.of(context).textTheme.labelSmall, + ), + const SizedBox(height: 8), + LinearProgressIndicator( + value: rebuildJob.progress == null + ? 0.0 + : ((rebuildJob.progress ?? 0) < 1) + ? null + : rebuildJob.progress! / 100.0, + color: _getColor( + rebuildJob.status, + context, + ), + backgroundColor: Theme.of(context) + .colorScheme + .surfaceVariant, + minHeight: 7.0, + borderRadius: BorderRadius.circular(7.0), + ), + const SizedBox(height: 8), + if (rebuildJob.error != null || + rebuildJob.result != null || + rebuildJob.statusText != null) + Text( + rebuildJob.error ?? + rebuildJob.result ?? + rebuildJob.statusText ?? + '', + style: + Theme.of(context).textTheme.labelSmall, + ), + ], + ), + ), + ), + ), + ], + ); + }, + ), + const SizedBox(height: 16), + BrandButton.rised( + onPressed: () => context.read().acknowledgeFinished(), + text: 'basis.done'.tr(), + ), ]; } else if (state is JobsStateWithJobs) { widgets = [ @@ -84,19 +376,31 @@ class JobsContent extends StatelessWidget { horizontal: 15, vertical: 10, ), - child: Text( - j.title, - style: - Theme.of(context).textTheme.labelLarge?.copyWith( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + j.title, + style: Theme.of(context) + .textTheme + .labelLarge + ?.copyWith( color: Theme.of(context) .colorScheme .onSurfaceVariant, ), + ), + if (j.message != null) + Text( + j.message!, + style: Theme.of(context).textTheme.labelSmall, + ), + ], ), ), ), ), - const SizedBox(width: 10), + const SizedBox(width: 8), ElevatedButton( style: ElevatedButton.styleFrom( backgroundColor: @@ -116,7 +420,7 @@ class JobsContent extends StatelessWidget { ], ), ), - const SizedBox(height: 20), + const SizedBox(height: 16), BrandButton.rised( onPressed: () => context.read().applyAll(), text: 'jobs.start'.tr(), @@ -161,23 +465,25 @@ class JobsContent extends StatelessWidget { ], ), ), - ...serverJobs.map( - (final job) => Dismissible( - key: ValueKey(job.uid), - direction: job.status == JobStatusEnum.finished || - job.status == JobStatusEnum.error - ? DismissDirection.horizontal - : DismissDirection.none, - child: ServerJobCard( - serverJob: job, + ...serverJobs + .whereNot((final job) => job.uid == state.rebuildJobUid) + .map( + (final job) => Dismissible( + key: ValueKey(job.uid), + direction: job.status == JobStatusEnum.finished || + job.status == JobStatusEnum.error + ? DismissDirection.horizontal + : DismissDirection.none, + child: ServerJobCard( + serverJob: job, + ), + onDismissed: (final direction) { + context.read().add( + RemoveServerJob(job.uid), + ); + }, + ), ), - onDismissed: (final direction) { - context.read().add( - RemoveServerJob(job.uid), - ); - }, - ), - ), const SizedBox(height: 24), ], ); diff --git a/lib/ui/pages/server_details/server_details_screen.dart b/lib/ui/pages/server_details/server_details_screen.dart index 1b66a21b..ab1f64d3 100644 --- a/lib/ui/pages/server_details/server_details_screen.dart +++ b/lib/ui/pages/server_details/server_details_screen.dart @@ -8,7 +8,6 @@ import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart'; import 'package:selfprivacy/logic/cubit/metrics/metrics_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_detailed_info/server_detailed_info_cubit.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; -import 'package:selfprivacy/logic/models/auto_upgrade_settings.dart'; import 'package:selfprivacy/logic/models/job.dart'; import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart'; import 'package:selfprivacy/ui/components/brand_loader/brand_loader.dart'; diff --git a/lib/ui/pages/server_details/server_settings.dart b/lib/ui/pages/server_details/server_settings.dart index a9facd5d..c6381a3e 100644 --- a/lib/ui/pages/server_details/server_settings.dart +++ b/lib/ui/pages/server_details/server_settings.dart @@ -30,24 +30,32 @@ class _ServerSettingsState extends State<_ServerSettings> { value: allowAutoUpgrade ?? false, onChanged: (final switched) { context.read().addJob( - RebuildServerJob(title: 'jobs.upgrade_server'.tr()), - ); - context - .read() - .repository - .setAutoUpgradeSettings( - AutoUpgradeSettings( - enable: switched, + ChangeAutoUpgradeSettingsJob( allowReboot: rebootAfterUpgrade ?? false, + enable: switched, ), ); setState(() { allowAutoUpgrade = switched; }); }, - title: Text('server.allow_autoupgrade'.tr()), + title: Text( + 'server.allow_autoupgrade'.tr(), + style: TextStyle( + fontStyle: allowAutoUpgrade != + serverDetailsState.autoUpgradeSettings.enable + ? FontStyle.italic + : FontStyle.normal, + ), + ), subtitle: Text( 'server.allow_autoupgrade_hint'.tr(), + style: TextStyle( + fontStyle: allowAutoUpgrade != + serverDetailsState.autoUpgradeSettings.enable + ? FontStyle.italic + : FontStyle.normal, + ), ), activeColor: Theme.of(context).colorScheme.primary, ), @@ -55,24 +63,32 @@ class _ServerSettingsState extends State<_ServerSettings> { value: rebootAfterUpgrade ?? false, onChanged: (final switched) { context.read().addJob( - RebuildServerJob(title: 'jobs.upgrade_server'.tr()), - ); - context - .read() - .repository - .setAutoUpgradeSettings( - AutoUpgradeSettings( - enable: allowAutoUpgrade ?? false, + ChangeAutoUpgradeSettingsJob( allowReboot: switched, + enable: allowAutoUpgrade ?? false, ), ); setState(() { rebootAfterUpgrade = switched; }); }, - title: Text('server.reboot_after_upgrade'.tr()), + title: Text( + 'server.reboot_after_upgrade'.tr(), + style: TextStyle( + fontStyle: rebootAfterUpgrade != + serverDetailsState.autoUpgradeSettings.allowReboot + ? FontStyle.italic + : FontStyle.normal, + ), + ), subtitle: Text( 'server.reboot_after_upgrade_hint'.tr(), + style: TextStyle( + fontStyle: rebootAfterUpgrade != + serverDetailsState.autoUpgradeSettings.allowReboot + ? FontStyle.italic + : FontStyle.normal, + ), ), activeColor: Theme.of(context).colorScheme.primary, ), @@ -82,9 +98,6 @@ class _ServerSettingsState extends State<_ServerSettings> { serverDetailsState.serverTimezone.toString(), ), onTap: () { - context.read().addJob( - RebuildServerJob(title: 'jobs.upgrade_server'.tr()), - ); Navigator.of(context).push( materialRoute( const SelectTimezone(), diff --git a/lib/ui/pages/server_details/time_zone/time_zone.dart b/lib/ui/pages/server_details/time_zone/time_zone.dart index 5eb369bc..2bb2608f 100644 --- a/lib/ui/pages/server_details/time_zone/time_zone.dart +++ b/lib/ui/pages/server_details/time_zone/time_zone.dart @@ -140,8 +140,10 @@ class _SelectTimezoneState extends State { 'GMT ${duration.toTimezoneOffsetFormat()} ${area.isNotEmpty ? '($area)' : ''}', ), onTap: () { - context.read().repository.setTimezone( - location.name, + context.read().addJob( + ChangeServerTimezoneJob( + timezone: location.name, + ), ); Navigator.of(context).pop(); }, diff --git a/pubspec.lock b/pubspec.lock index 4c2cee42..f71810c6 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -202,7 +202,7 @@ packages: source: hosted version: "4.8.0" collection: - dependency: transitive + dependency: "direct main" description: name: collection sha256: ee67cb0715911d28db6bf4af1026078bd6f0128b07a5f66fb2ed94ec6783c09a diff --git a/pubspec.yaml b/pubspec.yaml index 289888d7..dabc5cff 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -13,6 +13,7 @@ dependencies: auto_size_text: ^3.0.0 bloc_concurrency: ^0.2.3 crypt: ^4.3.1 + collection: ^1.18.0 cubit_form: ^2.0.1 device_info_plus: ^9.1.1 dio: ^5.4.0 From 9459191c09d313e96a64c758e176598d9ae4e472 Mon Sep 17 00:00:00 2001 From: Inex Code Date: Tue, 20 Feb 2024 20:04:39 +0300 Subject: [PATCH 28/34] refactor: Remove Job dependency on ClientJobsCubit --- .../cubit/client_jobs/client_jobs_cubit.dart | 7 ++--- lib/logic/models/job.dart | 29 +++++++++---------- 2 files changed, 16 insertions(+), 20 deletions(-) diff --git a/lib/logic/cubit/client_jobs/client_jobs_cubit.dart b/lib/logic/cubit/client_jobs/client_jobs_cubit.dart index 25ed3642..16cadfd0 100644 --- a/lib/logic/cubit/client_jobs/client_jobs_cubit.dart +++ b/lib/logic/cubit/client_jobs/client_jobs_cubit.dart @@ -5,7 +5,6 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:selfprivacy/config/get_it_config.dart'; -import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart'; import 'package:selfprivacy/logic/models/job.dart'; import 'package:selfprivacy/logic/models/json/server_job.dart'; @@ -28,8 +27,6 @@ class JobsCubit extends Cubit { StreamSubscription? _apiDataSubscription; - final ServerApi api = ServerApi(); - void _handleServerJobs(final List jobs) { if (state is! JobsStateLoading) { return; @@ -66,7 +63,7 @@ class JobsCubit extends Cubit { const [], ), ); - final rebootResult = await api.reboot(); + final rebootResult = await getIt().api.reboot(); if (rebootResult.success && rebootResult.data != null) { emit( JobsStateFinished( @@ -136,7 +133,7 @@ class JobsCubit extends Cubit { (state as JobsStateLoading) .updateJobStatus(job.id, JobStatusEnum.running), ); - final (result, message) = await job.execute(this); + final (result, message) = await job.execute(); if (result) { emit( (state as JobsStateLoading).updateJobStatus( diff --git a/lib/logic/models/job.dart b/lib/logic/models/job.dart index b88d759c..bce20d14 100644 --- a/lib/logic/models/job.dart +++ b/lib/logic/models/job.dart @@ -2,7 +2,6 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:selfprivacy/config/get_it_config.dart'; -import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart'; import 'package:selfprivacy/logic/models/auto_upgrade_settings.dart'; import 'package:selfprivacy/logic/models/hive/user.dart'; import 'package:selfprivacy/logic/models/json/server_job.dart'; @@ -27,7 +26,7 @@ abstract class ClientJob extends Equatable { final String? message; bool canAddTo(final List jobs) => true; - Future<(bool, String)> execute(final JobsCubit cubit); + Future<(bool, String)> execute(); @override List get props => [id, title, status]; @@ -50,7 +49,7 @@ class RebuildServerJob extends ClientJob { !jobs.any((final job) => job is RebuildServerJob); @override - Future<(bool, String)> execute(final JobsCubit cubit) async => + Future<(bool, String)> execute() async => (false, 'unimplemented'); @override @@ -74,7 +73,7 @@ class UpgradeServerJob extends ClientJob { !jobs.any((final job) => job is UpgradeServerJob); @override - Future<(bool, String)> execute(final JobsCubit cubit) async => + Future<(bool, String)> execute() async => (false, 'unimplemented'); @override @@ -101,7 +100,7 @@ class RebootServerJob extends ClientJob { !jobs.any((final job) => job is RebootServerJob); @override - Future<(bool, String)> execute(final JobsCubit cubit) async => + Future<(bool, String)> execute() async => (false, 'unimplemented'); @override @@ -127,7 +126,7 @@ class CreateUserJob extends ClientJob { final User user; @override - Future<(bool, String)> execute(final JobsCubit cubit) async => + Future<(bool, String)> execute() async => getIt().createUser(user); @override @@ -157,7 +156,7 @@ class ResetUserPasswordJob extends ClientJob { final User user; @override - Future<(bool, String)> execute(final JobsCubit cubit) async => + Future<(bool, String)> execute() async => getIt().changeUserPassword(user, user.password!); @override @@ -192,7 +191,7 @@ class DeleteUserJob extends ClientJob { ); @override - Future<(bool, String)> execute(final JobsCubit cubit) async => + Future<(bool, String)> execute() async => getIt().deleteUser(user); @override @@ -232,8 +231,8 @@ class ServiceToggleJob extends ClientJob { ); @override - Future<(bool, String)> execute(final JobsCubit cubit) async { - await cubit.api.switchService(service.id, needToTurnOn); + Future<(bool, String)> execute() async { + await getIt().api.switchService(service.id, needToTurnOn); return (true, 'Check not implemented'); } @@ -267,7 +266,7 @@ class CreateSSHKeyJob extends ClientJob { final String publicKey; @override - Future<(bool, String)> execute(final JobsCubit cubit) async => + Future<(bool, String)> execute() async => getIt().addSshKey(user, publicKey); @override @@ -308,7 +307,7 @@ class DeleteSSHKeyJob extends ClientJob { ); @override - Future<(bool, String)> execute(final JobsCubit cubit) async => + Future<(bool, String)> execute() async => getIt().deleteSshKey(user, publicKey); @override @@ -352,8 +351,8 @@ class ChangeAutoUpgradeSettingsJob extends ReplaceableJob { final bool allowReboot; @override - Future<(bool, String)> execute(final JobsCubit cubit) async { - await cubit.api.setAutoUpgradeSettings( + Future<(bool, String)> execute() async { + await getIt().api.setAutoUpgradeSettings( AutoUpgradeSettings(enable: enable, allowReboot: allowReboot), ); return (true, 'Check not implemented'); @@ -393,7 +392,7 @@ class ChangeServerTimezoneJob extends ReplaceableJob { final String timezone; @override - Future<(bool, String)> execute(final JobsCubit cubit) async { + Future<(bool, String)> execute() async { await getIt().api.setTimezone(timezone); return (true, 'Check not implemented'); } From 92cf2cde6d16ab5d72f2fc312dcacfe597d8307d Mon Sep 17 00:00:00 2001 From: Inex Code Date: Tue, 20 Feb 2024 20:09:14 +0300 Subject: [PATCH 29/34] refactor: Refactor ServerDetailsCubit to use ApiConnectionRepository --- .../server_detailed_info_cubit.dart | 71 +++++++++++++++---- .../server_detailed_info_repository.dart | 54 -------------- .../server_detailed_info_state.dart | 63 +++++++++++++--- .../get_it/api_connection_repository.dart | 9 +++ lib/logic/models/job.dart | 19 ++--- 5 files changed, 130 insertions(+), 86 deletions(-) delete mode 100644 lib/logic/cubit/server_detailed_info/server_detailed_info_repository.dart diff --git a/lib/logic/cubit/server_detailed_info/server_detailed_info_cubit.dart b/lib/logic/cubit/server_detailed_info/server_detailed_info_cubit.dart index 0ffe7766..a2b53f59 100644 --- a/lib/logic/cubit/server_detailed_info/server_detailed_info_cubit.dart +++ b/lib/logic/cubit/server_detailed_info/server_detailed_info_cubit.dart @@ -1,34 +1,75 @@ +import 'dart:async'; + import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/logic/cubit/server_connection_dependent/server_connection_dependent_cubit.dart'; -import 'package:selfprivacy/logic/cubit/server_detailed_info/server_detailed_info_repository.dart'; import 'package:selfprivacy/logic/models/auto_upgrade_settings.dart'; import 'package:selfprivacy/logic/models/server_metadata.dart'; +import 'package:selfprivacy/logic/models/system_settings.dart'; import 'package:selfprivacy/logic/models/timezone_settings.dart'; +import 'package:selfprivacy/logic/providers/providers_controller.dart'; part 'server_detailed_info_state.dart'; class ServerDetailsCubit extends ServerConnectionDependentCubit { - ServerDetailsCubit() : super(ServerDetailsInitial()); + ServerDetailsCubit() : super(const ServerDetailsInitial()) { + final apiConnectionRepository = getIt(); + _apiDataSubscription = apiConnectionRepository.dataStream.listen( + (final ApiData apiData) { + if (apiData.settings.data != null) { + _handleServerSettings(apiData.settings.data!); + } + }, + ); + } - ServerDetailsRepository repository = ServerDetailsRepository(); + StreamSubscription? _apiDataSubscription; + + void _handleServerSettings(final SystemSettings settings) { + emit( + Loaded( + metadata: state.metadata, + serverTimezone: TimeZoneSettings.fromString(settings.timezone), + autoUpgradeSettings: settings.autoUpgradeSettings, + ), + ); + } + + Future> get _metadata async { + List data = []; + + final serverProviderApi = ProvidersController.currentServerProvider; + final dnsProviderApi = ProvidersController.currentDnsProvider; + if (serverProviderApi != null && dnsProviderApi != null) { + final serverId = getIt().serverDetails?.id ?? 0; + final metadataResult = await serverProviderApi.getMetadata(serverId); + metadataResult.data.add( + ServerMetadataEntity( + trId: 'server.dns_provider', + value: dnsProviderApi.type.displayName, + type: MetadataType.other, + ), + ); + + data = metadataResult.data; + } + + return data; + } void check() async { final bool isReadyToCheck = getIt().serverDetails != null; try { if (isReadyToCheck) { - emit(ServerDetailsLoading()); - final ServerDetailsRepositoryDto data = await repository.load(); + emit(const ServerDetailsLoading()); + final List metadata = await _metadata; emit( - Loaded( - metadata: data.metadata, - autoUpgradeSettings: data.autoUpgradeSettings, - serverTimezone: data.serverTimezone, - checkTime: DateTime.now(), + state.copyWith( + metadata: metadata, ), ); } else { - emit(ServerDetailsNotReady()); + emit(const ServerDetailsNotReady()); } } on StateError { print('Tried to emit server info state when cubit is closed'); @@ -37,11 +78,17 @@ class ServerDetailsCubit @override void clear() { - emit(ServerDetailsNotReady()); + emit(const ServerDetailsNotReady()); } @override void load() async { check(); } + + @override + Future close() { + _apiDataSubscription?.cancel(); + return super.close(); + } } diff --git a/lib/logic/cubit/server_detailed_info/server_detailed_info_repository.dart b/lib/logic/cubit/server_detailed_info/server_detailed_info_repository.dart deleted file mode 100644 index 54540e5f..00000000 --- a/lib/logic/cubit/server_detailed_info/server_detailed_info_repository.dart +++ /dev/null @@ -1,54 +0,0 @@ -import 'package:selfprivacy/config/get_it_config.dart'; -import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart'; -import 'package:selfprivacy/logic/models/auto_upgrade_settings.dart'; -import 'package:selfprivacy/logic/models/server_metadata.dart'; -import 'package:selfprivacy/logic/models/timezone_settings.dart'; -import 'package:selfprivacy/logic/providers/providers_controller.dart'; - -class ServerDetailsRepository { - ServerApi server = ServerApi(); - - Future load() async { - final settings = await server.getSystemSettings(); - return ServerDetailsRepositoryDto( - autoUpgradeSettings: settings.autoUpgradeSettings, - metadata: await metadata, - serverTimezone: TimeZoneSettings.fromString( - settings.timezone, - ), - ); - } - - Future> get metadata async { - List data = []; - - final serverProviderApi = ProvidersController.currentServerProvider; - final dnsProviderApi = ProvidersController.currentDnsProvider; - if (serverProviderApi != null && dnsProviderApi != null) { - final serverId = getIt().serverDetails?.id ?? 0; - final metadataResult = await serverProviderApi.getMetadata(serverId); - metadataResult.data.add( - ServerMetadataEntity( - trId: 'server.dns_provider', - value: dnsProviderApi.type.displayName, - type: MetadataType.other, - ), - ); - - data = metadataResult.data; - } - - return data; - } -} - -class ServerDetailsRepositoryDto { - ServerDetailsRepositoryDto({ - required this.metadata, - required this.serverTimezone, - required this.autoUpgradeSettings, - }); - final List metadata; - final TimeZoneSettings serverTimezone; - final AutoUpgradeSettings autoUpgradeSettings; -} diff --git a/lib/logic/cubit/server_detailed_info/server_detailed_info_state.dart b/lib/logic/cubit/server_detailed_info/server_detailed_info_state.dart index 64f4d91d..8fb3a6c7 100644 --- a/lib/logic/cubit/server_detailed_info/server_detailed_info_state.dart +++ b/lib/logic/cubit/server_detailed_info/server_detailed_info_state.dart @@ -1,37 +1,78 @@ part of 'server_detailed_info_cubit.dart'; abstract class ServerDetailsState extends ServerInstallationDependendState { - const ServerDetailsState(); + const ServerDetailsState({ + required this.metadata, + }); + + final List metadata; @override - List get props => []; + List get props => [metadata]; + + ServerDetailsState copyWith({ + final List? metadata, + }); } -class ServerDetailsInitial extends ServerDetailsState {} +class ServerDetailsInitial extends ServerDetailsState { + const ServerDetailsInitial({super.metadata = const []}); -class ServerDetailsLoading extends ServerDetailsState {} + @override + ServerDetailsInitial copyWith({final List? metadata}) => + ServerDetailsInitial( + metadata: metadata ?? this.metadata, + ); +} -class ServerDetailsNotReady extends ServerDetailsState {} +class ServerDetailsLoading extends ServerDetailsState { + const ServerDetailsLoading({super.metadata = const []}); -class Loading extends ServerDetailsState {} + @override + ServerDetailsLoading copyWith({final List? metadata}) => + ServerDetailsLoading( + metadata: metadata ?? this.metadata, + ); +} + +class ServerDetailsNotReady extends ServerDetailsState { + const ServerDetailsNotReady({super.metadata = const []}); + + @override + ServerDetailsNotReady copyWith({ + final List? metadata, + }) => + ServerDetailsNotReady( + metadata: metadata ?? this.metadata, + ); +} class Loaded extends ServerDetailsState { const Loaded({ - required this.metadata, + required super.metadata, required this.serverTimezone, required this.autoUpgradeSettings, - required this.checkTime, }); - final List metadata; final TimeZoneSettings serverTimezone; final AutoUpgradeSettings autoUpgradeSettings; - final DateTime checkTime; @override List get props => [ metadata, serverTimezone, autoUpgradeSettings, - checkTime, ]; + + @override + Loaded copyWith({ + final List? metadata, + final TimeZoneSettings? serverTimezone, + final AutoUpgradeSettings? autoUpgradeSettings, + final DateTime? checkTime, + }) => + Loaded( + metadata: metadata ?? this.metadata, + serverTimezone: serverTimezone ?? this.serverTimezone, + autoUpgradeSettings: autoUpgradeSettings ?? this.autoUpgradeSettings, + ); } diff --git a/lib/logic/get_it/api_connection_repository.dart b/lib/logic/get_it/api_connection_repository.dart index eb818f35..3096874c 100644 --- a/lib/logic/get_it/api_connection_repository.dart +++ b/lib/logic/get_it/api_connection_repository.dart @@ -15,6 +15,7 @@ import 'package:selfprivacy/logic/models/json/recovery_token_status.dart'; import 'package:selfprivacy/logic/models/json/server_disk_volume.dart'; import 'package:selfprivacy/logic/models/json/server_job.dart'; import 'package:selfprivacy/logic/models/service.dart'; +import 'package:selfprivacy/logic/models/system_settings.dart'; /// Repository for all API calls /// Stores the current state of all data from API and exposes it to Blocs. @@ -226,6 +227,7 @@ class ApiConnectionRepository { await _apiData.recoveryKeyStatus.fetchData(); _apiData.devices.data = await _apiData.devices.fetchData(); _apiData.users.data = await _apiData.users.fetchData(); + _apiData.settings.data = await _apiData.settings.fetchData(); _dataStream.add(_apiData); connectionStatus = ConnectionStatus.connected; @@ -270,6 +272,8 @@ class ApiConnectionRepository { await _apiData.devices .refetchData(version, () => _dataStream.add(_apiData)); await _apiData.users.refetchData(version, () => _dataStream.add(_apiData)); + await _apiData.settings + .refetchData(version, () => _dataStream.add(_apiData)); } void emitData() { @@ -312,6 +316,10 @@ class ApiData { ), users = ApiDataElement>( fetchData: () async => api.getAllUsers(), + ), + settings = ApiDataElement( + fetchData: () async => api.getSystemSettings(), + ttl: 600, ); ApiDataElement> serverJobs; @@ -323,6 +331,7 @@ class ApiData { ApiDataElement recoveryKeyStatus; ApiDataElement> devices; ApiDataElement> users; + ApiDataElement settings; } enum ConnectionStatus { diff --git a/lib/logic/models/job.dart b/lib/logic/models/job.dart index bce20d14..81248975 100644 --- a/lib/logic/models/job.dart +++ b/lib/logic/models/job.dart @@ -49,8 +49,7 @@ class RebuildServerJob extends ClientJob { !jobs.any((final job) => job is RebuildServerJob); @override - Future<(bool, String)> execute() async => - (false, 'unimplemented'); + Future<(bool, String)> execute() async => (false, 'unimplemented'); @override RebuildServerJob copyWithNewStatus({ @@ -73,8 +72,7 @@ class UpgradeServerJob extends ClientJob { !jobs.any((final job) => job is UpgradeServerJob); @override - Future<(bool, String)> execute() async => - (false, 'unimplemented'); + Future<(bool, String)> execute() async => (false, 'unimplemented'); @override UpgradeServerJob copyWithNewStatus({ @@ -100,8 +98,7 @@ class RebootServerJob extends ClientJob { !jobs.any((final job) => job is RebootServerJob); @override - Future<(bool, String)> execute() async => - (false, 'unimplemented'); + Future<(bool, String)> execute() async => (false, 'unimplemented'); @override RebootServerJob copyWithNewStatus({ @@ -232,7 +229,9 @@ class ServiceToggleJob extends ClientJob { @override Future<(bool, String)> execute() async { - await getIt().api.switchService(service.id, needToTurnOn); + await getIt() + .api + .switchService(service.id, needToTurnOn); return (true, 'Check not implemented'); } @@ -353,8 +352,9 @@ class ChangeAutoUpgradeSettingsJob extends ReplaceableJob { @override Future<(bool, String)> execute() async { await getIt().api.setAutoUpgradeSettings( - AutoUpgradeSettings(enable: enable, allowReboot: allowReboot), - ); + AutoUpgradeSettings(enable: enable, allowReboot: allowReboot), + ); + getIt().apiData.settings.invalidate(); return (true, 'Check not implemented'); } @@ -394,6 +394,7 @@ class ChangeServerTimezoneJob extends ReplaceableJob { @override Future<(bool, String)> execute() async { await getIt().api.setTimezone(timezone); + getIt().apiData.settings.invalidate(); return (true, 'Check not implemented'); } From caa2fd3b8e3cb616f621be313579dea5315b2cc5 Mon Sep 17 00:00:00 2001 From: Inex Code Date: Tue, 20 Feb 2024 23:17:36 +0300 Subject: [PATCH 30/34] refactor: Handle situation when the job has to be removed Closes #166 --- assets/translations/en.json | 1 + .../graphql_maps/server_api/server_api.dart | 50 +++++++++++++++++-- .../cubit/client_jobs/client_jobs_state.dart | 31 +++++++++--- .../get_it/api_connection_repository.dart | 32 ++++++++++++ lib/logic/models/job.dart | 37 +++++++------- 5 files changed, 123 insertions(+), 28 deletions(-) diff --git a/assets/translations/en.json b/assets/translations/en.json index 14861d32..b89dbc58 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -597,6 +597,7 @@ "service_turn_on": "Turn on", "job_added": "Job added", "job_postponed": "Job added, but you will be able to launch it after current jobs are finished", + "job_removed": "Job removed", "run_jobs": "Run jobs", "reboot_success": "Server is rebooting", "reboot_failed": "Couldn't reboot the server. Check the app logs.", diff --git a/lib/logic/api_maps/graphql_maps/server_api/server_api.dart b/lib/logic/api_maps/graphql_maps/server_api/server_api.dart index b511b6a9..d94d7cf6 100644 --- a/lib/logic/api_maps/graphql_maps/server_api/server_api.dart +++ b/lib/logic/api_maps/graphql_maps/server_api/server_api.dart @@ -149,7 +149,7 @@ class ServerApi extends GraphQLApiMap } } - Future setAutoUpgradeSettings( + Future> setAutoUpgradeSettings( final AutoUpgradeSettings settings, ) async { try { @@ -164,13 +164,38 @@ class ServerApi extends GraphQLApiMap final mutation = Options$Mutation$ChangeAutoUpgradeSettings( variables: variables, ); - await client.mutate$ChangeAutoUpgradeSettings(mutation); + final result = await client.mutate$ChangeAutoUpgradeSettings(mutation); + if (result.hasException) { + return GenericResult( + success: false, + message: result.exception.toString(), + data: null, + ); + } + return GenericResult( + success: result.parsedData?.system.changeAutoUpgradeSettings.success ?? + false, + message: result.parsedData?.system.changeAutoUpgradeSettings.message, + data: result.parsedData == null + ? null + : AutoUpgradeSettings( + allowReboot: result + .parsedData!.system.changeAutoUpgradeSettings.allowReboot, + enable: result.parsedData!.system.changeAutoUpgradeSettings + .enableAutoUpgrade, + ), + ); } catch (e) { print(e); + return GenericResult( + success: false, + message: e.toString(), + data: null, + ); } } - Future setTimezone(final String timezone) async { + Future> setTimezone(final String timezone) async { try { final GraphQLClient client = await getClient(); final variables = Variables$Mutation$ChangeTimezone( @@ -179,9 +204,26 @@ class ServerApi extends GraphQLApiMap final mutation = Options$Mutation$ChangeTimezone( variables: variables, ); - await client.mutate$ChangeTimezone(mutation); + final result = await client.mutate$ChangeTimezone(mutation); + if (result.hasException) { + return GenericResult( + success: false, + message: result.exception.toString(), + data: '', + ); + } + return GenericResult( + success: result.parsedData?.system.changeTimezone.success ?? false, + message: result.parsedData?.system.changeTimezone.message, + data: result.parsedData?.system.changeTimezone.timezone, + ); } catch (e) { print(e); + return GenericResult( + success: false, + message: e.toString(), + data: '', + ); } } diff --git a/lib/logic/cubit/client_jobs/client_jobs_state.dart b/lib/logic/cubit/client_jobs/client_jobs_state.dart index 4f7244f5..c7ab2c0c 100644 --- a/lib/logic/cubit/client_jobs/client_jobs_state.dart +++ b/lib/logic/cubit/client_jobs/client_jobs_state.dart @@ -45,8 +45,15 @@ class JobsStateWithJobs extends JobsState { final List newJobsList = clientJobList .where((final element) => element.runtimeType != job.runtimeType) .toList(); - newJobsList.add(job); - getIt().showSnackBar('jobs.job_added'.tr()); + if (job.shouldRemoveInsteadOfAdd(clientJobList)) { + getIt().showSnackBar('jobs.job_removed'.tr()); + } else { + newJobsList.add(job); + getIt().showSnackBar('jobs.job_added'.tr()); + } + if (newJobsList.isEmpty) { + return JobsStateEmpty(); + } return JobsStateWithJobs(newJobsList); } if (job.canAddTo(clientJobList)) { @@ -102,13 +109,16 @@ class JobsStateLoading extends JobsState { @override JobsState addJob(final ClientJob job) { - // Do the same, but add jobs to the postponed list if (job is ReplaceableJob) { final List newPostponedJobs = postponedJobs .where((final element) => element.runtimeType != job.runtimeType) .toList(); - newPostponedJobs.add(job); - getIt().showSnackBar('jobs.job_postponed'.tr()); + if (job.shouldRemoveInsteadOfAdd(postponedJobs)) { + getIt().showSnackBar('jobs.job_removed'.tr()); + } else { + newPostponedJobs.add(job); + getIt().showSnackBar('jobs.job_postponed'.tr()); + } return JobsStateLoading(clientJobList, rebuildJobUid, newPostponedJobs); } if (job.canAddTo(postponedJobs)) { @@ -140,8 +150,15 @@ class JobsStateFinished extends JobsState { final List newPostponedJobs = postponedJobs .where((final element) => element.runtimeType != job.runtimeType) .toList(); - newPostponedJobs.add(job); - getIt().showSnackBar('jobs.job_added'.tr()); + if (job.shouldRemoveInsteadOfAdd(postponedJobs)) { + getIt().showSnackBar('jobs.job_removed'.tr()); + } else { + newPostponedJobs.add(job); + getIt().showSnackBar('jobs.job_added'.tr()); + } + if (newPostponedJobs.isEmpty) { + return JobsStateEmpty(); + } return JobsStateWithJobs(newPostponedJobs); } if (job.canAddTo(postponedJobs)) { diff --git a/lib/logic/get_it/api_connection_repository.dart b/lib/logic/get_it/api_connection_repository.dart index 3096874c..10c7fc0e 100644 --- a/lib/logic/get_it/api_connection_repository.dart +++ b/lib/logic/get_it/api_connection_repository.dart @@ -6,6 +6,7 @@ import 'package:pub_semver/pub_semver.dart'; import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/config/hive_config.dart'; import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart'; +import 'package:selfprivacy/logic/models/auto_upgrade_settings.dart'; import 'package:selfprivacy/logic/models/backup.dart'; import 'package:selfprivacy/logic/models/hive/server_details.dart'; import 'package:selfprivacy/logic/models/hive/server_domain.dart'; @@ -188,6 +189,37 @@ class ApiConnectionRepository { return (true, result.message ?? 'basis.done'.tr()); } + Future<(bool, String)> setAutoUpgradeSettings( + final bool enable, + final bool allowReboot, + ) async { + final GenericResult result = + await api.setAutoUpgradeSettings( + AutoUpgradeSettings( + enable: enable, + allowReboot: allowReboot, + ), + ); + _apiData.settings.invalidate(); + if (result.data != null) { + return (true, result.message ?? 'basis.done'.tr()); + } else { + return (false, result.message ?? 'jobs.generic_error'.tr()); + } + } + + Future<(bool, String)> setServerTimezone( + final String timezone, + ) async { + final GenericResult result = await api.setTimezone(timezone); + _apiData.settings.invalidate(); + if (result.success) { + return (true, result.message ?? 'basis.done'.tr()); + } else { + return (false, result.message ?? 'jobs.generic_error'.tr()); + } + } + void dispose() { _dataStream.close(); _connectionStatusStream.close(); diff --git a/lib/logic/models/job.dart b/lib/logic/models/job.dart index 81248975..fea658f9 100644 --- a/lib/logic/models/job.dart +++ b/lib/logic/models/job.dart @@ -2,7 +2,6 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; import 'package:selfprivacy/config/get_it_config.dart'; -import 'package:selfprivacy/logic/models/auto_upgrade_settings.dart'; import 'package:selfprivacy/logic/models/hive/user.dart'; import 'package:selfprivacy/logic/models/json/server_job.dart'; import 'package:selfprivacy/logic/models/service.dart'; @@ -350,18 +349,21 @@ class ChangeAutoUpgradeSettingsJob extends ReplaceableJob { final bool allowReboot; @override - Future<(bool, String)> execute() async { - await getIt().api.setAutoUpgradeSettings( - AutoUpgradeSettings(enable: enable, allowReboot: allowReboot), - ); - getIt().apiData.settings.invalidate(); - return (true, 'Check not implemented'); - } + Future<(bool, String)> execute() async => getIt() + .setAutoUpgradeSettings(enable, allowReboot); @override bool shouldRemoveInsteadOfAdd(final List jobs) { - // TODO: Finish this - throw UnimplementedError(); + final currentSettings = getIt() + .apiData + .settings + .data + ?.autoUpgradeSettings; + if (currentSettings == null) { + return false; + } + return currentSettings.enable == enable && + currentSettings.allowReboot == allowReboot; } @override @@ -392,16 +394,17 @@ class ChangeServerTimezoneJob extends ReplaceableJob { final String timezone; @override - Future<(bool, String)> execute() async { - await getIt().api.setTimezone(timezone); - getIt().apiData.settings.invalidate(); - return (true, 'Check not implemented'); - } + Future<(bool, String)> execute() async => + getIt().setServerTimezone(timezone); @override bool shouldRemoveInsteadOfAdd(final List jobs) { - // TODO: Finish this - throw UnimplementedError(); + final currentSettings = + getIt().apiData.settings.data?.timezone; + if (currentSettings == null) { + return false; + } + return currentSettings == timezone; } @override From 43a339af9111148182e8184cac35f4bcc5d50481 Mon Sep 17 00:00:00 2001 From: Inex Code Date: Tue, 20 Feb 2024 23:34:45 +0300 Subject: [PATCH 31/34] refactor: Code deduplication in server data reload --- .../get_it/api_connection_repository.dart | 56 +++++++++---------- 1 file changed, 27 insertions(+), 29 deletions(-) diff --git a/lib/logic/get_it/api_connection_repository.dart b/lib/logic/get_it/api_connection_repository.dart index 10c7fc0e..edc5e0ff 100644 --- a/lib/logic/get_it/api_connection_repository.dart +++ b/lib/logic/get_it/api_connection_repository.dart @@ -250,17 +250,7 @@ class ApiConnectionRepository { _dataStream.add(_apiData); } - _apiData.serverJobs.data = await _apiData.serverJobs.fetchData(); - _apiData.backupConfig.data = await _apiData.backupConfig.fetchData(); - _apiData.backups.data = await _apiData.backups.fetchData(); - _apiData.services.data = await _apiData.services.fetchData(); - _apiData.volumes.data = await _apiData.volumes.fetchData(); - _apiData.recoveryKeyStatus.data = - await _apiData.recoveryKeyStatus.fetchData(); - _apiData.devices.data = await _apiData.devices.fetchData(); - _apiData.users.data = await _apiData.users.fetchData(); - _apiData.settings.data = await _apiData.settings.fetchData(); - _dataStream.add(_apiData); + await _refetchEverything(Version.parse(apiVersion)); connectionStatus = ConnectionStatus.connected; _connectionStatusStream.add(connectionStatus); @@ -272,6 +262,27 @@ class ApiConnectionRepository { ); } + Future _refetchEverything(final Version version) async { + await _apiData.serverJobs + .refetchData(version, () => _dataStream.add(_apiData)); + await _apiData.backups + .refetchData(version, () => _dataStream.add(_apiData)); + await _apiData.backupConfig + .refetchData(version, () => _dataStream.add(_apiData)); + await _apiData.services + .refetchData(version, () => _dataStream.add(_apiData)); + await _apiData.volumes + .refetchData(version, () => _dataStream.add(_apiData)); + await _apiData.recoveryKeyStatus + .refetchData(version, () => _dataStream.add(_apiData)); + await _apiData.devices + .refetchData(version, () => _dataStream.add(_apiData)); + await _apiData.users.refetchData(version, () => _dataStream.add(_apiData)); + await _apiData.settings + .refetchData(version, () => _dataStream.add(_apiData)); + print(_apiData.services.data?.length); + } + Future reload(final Timer? timer) async { final serverDetails = getIt().serverDetails; if (serverDetails == null) { @@ -289,23 +300,7 @@ class ApiConnectionRepository { _apiData.apiVersion.data = apiVersion; } final Version version = Version.parse(apiVersion); - await _apiData.serverJobs - .refetchData(version, () => _dataStream.add(_apiData)); - await _apiData.backups - .refetchData(version, () => _dataStream.add(_apiData)); - await _apiData.backupConfig - .refetchData(version, () => _dataStream.add(_apiData)); - await _apiData.services - .refetchData(version, () => _dataStream.add(_apiData)); - await _apiData.volumes - .refetchData(version, () => _dataStream.add(_apiData)); - await _apiData.recoveryKeyStatus - .refetchData(version, () => _dataStream.add(_apiData)); - await _apiData.devices - .refetchData(version, () => _dataStream.add(_apiData)); - await _apiData.users.refetchData(version, () => _dataStream.add(_apiData)); - await _apiData.settings - .refetchData(version, () => _dataStream.add(_apiData)); + await _refetchEverything(version); } void emitData() { @@ -392,8 +387,11 @@ class ApiDataElement { final Version version, final Function callback, ) async { + print('Refetching data'); if (VersionConstraint.parse(requiredApiVersion).allows(version)) { - if (isExpired) { + print('Version is allowed'); + if (isExpired || _data == null) { + print('Data is expired'); final newData = await fetchData(); if (T is List) { if (Object.hashAll(newData as Iterable) != From 7bb96b5ed03738586427a3de02f6ef8c1de51628 Mon Sep 17 00:00:00 2001 From: Inex Code Date: Wed, 21 Feb 2024 00:45:32 +0300 Subject: [PATCH 32/34] chore: remove prints --- lib/logic/get_it/api_connection_repository.dart | 4 ---- 1 file changed, 4 deletions(-) diff --git a/lib/logic/get_it/api_connection_repository.dart b/lib/logic/get_it/api_connection_repository.dart index edc5e0ff..78968a45 100644 --- a/lib/logic/get_it/api_connection_repository.dart +++ b/lib/logic/get_it/api_connection_repository.dart @@ -280,7 +280,6 @@ class ApiConnectionRepository { await _apiData.users.refetchData(version, () => _dataStream.add(_apiData)); await _apiData.settings .refetchData(version, () => _dataStream.add(_apiData)); - print(_apiData.services.data?.length); } Future reload(final Timer? timer) async { @@ -387,11 +386,8 @@ class ApiDataElement { final Version version, final Function callback, ) async { - print('Refetching data'); if (VersionConstraint.parse(requiredApiVersion).allows(version)) { - print('Version is allowed'); if (isExpired || _data == null) { - print('Data is expired'); final newData = await fetchData(); if (T is List) { if (Object.hashAll(newData as Iterable) != From 160e6d3b356830383eb6a656d73b5880318772be Mon Sep 17 00:00:00 2001 From: Inex Code Date: Wed, 21 Feb 2024 05:00:45 +0300 Subject: [PATCH 33/34] refactor: Remove unused job --- .../graphql_maps/server_api/server_api.dart | 39 +++++++++++++++++-- lib/logic/models/job.dart | 27 +------------ 2 files changed, 37 insertions(+), 29 deletions(-) diff --git a/lib/logic/api_maps/graphql_maps/server_api/server_api.dart b/lib/logic/api_maps/graphql_maps/server_api/server_api.dart index d94d7cf6..31c09a74 100644 --- a/lib/logic/api_maps/graphql_maps/server_api/server_api.dart +++ b/lib/logic/api_maps/graphql_maps/server_api/server_api.dart @@ -132,20 +132,51 @@ class ServerApi extends GraphQLApiMap return usesBinds; } - Future switchService(final String uid, final bool needTurnOn) async { + Future switchService( + final String uid, + final bool needTurnOn, + ) async { try { final GraphQLClient client = await getClient(); if (needTurnOn) { final variables = Variables$Mutation$EnableService(serviceId: uid); final mutation = Options$Mutation$EnableService(variables: variables); - await client.mutate$EnableService(mutation); + final result = await client.mutate$EnableService(mutation); + if (result.hasException) { + return GenericResult( + success: false, + message: result.exception.toString(), + data: null, + ); + } + return GenericResult( + success: result.parsedData?.services.enableService.success ?? false, + message: result.parsedData?.services.enableService.message, + data: null, + ); } else { final variables = Variables$Mutation$DisableService(serviceId: uid); final mutation = Options$Mutation$DisableService(variables: variables); - await client.mutate$DisableService(mutation); + final result = await client.mutate$DisableService(mutation); + if (result.hasException) { + return GenericResult( + success: false, + message: result.exception.toString(), + data: null, + ); + } + return GenericResult( + success: result.parsedData?.services.disableService.success ?? false, + message: result.parsedData?.services.disableService.message, + data: null, + ); } } catch (e) { - print(e); + return GenericResult( + success: false, + message: e.toString(), + data: null, + ); } } diff --git a/lib/logic/models/job.dart b/lib/logic/models/job.dart index fea658f9..0cda1407 100644 --- a/lib/logic/models/job.dart +++ b/lib/logic/models/job.dart @@ -36,29 +36,6 @@ abstract class ClientJob extends Equatable { }); } -@Deprecated('Jobs bloc should handle it itself') -class RebuildServerJob extends ClientJob { - RebuildServerJob({ - required super.title, - super.id, - }); - - @override - bool canAddTo(final List jobs) => - !jobs.any((final job) => job is RebuildServerJob); - - @override - Future<(bool, String)> execute() async => (false, 'unimplemented'); - - @override - RebuildServerJob copyWithNewStatus({ - required final JobStatusEnum status, - final String? message, - }) { - throw UnimplementedError(); - } -} - class UpgradeServerJob extends ClientJob { UpgradeServerJob({ super.status, @@ -228,10 +205,10 @@ class ServiceToggleJob extends ClientJob { @override Future<(bool, String)> execute() async { - await getIt() + final result = await getIt() .api .switchService(service.id, needToTurnOn); - return (true, 'Check not implemented'); + return (result.success, result.message ?? 'jobs.generic_error'.tr()); } @override From 275e8b1f40cf7f733b8dfeaa88198fec136ac095 Mon Sep 17 00:00:00 2001 From: Inex Code Date: Fri, 23 Feb 2024 17:49:10 +0300 Subject: [PATCH 34/34] chore: Fixes from review --- lib/logic/bloc/backups/backups_state.dart | 2 +- lib/ui/pages/backups/backup_details.dart | 25 +---------------------- 2 files changed, 2 insertions(+), 25 deletions(-) diff --git a/lib/logic/bloc/backups/backups_state.dart b/lib/logic/bloc/backups/backups_state.dart index e41c3b74..7cdfe023 100644 --- a/lib/logic/bloc/backups/backups_state.dart +++ b/lib/logic/bloc/backups/backups_state.dart @@ -131,7 +131,7 @@ class BackupsInitialized extends BackupsState { final List list = _backupList; list.sort((final a, final b) => b.time.compareTo(a.time)); return list; - } on UnsupportedError { + } catch (_) { return _backupList; } } diff --git a/lib/ui/pages/backups/backup_details.dart b/lib/ui/pages/backups/backup_details.dart index 6eb9d009..dad45db4 100644 --- a/lib/ui/pages/backups/backup_details.dart +++ b/lib/ui/pages/backups/backup_details.dart @@ -419,30 +419,7 @@ class BackupDetailsPage extends StatelessWidget { .read() .add(const ForceSnapshotListUpdate()), ), - const SizedBox(height: 8), - const Divider(), - const SizedBox(height: 8), - ListTile( - title: Text( - 'backup.reupload_key'.tr(), - style: TextStyle( - color: overrideColor, - ), - ), - subtitle: Text( - 'backup.reupload_key_subtitle'.tr(), - style: TextStyle( - color: overrideColor, - ), - ), - leading: Icon( - Icons.warning_amber_outlined, - color: overrideColor, - ), - // onTap: preventActions - // ? null - // : () => context.read().reuploadKey(), - ), + // TODO: Return reupload key button in some form ], ), ],