From fa21bdf034827512a4c77c8177fbf0beb2ed9e4f Mon Sep 17 00:00:00 2001 From: Inex Code Date: Fri, 26 Jan 2024 14:43:44 +0400 Subject: [PATCH 01/51] 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/51] 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/51] 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/51] 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/51] 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/51] 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/51] 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/51] 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/51] 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/51] 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/51] 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/51] 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/51] 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/51] 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/51] 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/51] 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/51] 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/51] 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 40f4f8822f3c9b0f0bc2f8318c047ea9843026b2 Mon Sep 17 00:00:00 2001 From: Aliaksei Tratseuski Date: Wed, 31 Jan 2024 06:34:15 +0400 Subject: [PATCH 19/51] chore: segmented_buttons rewrite --- .../components/buttons/segmented_buttons.dart | 84 +++++++++++-------- 1 file changed, 49 insertions(+), 35 deletions(-) diff --git a/lib/ui/components/buttons/segmented_buttons.dart b/lib/ui/components/buttons/segmented_buttons.dart index b876f71d..adb3a32b 100644 --- a/lib/ui/components/buttons/segmented_buttons.dart +++ b/lib/ui/components/buttons/segmented_buttons.dart @@ -46,41 +46,55 @@ class SegmentedButtons extends StatelessWidget { color: Theme.of(context).colorScheme.onSurface, isSelected: isSelected, onPressed: onPressed, - children: titles.asMap().entries.map((final entry) { - final index = entry.key; - final title = entry.value; - return Stack( - alignment: Alignment.centerLeft, - children: [ - AnimatedOpacity( - duration: const Duration(milliseconds: 200), - opacity: isSelected[index] ? 1 : 0, - child: AnimatedScale( - duration: const Duration(milliseconds: 200), - curve: Curves.easeInOutCubicEmphasized, - alignment: Alignment.centerLeft, - scale: isSelected[index] ? 1 : 0, - child: Icon( - Icons.check, - size: 18, - color: Theme.of(context).colorScheme.onSecondaryContainer, - ), - ), - ), - AnimatedPadding( - padding: isSelected[index] - ? const EdgeInsets.only(left: 24) - : EdgeInsets.zero, - duration: const Duration(milliseconds: 200), - curve: Curves.easeInOutCubicEmphasized, - child: Text( - title, - style: Theme.of(context).textTheme.labelLarge, - ), - ), - ], - ); - }).toList(), + children: [ + for (int i = 0; i < titles.length; i++) + _ButtonSegment( + isSelected: isSelected[i], + title: titles[i], + ), + ], ), ); } + +class _ButtonSegment extends StatelessWidget { + const _ButtonSegment({ + required this.isSelected, + required this.title, + }); + + final bool isSelected; + final String title; + + @override + Widget build(final BuildContext context) => Stack( + alignment: Alignment.centerLeft, + children: [ + AnimatedOpacity( + duration: const Duration(milliseconds: 200), + opacity: isSelected ? 1 : 0, + child: AnimatedScale( + duration: const Duration(milliseconds: 200), + curve: Curves.easeInOutCubicEmphasized, + alignment: Alignment.centerLeft, + scale: isSelected ? 1 : 0, + child: Icon( + Icons.check, + size: 18, + color: Theme.of(context).colorScheme.onSecondaryContainer, + ), + ), + ), + AnimatedPadding( + padding: + isSelected ? const EdgeInsets.only(left: 24) : EdgeInsets.zero, + duration: const Duration(milliseconds: 200), + curve: Curves.easeInOutCubicEmphasized, + child: Text( + title, + style: Theme.of(context).textTheme.labelLarge, + ), + ), + ], + ); +} From 370186030ad74c33aa285db7e65fc3ba0b579a88 Mon Sep 17 00:00:00 2001 From: Aliaksei Tratseuski Date: Wed, 31 Jan 2024 06:46:11 +0400 Subject: [PATCH 20/51] added keys to segmented_buttons _ButtonSegment's --- lib/ui/components/buttons/segmented_buttons.dart | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/ui/components/buttons/segmented_buttons.dart b/lib/ui/components/buttons/segmented_buttons.dart index adb3a32b..3afb2d27 100644 --- a/lib/ui/components/buttons/segmented_buttons.dart +++ b/lib/ui/components/buttons/segmented_buttons.dart @@ -49,6 +49,7 @@ class SegmentedButtons extends StatelessWidget { children: [ for (int i = 0; i < titles.length; i++) _ButtonSegment( + key: ValueKey(i), isSelected: isSelected[i], title: titles[i], ), @@ -61,6 +62,7 @@ class _ButtonSegment extends StatelessWidget { const _ButtonSegment({ required this.isSelected, required this.title, + super.key, }); final bool isSelected; From 6914b01d2a8aea1260ad7aeb2a8b04e539407bf2 Mon Sep 17 00:00:00 2001 From: Inex Code Date: Tue, 6 Feb 2024 18:21:21 +0300 Subject: [PATCH 21/51] 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 98228cfc0514e6ab88e3037a0a895f65a3a5db48 Mon Sep 17 00:00:00 2001 From: Inex Code Date: Wed, 7 Feb 2024 13:39:41 +0300 Subject: [PATCH 22/51] fix(hetzner): Fix the resize volume request --- .../rest_maps/server_providers/hetzner/hetzner_api.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner_api.dart b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner_api.dart index 2bc91833..84e65cd7 100644 --- a/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner_api.dart +++ b/lib/logic/api_maps/rest_maps/server_providers/hetzner/hetzner_api.dart @@ -547,7 +547,7 @@ class HetznerApi extends RestApiMap { resizeVolumeResponse = await client.post( '/volumes/${volume.id}/actions/resize', data: { - 'size': size.gibibyte, + 'size': size.gibibyte.floor(), }, ); success = From 74675cab23d98970cd9beac817ce9c9a078c8bf2 Mon Sep 17 00:00:00 2001 From: Inex Code Date: Wed, 7 Feb 2024 13:47:22 +0300 Subject: [PATCH 23/51] chore: Bump version to 0.10.1 --- appimage.yml | 2 +- .../metadata/android/en-US/changelogs/0.10.1.txt | 15 +++++++++++++++ pubspec.yaml | 2 +- 3 files changed, 17 insertions(+), 2 deletions(-) create mode 100644 fastlane/metadata/android/en-US/changelogs/0.10.1.txt diff --git a/appimage.yml b/appimage.yml index 9914346f..d037b747 100644 --- a/appimage.yml +++ b/appimage.yml @@ -10,7 +10,7 @@ AppDir: id: org.selfprivacy.app name: SelfPrivacy icon: org.selfprivacy.app - version: 0.10.0 + version: 0.10.1 exec: selfprivacy exec_args: $@ apt: diff --git a/fastlane/metadata/android/en-US/changelogs/0.10.1.txt b/fastlane/metadata/android/en-US/changelogs/0.10.1.txt new file mode 100644 index 00000000..168842f1 --- /dev/null +++ b/fastlane/metadata/android/en-US/changelogs/0.10.1.txt @@ -0,0 +1,15 @@ +### Features + +- Enabled the following languages: + - Azerbaijani + - Belarusian + - Hebrew + - Latvian + - Macedonian + - Slovak + - Slovenian + +### Bug Fixes + +- **Hetzner**: Fixed an issue where could not resize a volume on Hetzner ([#456](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/456), resolves [#455](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/455)) +- **DNS**: Make sure that we notice domain ownership lost ([#441](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/441), resolves [#390](https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues/390)) diff --git a/pubspec.yaml b/pubspec.yaml index 12a071d1..0c1641a6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,7 +1,7 @@ name: selfprivacy description: selfprivacy.org publish_to: 'none' -version: 0.10.0+20 +version: 0.10.1+21 environment: sdk: '>=3.2.1 <4.0.0' From ba0e247fbacb25b0824ac18f51e6d21c81ff18f7 Mon Sep 17 00:00:00 2001 From: dettlaff Date: Thu, 8 Feb 2024 00:06:55 +0400 Subject: [PATCH 24/51] fix: remove SnackBarBehaviov --- lib/ui/pages/backups/snapshot_id_list_tile.dart | 1 - lib/ui/pages/users/new_user.dart | 1 - lib/ui/pages/users/user_details.dart | 1 - 3 files changed, 3 deletions(-) diff --git a/lib/ui/pages/backups/snapshot_id_list_tile.dart b/lib/ui/pages/backups/snapshot_id_list_tile.dart index 802b90ee..22d35d34 100644 --- a/lib/ui/pages/backups/snapshot_id_list_tile.dart +++ b/lib/ui/pages/backups/snapshot_id_list_tile.dart @@ -17,7 +17,6 @@ class SnapshotIdListTile extends StatelessWidget { PlatformAdapter.setClipboard(snapshotId); getIt().showSnackBar( 'basis.copied_to_clipboard'.tr(), - behavior: SnackBarBehavior.floating, ); }, leading: Icon( diff --git a/lib/ui/pages/users/new_user.dart b/lib/ui/pages/users/new_user.dart index 2315bdb1..d35efbbd 100644 --- a/lib/ui/pages/users/new_user.dart +++ b/lib/ui/pages/users/new_user.dart @@ -89,7 +89,6 @@ class NewUserPage extends StatelessWidget { PlatformAdapter.setClipboard(currentPassword); getIt().showSnackBar( 'basis.copied_to_clipboard'.tr(), - behavior: SnackBarBehavior.floating, ); }, ), diff --git a/lib/ui/pages/users/user_details.dart b/lib/ui/pages/users/user_details.dart index 9523ccf4..88727c7e 100644 --- a/lib/ui/pages/users/user_details.dart +++ b/lib/ui/pages/users/user_details.dart @@ -153,7 +153,6 @@ class _UserLogins extends StatelessWidget { PlatformAdapter.setClipboard(email); getIt().showSnackBar( 'basis.copied_to_clipboard'.tr(), - behavior: SnackBarBehavior.floating, ); }, title: email, From c67661ff65b38a324f2fb45191af88f2beb656ff Mon Sep 17 00:00:00 2001 From: dettlaff Date: Thu, 8 Feb 2024 00:19:27 +0400 Subject: [PATCH 25/51] feat: change NavigationDestinationLabelBehavior --- lib/ui/layouts/root_scaffold_with_navigation.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ui/layouts/root_scaffold_with_navigation.dart b/lib/ui/layouts/root_scaffold_with_navigation.dart index 40f01e9f..2278bb4e 100644 --- a/lib/ui/layouts/root_scaffold_with_navigation.dart +++ b/lib/ui/layouts/root_scaffold_with_navigation.dart @@ -201,7 +201,7 @@ class _BottomBar extends StatelessWidget { ), child: NavigationBar( selectedIndex: prevActiveIndex == -1 ? 0 : prevActiveIndex, - labelBehavior: NavigationDestinationLabelBehavior.onlyShowSelected, + labelBehavior: NavigationDestinationLabelBehavior.alwaysShow, onDestinationSelected: (final index) { context.router.replaceAll([destinations[index].route]); }, From dd81053f4298f332ef379ff1821afae2210e3511 Mon Sep 17 00:00:00 2001 From: aliaksei tratseuski Date: Thu, 8 Feb 2024 13:59:52 +0200 Subject: [PATCH 26/51] refactor(UI): Rewrite onboarding page rewrote OnboardingPage: * decomposed into separate widgets * now content stays centered on wide screens (set so width won't expand further than 480px) * pageController is now properly disposed * added some more code changes to * main (error widget builder) * brand_header (centerTitle instead of empty actions list) * console_page (listener callback fix, used gaps instead of SizedBox'es, added keys to list items) * service_page (just cleaner build method) * removed some dead code Co-authored-by: Aliaksei Tratseuski Reviewed-on: https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/pulls/444 Co-authored-by: aliaksei tratseuski Co-committed-by: aliaksei tratseuski --- lib/config/md_files.dart | 1 - lib/logic/models/message.dart | 7 +- lib/main.dart | 8 +- .../components/brand_header/brand_header.dart | 4 +- lib/ui/components/buttons/brand_button.dart | 10 +- lib/ui/components/buttons/buttons.dart | 2 + .../components/buttons/sp_brand_button.dart | 28 ++++ .../components/progress_bar/progress_bar.dart | 71 --------- lib/ui/pages/more/console.dart | 47 +++--- lib/ui/pages/onboarding/onboarding.dart | 149 ++---------------- .../views/onboarding_first_view.dart | 50 ++++++ .../views/onboarding_second_view.dart | 60 +++++++ .../onboarding/views/onboarding_view.dart | 53 +++++++ lib/ui/pages/onboarding/views/views.dart | 2 + lib/ui/pages/root_route.dart | 6 - lib/ui/pages/server_details/text_details.dart | 2 - lib/ui/pages/services/service_page.dart | 109 +++++-------- lib/utils/ui_helpers.dart | 8 +- pubspec.lock | 16 +- pubspec.yaml | 1 + 20 files changed, 312 insertions(+), 322 deletions(-) delete mode 100644 lib/config/md_files.dart create mode 100644 lib/ui/components/buttons/buttons.dart create mode 100644 lib/ui/components/buttons/sp_brand_button.dart create mode 100644 lib/ui/pages/onboarding/views/onboarding_first_view.dart create mode 100644 lib/ui/pages/onboarding/views/onboarding_second_view.dart create mode 100644 lib/ui/pages/onboarding/views/onboarding_view.dart create mode 100644 lib/ui/pages/onboarding/views/views.dart diff --git a/lib/config/md_files.dart b/lib/config/md_files.dart deleted file mode 100644 index 8b137891..00000000 --- a/lib/config/md_files.dart +++ /dev/null @@ -1 +0,0 @@ - diff --git a/lib/logic/models/message.dart b/lib/logic/models/message.dart index aaaf0930..b722d464 100644 --- a/lib/logic/models/message.dart +++ b/lib/logic/models/message.dart @@ -1,8 +1,7 @@ import 'package:graphql/client.dart'; import 'package:intl/intl.dart'; -final DateFormat formatter = DateFormat('hh:mm'); - +/// TODO(misterfourtytwo): add equality override class Message { Message({this.text, this.severity = MessageSeverity.normal}) : time = DateTime.now(); @@ -13,7 +12,9 @@ class Message { final String? text; final DateTime time; final MessageSeverity severity; - String get timeString => formatter.format(time); + + static final DateFormat _formatter = DateFormat('hh:mm'); + String get timeString => _formatter.format(time); } enum MessageSeverity { diff --git a/lib/main.dart b/lib/main.dart index 83ca4708..13b05aad 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -92,13 +92,15 @@ class SelfprivacyApp extends StatelessWidget { ? ThemeMode.dark : ThemeMode.light, builder: (final BuildContext context, final Widget? widget) { - Widget error = const Text('...rendering error...'); + Widget error = + const Center(child: Text('...rendering error...')); if (widget is Scaffold || widget is Navigator) { - error = Scaffold(body: Center(child: error)); + error = Scaffold(body: error); } ErrorWidget.builder = (final FlutterErrorDetails errorDetails) => error; - return widget!; + + return widget ?? error; }, ); }, diff --git a/lib/ui/components/brand_header/brand_header.dart b/lib/ui/components/brand_header/brand_header.dart index 3151aff7..56be04df 100644 --- a/lib/ui/components/brand_header/brand_header.dart +++ b/lib/ui/components/brand_header/brand_header.dart @@ -14,6 +14,7 @@ class BrandHeader extends StatelessWidget { @override Widget build(final BuildContext context) => AppBar( + centerTitle: true, title: Padding( padding: const EdgeInsets.only(top: 4.0), child: Text(title), @@ -25,8 +26,5 @@ class BrandHeader extends StatelessWidget { onBackButtonPressed ?? () => Navigator.of(context).pop(), ) : null, - actions: const [ - SizedBox.shrink(), - ], ); } diff --git a/lib/ui/components/buttons/brand_button.dart b/lib/ui/components/buttons/brand_button.dart index c381af43..bb2e722a 100644 --- a/lib/ui/components/buttons/brand_button.dart +++ b/lib/ui/components/buttons/brand_button.dart @@ -7,8 +7,9 @@ class BrandButton { final String? text, final Widget? child, }) { - assert(text == null || child == null, 'required title or child'); - assert(text != null || child != null, 'required title or child'); + assert((text ?? child) != null, 'either title or child must not be empty'); + assert(text != null || child != null, 'title or child must be provided'); + return ConstrainedBox( constraints: const BoxConstraints( minHeight: 48, @@ -28,8 +29,9 @@ class BrandButton { final String? text, final Widget? child, }) { - assert(text == null || child == null, 'required title or child'); - assert(text != null || child != null, 'required title or child'); + assert((text ?? child) != null, 'either title or child must not be empty'); + assert(text != null || child != null, 'title or child must be provided'); + return ConstrainedBox( constraints: const BoxConstraints( minWidth: double.infinity, diff --git a/lib/ui/components/buttons/buttons.dart b/lib/ui/components/buttons/buttons.dart new file mode 100644 index 00000000..49e0bb07 --- /dev/null +++ b/lib/ui/components/buttons/buttons.dart @@ -0,0 +1,2 @@ +export 'brand_button.dart'; +export 'sp_brand_button.dart'; diff --git a/lib/ui/components/buttons/sp_brand_button.dart b/lib/ui/components/buttons/sp_brand_button.dart new file mode 100644 index 00000000..036b30f2 --- /dev/null +++ b/lib/ui/components/buttons/sp_brand_button.dart @@ -0,0 +1,28 @@ +import 'package:flutter/material.dart'; + +class SPBrandButton extends StatelessWidget { + const SPBrandButton({ + required this.child, + required this.onPressed, + super.key, + }); + + SPBrandButton.text({ + required final String title, + required this.onPressed, + super.key, + }) : child = Text(title); + + final Widget child; + final VoidCallback onPressed; + + @override + Widget build(final BuildContext context) => FilledButton( + // TODO(misterfourtytwo): move button styles to theme configuration + style: const ButtonStyle( + minimumSize: MaterialStatePropertyAll(Size.fromHeight(48)), + ), + onPressed: onPressed, + child: child, + ); +} diff --git a/lib/ui/components/progress_bar/progress_bar.dart b/lib/ui/components/progress_bar/progress_bar.dart index 1861bd0b..d1c00a24 100644 --- a/lib/ui/components/progress_bar/progress_bar.dart +++ b/lib/ui/components/progress_bar/progress_bar.dart @@ -1,5 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:selfprivacy/logic/cubit/app_settings/app_settings_cubit.dart'; class ProgressBar extends StatefulWidget { const ProgressBar({ @@ -21,41 +20,6 @@ class _ProgressBarState extends State { Widget build(final BuildContext context) { final double progress = 1 / widget.steps.length * (widget.activeIndex + 0.3); - final bool isDark = context.watch().state.isDarkModeOn; - final TextStyle style = - isDark ? progressTextStyleDark : progressTextStyleLight; - - final Iterable allSteps = widget.steps.asMap().map( - (final i, final step) { - final Container value = _stepTitle(index: i, style: style, step: step); - return MapEntry(i, value); - }, - ).values; - - final List odd = []; - final List even = []; - - int i = 0; - for (final Container step in allSteps) { - if (i.isEven) { - even.add(step); - } else { - odd.add(step); - } - i++; - } - - odd.insert( - 0, - const SizedBox( - width: 10, - ), - ); - odd.add( - const SizedBox( - width: 20, - ), - ); return Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -91,39 +55,4 @@ class _ProgressBarState extends State { ], ); } - - Container _stepTitle({ - required final int index, - TextStyle? style, - final String? step, - }) { - final bool isActive = index == widget.activeIndex; - - style = isActive ? style!.copyWith(fontWeight: FontWeight.w700) : style; - return Container( - padding: const EdgeInsets.only(left: 10), - height: 20, - alignment: Alignment.center, - child: RichText( - textAlign: TextAlign.justify, - text: TextSpan( - style: progressTextStyleLight, - children: [ - TextSpan(text: '${index + 1}.', style: style), - TextSpan(text: step, style: style), - ], - ), - ), - ); - } } - -const TextStyle progressTextStyleLight = TextStyle( - fontSize: 11, - color: Colors.black, - height: 1.7, -); - -final TextStyle progressTextStyleDark = progressTextStyleLight.copyWith( - color: Colors.white, -); diff --git a/lib/ui/pages/more/console.dart b/lib/ui/pages/more/console.dart index dc3801a7..94c06b4d 100644 --- a/lib/ui/pages/more/console.dart +++ b/lib/ui/pages/more/console.dart @@ -1,8 +1,7 @@ -import 'dart:collection'; - import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/logic/models/message.dart'; import 'package:selfprivacy/ui/components/list_tiles/log_list_tile.dart'; @@ -16,25 +15,36 @@ class ConsolePage extends StatefulWidget { } class _ConsolePageState extends State { + bool paused = false; + @override void initState() { - getIt.get().addListener(update); - super.initState(); + + getIt().addListener(update); } @override void dispose() { getIt().removeListener(update); + super.dispose(); } - bool paused = false; - void update() { - if (!paused) { - setState(() => {}); - } + /// listener update could come at any time, like when widget is already + /// unmounted or during frame build, adding as postframe callback ensures + /// that element is marked for rebuild + WidgetsBinding.instance.addPostFrameCallback((final _) { + if (!paused && mounted) { + setState(() => {}); + } + }); + } + + void togglePause() { + paused ^= true; + setState(() {}); } @override @@ -51,7 +61,7 @@ class _ConsolePageState extends State { icon: Icon( paused ? Icons.play_arrow_outlined : Icons.pause_outlined, ), - onPressed: () => setState(() => paused = !paused), + onPressed: togglePause, ), ], ), @@ -69,12 +79,12 @@ class _ConsolePageState extends State { reverse: true, shrinkWrap: true, children: [ - const SizedBox(height: 20), - ...UnmodifiableListView( - messages - .map((final message) => LogListItem(message: message)) - .toList() - .reversed, + const Gap(20), + ...messages.reversed.map( + (final message) => LogListItem( + key: ValueKey(message), + message: message, + ), ), ], ); @@ -82,11 +92,10 @@ class _ConsolePageState extends State { return Column( mainAxisAlignment: MainAxisAlignment.center, mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text('console_page.waiting'.tr()), - const SizedBox( - height: 16, - ), + const Gap(16), const CircularProgressIndicator(), ], ); diff --git a/lib/ui/pages/onboarding/onboarding.dart b/lib/ui/pages/onboarding/onboarding.dart index c4075741..44576408 100644 --- a/lib/ui/pages/onboarding/onboarding.dart +++ b/lib/ui/pages/onboarding/onboarding.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_settings/app_settings_cubit.dart'; -import 'package:selfprivacy/ui/components/buttons/brand_button.dart'; +import 'package:selfprivacy/ui/pages/onboarding/views/views.dart'; import 'package:selfprivacy/ui/router/router.dart'; @RoutePage() @@ -17,152 +17,35 @@ class _OnboardingPageState extends State { PageController pageController = PageController(); @override - void initState() { - super.initState(); + void dispose() { + pageController.dispose(); + super.dispose(); } + Future scrollTo(final int targetView) => pageController.animateToPage( + targetView, + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOutCubicEmphasized, + ); + @override - Widget build(final BuildContext context) => Scaffold( - body: PageView( + Widget build(final BuildContext context) => Material( + child: PageView( controller: pageController, children: [ - _withPadding(firstPage()), - _withPadding(secondPage()), - ], - ), - ); - - Widget _withPadding(final Widget child) => Padding( - padding: const EdgeInsets.symmetric( - horizontal: 15, - ), - child: child, - ); - - Widget firstPage() => ConstrainedBox( - constraints: BoxConstraints( - maxHeight: MediaQuery.of(context).size.height, - ), - child: Column( - children: [ - Expanded( - child: ListView( - children: [ - const SizedBox(height: 30), - Text( - 'onboarding.page1_title'.tr(), - style: Theme.of(context).textTheme.headlineSmall, - ), - const SizedBox(height: 16), - Text( - 'onboarding.page1_text'.tr(), - style: Theme.of(context).textTheme.bodyMedium, - ), - const SizedBox(height: 32), - Center( - child: Image.asset( - _fileName( - context: context, - path: 'assets/images/onboarding', - fileExtention: 'png', - fileName: 'onboarding1', - ), - ), - ), - ], - ), + OnboardingFirstView( + onProceed: () => scrollTo(1), ), - BrandButton.rised( - onPressed: () { - pageController.animateToPage( - 1, - duration: const Duration(milliseconds: 300), - curve: Curves.easeInOutCubicEmphasized, - ); - }, - text: 'basis.next'.tr(), - ), - const SizedBox(height: 30), - ], - ), - ); - - Widget secondPage() => ConstrainedBox( - constraints: BoxConstraints( - maxHeight: MediaQuery.of(context).size.height, - ), - child: Column( - children: [ - Expanded( - child: ListView( - children: [ - const SizedBox(height: 30), - Text( - 'onboarding.page2_title'.tr(), - style: Theme.of(context).textTheme.headlineSmall, - ), - const SizedBox(height: 16), - Text( - 'onboarding.page2_text'.tr(), - style: Theme.of(context).textTheme.bodyMedium, - ), - const SizedBox(height: 16), - Text( - 'onboarding.page2_server_provider_title'.tr(), - style: Theme.of(context).textTheme.titleLarge, - ), - const SizedBox(height: 16), - Text( - 'onboarding.page2_server_provider_text'.tr(), - style: Theme.of(context).textTheme.bodyMedium, - ), - const SizedBox(height: 16), - Text( - 'onboarding.page2_dns_provider_title'.tr(), - style: Theme.of(context).textTheme.titleLarge, - ), - const SizedBox(height: 16), - Text( - 'onboarding.page2_dns_provider_text'.tr(), - style: Theme.of(context).textTheme.bodyMedium, - ), - const SizedBox(height: 16), - Text( - 'onboarding.page2_backup_provider_title'.tr(), - style: Theme.of(context).textTheme.titleLarge, - ), - const SizedBox(height: 16), - Text( - 'onboarding.page2_backup_provider_text'.tr(), - style: Theme.of(context).textTheme.bodyMedium, - ), - const SizedBox(height: 16), - ], - ), - ), - BrandButton.rised( - onPressed: () { + OnboardingSecondView( + onProceed: () { context.read().turnOffOnboarding(); context.router.replaceAll([ const RootRoute(), const InitializingRoute(), ]); }, - text: 'basis.got_it'.tr(), ), - const SizedBox(height: 30), ], ), ); } - -String _fileName({ - required final BuildContext context, - required final String path, - required final String fileName, - required final String fileExtention, -}) { - final ThemeData theme = Theme.of(context); - final bool isDark = theme.brightness == Brightness.dark; - return '$path/$fileName${isDark ? '-dark' : '-light'}.$fileExtention'; -} diff --git a/lib/ui/pages/onboarding/views/onboarding_first_view.dart b/lib/ui/pages/onboarding/views/onboarding_first_view.dart new file mode 100644 index 00000000..fcef8ec5 --- /dev/null +++ b/lib/ui/pages/onboarding/views/onboarding_first_view.dart @@ -0,0 +1,50 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; +import 'package:selfprivacy/ui/pages/onboarding/views/onboarding_view.dart'; + +class OnboardingFirstView extends StatelessWidget { + const OnboardingFirstView({ + required this.onProceed, + super.key, + }); + + final VoidCallback onProceed; + + String assetName({ + required final BuildContext context, + required final String path, + required final String fileName, + required final String fileExtension, + }) { + final String suffix = + Theme.of(context).brightness == Brightness.dark ? '-dark' : '-light'; + return '$path/$fileName$suffix.$fileExtension'; + } + + @override + Widget build(final BuildContext context) => OnboardingView( + onProceed: onProceed, + children: [ + Text( + 'onboarding.page1_title'.tr(), + style: Theme.of(context).textTheme.headlineSmall, + ), + const Gap(15), + Text( + 'onboarding.page1_text'.tr(), + style: Theme.of(context).textTheme.bodyMedium, + ), + const Gap(30), + Image.asset( + assetName( + context: context, + path: 'assets/images/onboarding', + fileName: 'onboarding1', + fileExtension: 'png', + ), + fit: BoxFit.fitWidth, + ), + ], + ); +} diff --git a/lib/ui/pages/onboarding/views/onboarding_second_view.dart b/lib/ui/pages/onboarding/views/onboarding_second_view.dart new file mode 100644 index 00000000..8a996349 --- /dev/null +++ b/lib/ui/pages/onboarding/views/onboarding_second_view.dart @@ -0,0 +1,60 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:gap/gap.dart'; +import 'package:selfprivacy/ui/pages/onboarding/views/onboarding_view.dart'; + +class OnboardingSecondView extends StatelessWidget { + const OnboardingSecondView({ + required this.onProceed, + super.key, + }); + + final VoidCallback onProceed; + + @override + Widget build(final BuildContext context) => OnboardingView( + buttonTitle: 'basis.got_it', + onProceed: onProceed, + children: [ + Text( + 'onboarding.page2_title'.tr(), + style: Theme.of(context).textTheme.headlineSmall, + ), + const Gap(16), + Text( + 'onboarding.page2_text'.tr(), + style: Theme.of(context).textTheme.bodyMedium, + ), + const Gap(16), + Text( + 'onboarding.page2_server_provider_title'.tr(), + style: Theme.of(context).textTheme.titleLarge, + ), + const Gap(16), + Text( + 'onboarding.page2_server_provider_text'.tr(), + style: Theme.of(context).textTheme.bodyMedium, + ), + const Gap(16), + Text( + 'onboarding.page2_dns_provider_title'.tr(), + style: Theme.of(context).textTheme.titleLarge, + ), + const Gap(16), + Text( + 'onboarding.page2_dns_provider_text'.tr(), + style: Theme.of(context).textTheme.bodyMedium, + ), + const Gap(16), + Text( + 'onboarding.page2_backup_provider_title'.tr(), + style: Theme.of(context).textTheme.titleLarge, + ), + const Gap(16), + Text( + 'onboarding.page2_backup_provider_text'.tr(), + style: Theme.of(context).textTheme.bodyMedium, + ), + ], + ); +} diff --git a/lib/ui/pages/onboarding/views/onboarding_view.dart b/lib/ui/pages/onboarding/views/onboarding_view.dart new file mode 100644 index 00000000..6cf41b94 --- /dev/null +++ b/lib/ui/pages/onboarding/views/onboarding_view.dart @@ -0,0 +1,53 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:selfprivacy/ui/components/buttons/buttons.dart'; + +// base widget for onboarding view +class OnboardingView extends StatelessWidget { + const OnboardingView({ + required this.onProceed, + required this.children, + this.buttonTitle = 'basis.next', + super.key, + }); + + /// Proceed button title + final String buttonTitle; + + /// Proceed button callback + final VoidCallback onProceed; + + /// Current view content + final List children; + + @override + Widget build(final BuildContext context) => Scaffold( + body: Align( + child: ConstrainedBox( + constraints: const BoxConstraints(maxWidth: 480), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Expanded( + child: ListView( + primary: true, + shrinkWrap: true, + padding: const EdgeInsets.all(15) + + const EdgeInsets.only(top: 15), + children: children, + ), + ), + Padding( + padding: const EdgeInsets.symmetric(horizontal: 15) + + const EdgeInsets.only(bottom: 30), + child: SPBrandButton.text( + title: buttonTitle.tr(), + onPressed: onProceed, + ), + ), + ], + ), + ), + ), + ); +} diff --git a/lib/ui/pages/onboarding/views/views.dart b/lib/ui/pages/onboarding/views/views.dart new file mode 100644 index 00000000..b4ea6ffd --- /dev/null +++ b/lib/ui/pages/onboarding/views/views.dart @@ -0,0 +1,2 @@ +export 'onboarding_first_view.dart'; +export 'onboarding_second_view.dart'; diff --git a/lib/ui/pages/root_route.dart b/lib/ui/pages/root_route.dart index b50f453b..6ae7607c 100644 --- a/lib/ui/pages/root_route.dart +++ b/lib/ui/pages/root_route.dart @@ -5,7 +5,6 @@ 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/ui/layouts/root_scaffold_with_navigation.dart'; import 'package:selfprivacy/ui/router/root_destinations.dart'; - import 'package:selfprivacy/ui/router/router.dart'; @RoutePage() @@ -150,8 +149,3 @@ class MainScreenNavigationDrawer extends StatelessWidget { ); } } - -class ChangeTab { - ChangeTab(this.onPress); - final ValueChanged onPress; -} diff --git a/lib/ui/pages/server_details/text_details.dart b/lib/ui/pages/server_details/text_details.dart index 2fef5440..f94a353b 100644 --- a/lib/ui/pages/server_details/text_details.dart +++ b/lib/ui/pages/server_details/text_details.dart @@ -56,5 +56,3 @@ class _TempMessage extends StatelessWidget { ), ); } - -final DateFormat formatter = DateFormat('HH:mm:ss'); diff --git a/lib/ui/pages/services/service_page.dart b/lib/ui/pages/services/service_page.dart index 37f9515c..b51edb31 100644 --- a/lib/ui/pages/services/service_page.dart +++ b/lib/ui/pages/services/service_page.dart @@ -170,83 +170,54 @@ class ServiceStatusCard extends StatelessWidget { @override Widget build(final BuildContext context) { + late IconData icon; + late String buttonTitle; + switch (status) { case ServiceStatus.active: - return FilledCard( - child: ListTile( - leading: const Icon( - Icons.check_circle_outline, - size: 24, - ), - title: Text('service_page.status.active'.tr()), - ), - ); + icon = Icons.check_circle_outline; + buttonTitle = 'service_page.status.active'; + break; + case ServiceStatus.inactive: - return FilledCard( - tertiary: true, - child: ListTile( - leading: const Icon( - Icons.stop_circle_outlined, - size: 24, - ), - title: Text('service_page.status.inactive'.tr()), - ), - ); + icon = Icons.stop_circle_outlined; + buttonTitle = 'service_page.status.inactive'; + break; + case ServiceStatus.failed: - return FilledCard( - error: true, - child: ListTile( - leading: const Icon( - Icons.error_outline, - size: 24, - ), - title: Text('service_page.status.failed'.tr()), - ), - ); + icon = Icons.error_outline; + buttonTitle = 'service_page.status.failed'; + break; + case ServiceStatus.off: - return FilledCard( - tertiary: true, - child: ListTile( - leading: const Icon( - Icons.power_settings_new, - size: 24, - ), - title: Text('service_page.status.off'.tr()), - ), - ); + icon = Icons.power_settings_new; + buttonTitle = 'service_page.status.off'; + break; + case ServiceStatus.activating: - return FilledCard( - tertiary: true, - child: ListTile( - leading: const Icon( - Icons.restart_alt_outlined, - size: 24, - ), - title: Text('service_page.status.activating'.tr()), - ), - ); + icon = Icons.restart_alt_outlined; + buttonTitle = 'service_page.status.activating'; + break; + case ServiceStatus.deactivating: - return FilledCard( - tertiary: true, - child: ListTile( - leading: const Icon( - Icons.restart_alt_outlined, - size: 24, - ), - title: Text('service_page.status.deactivating'.tr()), - ), - ); + icon = Icons.restart_alt_outlined; + buttonTitle = 'service_page.status.deactivating'; + break; + case ServiceStatus.reloading: - return FilledCard( - tertiary: true, - child: ListTile( - leading: const Icon( - Icons.restart_alt_outlined, - size: 24, - ), - title: Text('service_page.status.reloading'.tr()), - ), - ); + icon = Icons.restart_alt_outlined; + buttonTitle = 'service_page.status.reloading'; } + + return FilledCard( + tertiary: true, + child: ListTile( + leading: Icon( + icon, + size: 24, + ), + title: Text(buttonTitle.tr()), + ), + ); } } diff --git a/lib/utils/ui_helpers.dart b/lib/utils/ui_helpers.dart index 10f7419d..d7aec724 100644 --- a/lib/utils/ui_helpers.dart +++ b/lib/utils/ui_helpers.dart @@ -7,13 +7,13 @@ class UiHelpers { static String getDomainName(final ServerInstallationState config) => config.isDomainSelected ? config.serverDomain!.domainName : 'example.com'; + static final _formatter = NumberFormat()..minimumFractionDigits = 0; + static String formatWithPrecision( final double value, { final int fraction = 2, }) { - final NumberFormat formatter = NumberFormat(); - formatter.minimumFractionDigits = 0; - formatter.maximumFractionDigits = fraction; - return formatter.format(value); + _formatter.maximumFractionDigits = fraction; + return _formatter.format(value); } } diff --git a/pubspec.lock b/pubspec.lock index e1874330..a3ebff18 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -525,14 +525,22 @@ packages: url: "https://pub.dev" source: hosted version: "3.2.0" + gap: + dependency: "direct main" + description: + name: gap + sha256: f19387d4e32f849394758b91377f9153a1b41d79513ef7668c088c77dbc6955d + url: "https://pub.dev" + source: hosted + version: "3.0.1" get_it: dependency: "direct main" description: name: get_it - sha256: f79870884de16d689cf9a7d15eedf31ed61d750e813c538a6efb92660fea83c3 + sha256: e6017ce7fdeaf218dc51a100344d8cb70134b80e28b760f8bb23c242437bafd7 url: "https://pub.dev" source: hosted - version: "7.6.4" + version: "7.6.7" glob: dependency: transitive description: @@ -673,10 +681,10 @@ packages: dependency: "direct main" description: name: http - sha256: d4872660c46d929f6b8a9ef4e7a7eff7e49bbf0c4ec3f385ee32df5119175139 + sha256: a2bbf9d017fcced29139daa8ed2bba4ece450ab222871df93ca9eec6f80c34ba url: "https://pub.dev" source: hosted - version: "1.1.2" + version: "1.2.0" http_multi_server: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 0c1641a6..32e0b41c 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -27,6 +27,7 @@ dependencies: flutter_markdown: ^0.6.18+2 flutter_secure_storage: ^9.0.0 flutter_svg: ^2.0.9 + gap: ^3.0.1 get_it: ^7.6.4 gql: ^1.0.0 graphql: ^5.1.3 From 46910061ed886464e17127c1743b3515f4a5ef82 Mon Sep 17 00:00:00 2001 From: Inex Code Date: Thu, 8 Feb 2024 14:30:50 +0200 Subject: [PATCH 27/51] ci: Update Windows build --- .github/workflows/windows.yml | 7 +++++-- lib/ui/pages/onboarding/onboarding.dart | 1 - 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index c52968cd..452771bb 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -1,6 +1,9 @@ name: Windows Builder -on: tag +on: + push: + tags: + - '*.*.*' jobs: build-windows: @@ -14,7 +17,7 @@ jobs: # Install Flutter - uses: subosito/flutter-action@v2 with: - flutter-version: '3.3.10' + flutter-version: '3.16.1' channel: 'stable' # Build Windows artifact diff --git a/lib/ui/pages/onboarding/onboarding.dart b/lib/ui/pages/onboarding/onboarding.dart index 44576408..141c9463 100644 --- a/lib/ui/pages/onboarding/onboarding.dart +++ b/lib/ui/pages/onboarding/onboarding.dart @@ -1,5 +1,4 @@ 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_settings/app_settings_cubit.dart'; import 'package:selfprivacy/ui/pages/onboarding/views/views.dart'; From 1daf957245d4edc078d4ac307625a4861904b842 Mon Sep 17 00:00:00 2001 From: Inex Code Date: Thu, 8 Feb 2024 16:58:45 +0300 Subject: [PATCH 28/51] 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 29/51] 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 30/51] 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 31/51] 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 32/51] 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 33/51] 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 34/51] 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 35/51] 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 36/51] 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 37/51] 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 38/51] 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 39/51] 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 40/51] 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 41/51] 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 42/51] 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 ], ), ], From 9532ddc8afead1b4549d3d24ac129f5e8b161e3b Mon Sep 17 00:00:00 2001 From: Inex Code Date: Wed, 21 Feb 2024 01:48:52 +0300 Subject: [PATCH 43/51] feat(ui): About page now contains links --- assets/markdown/about-be.md | 12 - assets/markdown/about-cs.md | 12 - assets/markdown/about-de.md | 12 - assets/markdown/about-en.md | 12 - assets/markdown/about-es.md | 12 - assets/markdown/about-fr.md | 12 - assets/markdown/about-it.md | 12 - assets/markdown/about-ja.md | 12 - assets/markdown/about-ka.md | 12 - assets/markdown/about-nl.md | 12 - assets/markdown/about-pl.md | 12 - assets/markdown/about-ru.md | 12 - assets/markdown/about-sk.md | 12 - assets/markdown/about-th.md | 12 - assets/markdown/about-uk.md | 12 - assets/translations/en.json | 31 +- lib/illustrations/stray_deer.dart | 4 - lib/ui/pages/more/about_application.dart | 406 +++++++++++++++++++---- lib/ui/pages/more/about_us.dart | 30 -- lib/ui/pages/more/more.dart | 6 +- lib/ui/router/router.dart | 2 +- 21 files changed, 369 insertions(+), 290 deletions(-) delete mode 100644 assets/markdown/about-be.md delete mode 100644 assets/markdown/about-cs.md delete mode 100644 assets/markdown/about-de.md delete mode 100644 assets/markdown/about-en.md delete mode 100644 assets/markdown/about-es.md delete mode 100644 assets/markdown/about-fr.md delete mode 100644 assets/markdown/about-it.md delete mode 100644 assets/markdown/about-ja.md delete mode 100644 assets/markdown/about-ka.md delete mode 100644 assets/markdown/about-nl.md delete mode 100644 assets/markdown/about-pl.md delete mode 100644 assets/markdown/about-ru.md delete mode 100644 assets/markdown/about-sk.md delete mode 100644 assets/markdown/about-th.md delete mode 100644 assets/markdown/about-uk.md delete mode 100644 lib/ui/pages/more/about_us.dart diff --git a/assets/markdown/about-be.md b/assets/markdown/about-be.md deleted file mode 100644 index 71a9ef3e..00000000 --- a/assets/markdown/about-be.md +++ /dev/null @@ -1,12 +0,0 @@ -### Пра нас - -Усё больш арганізацый жадаюць валодаць нашымі дадзенымі -Праект дазваляе толькі Вам у поўнай меры распараджацца ўласнымі **дадзенымі** на сваім сэрвэры. - -### Наша місія - -Лічбавая незалежнасць і прыватнасць, даступныя кожнаму - -### Мэта - -Распрацаваць праграму, якая дазволіць кожнаму разгарнуць свае прыватныя паслугі для сябе і сваіх суседзяў. \ No newline at end of file diff --git a/assets/markdown/about-cs.md b/assets/markdown/about-cs.md deleted file mode 100644 index 4d0a01e2..00000000 --- a/assets/markdown/about-cs.md +++ /dev/null @@ -1,12 +0,0 @@ -### O nás - -More and more corporations want to control our data. -We want to have full control of our **data** on our own. - -### Naše poslání - -Digitální nezávislost a soukromí dostupné všem - -### Cíl - -Rozvíjet program, který umožní každému nasadit své soukromé služby pro sebe a své sousedy. \ No newline at end of file diff --git a/assets/markdown/about-de.md b/assets/markdown/about-de.md deleted file mode 100644 index 401af514..00000000 --- a/assets/markdown/about-de.md +++ /dev/null @@ -1,12 +0,0 @@ -### Über uns - -Immer mehr Unternehmen wollen unsere Daten kontrollieren. -Wir wollen selbst die volle Kontrolle über unsere **data** haben. - -### Unsere Mission - -Digitale Unabhängigkeit und Privatsphäre für alle verfügbar - -### Ziel - -Das Programm entwickeln, das es jedem ermöglicht, seine privaten Dienste für sich und seine Nachbarn einzusetzen. \ No newline at end of file diff --git a/assets/markdown/about-en.md b/assets/markdown/about-en.md deleted file mode 100644 index 3963aa6e..00000000 --- a/assets/markdown/about-en.md +++ /dev/null @@ -1,12 +0,0 @@ -### About us - -More and more corporations want to control our data. -We want to have full control of our **data** on our own. - -### Our mission - -Digital independence and privacy, available to everyone - -### Target - -Develop the program, which will allow everyone to deploy their private services for themselves and their neighbours. \ No newline at end of file diff --git a/assets/markdown/about-es.md b/assets/markdown/about-es.md deleted file mode 100644 index 3963aa6e..00000000 --- a/assets/markdown/about-es.md +++ /dev/null @@ -1,12 +0,0 @@ -### About us - -More and more corporations want to control our data. -We want to have full control of our **data** on our own. - -### Our mission - -Digital independence and privacy, available to everyone - -### Target - -Develop the program, which will allow everyone to deploy their private services for themselves and their neighbours. \ No newline at end of file diff --git a/assets/markdown/about-fr.md b/assets/markdown/about-fr.md deleted file mode 100644 index 3963aa6e..00000000 --- a/assets/markdown/about-fr.md +++ /dev/null @@ -1,12 +0,0 @@ -### About us - -More and more corporations want to control our data. -We want to have full control of our **data** on our own. - -### Our mission - -Digital independence and privacy, available to everyone - -### Target - -Develop the program, which will allow everyone to deploy their private services for themselves and their neighbours. \ No newline at end of file diff --git a/assets/markdown/about-it.md b/assets/markdown/about-it.md deleted file mode 100644 index 3963aa6e..00000000 --- a/assets/markdown/about-it.md +++ /dev/null @@ -1,12 +0,0 @@ -### About us - -More and more corporations want to control our data. -We want to have full control of our **data** on our own. - -### Our mission - -Digital independence and privacy, available to everyone - -### Target - -Develop the program, which will allow everyone to deploy their private services for themselves and their neighbours. \ No newline at end of file diff --git a/assets/markdown/about-ja.md b/assets/markdown/about-ja.md deleted file mode 100644 index 3963aa6e..00000000 --- a/assets/markdown/about-ja.md +++ /dev/null @@ -1,12 +0,0 @@ -### About us - -More and more corporations want to control our data. -We want to have full control of our **data** on our own. - -### Our mission - -Digital independence and privacy, available to everyone - -### Target - -Develop the program, which will allow everyone to deploy their private services for themselves and their neighbours. \ No newline at end of file diff --git a/assets/markdown/about-ka.md b/assets/markdown/about-ka.md deleted file mode 100644 index 3963aa6e..00000000 --- a/assets/markdown/about-ka.md +++ /dev/null @@ -1,12 +0,0 @@ -### About us - -More and more corporations want to control our data. -We want to have full control of our **data** on our own. - -### Our mission - -Digital independence and privacy, available to everyone - -### Target - -Develop the program, which will allow everyone to deploy their private services for themselves and their neighbours. \ No newline at end of file diff --git a/assets/markdown/about-nl.md b/assets/markdown/about-nl.md deleted file mode 100644 index 3963aa6e..00000000 --- a/assets/markdown/about-nl.md +++ /dev/null @@ -1,12 +0,0 @@ -### About us - -More and more corporations want to control our data. -We want to have full control of our **data** on our own. - -### Our mission - -Digital independence and privacy, available to everyone - -### Target - -Develop the program, which will allow everyone to deploy their private services for themselves and their neighbours. \ No newline at end of file diff --git a/assets/markdown/about-pl.md b/assets/markdown/about-pl.md deleted file mode 100644 index ebd41246..00000000 --- a/assets/markdown/about-pl.md +++ /dev/null @@ -1,12 +0,0 @@ -### About us - -More and more corporations want to control our data. -We want to have full control of our **data** on our own. - -### Misja projektu - -Niezależność i prywatność cyfrowa dostępna dla wszystkich - -### Cel - -Opracuj program, dzięki któremu każdy będzie mógł stworzyć prywatne usługi dla siebie i swoich bliskich. \ No newline at end of file diff --git a/assets/markdown/about-ru.md b/assets/markdown/about-ru.md deleted file mode 100644 index 15c0f237..00000000 --- a/assets/markdown/about-ru.md +++ /dev/null @@ -1,12 +0,0 @@ -### О проекте - -Всё больше организаций хотят владеть нашими данными -Проект позволяет только Вам в полной мере распоряжаться собственными **данными** на своём сервере. - -### Миссия проекта - -Цифровая независимость и приватность, доступная каждому - -### Цель - -Развивать программу, которая позволит каждому создавать приватные сервисы для себя и своих близких. \ No newline at end of file diff --git a/assets/markdown/about-sk.md b/assets/markdown/about-sk.md deleted file mode 100644 index d3135d3b..00000000 --- a/assets/markdown/about-sk.md +++ /dev/null @@ -1,12 +0,0 @@ -### O nás - -More and more corporations want to control our data. -We want to have full control of our **data** on our own. - -### Naše poslanie - -Digitálna nezávislosť a súkromie dostupné pre každého - -### Cieľ - -Vytvorte program, ktorý umožní každému vytvoriť súkromné služby pre seba a svojich blízkych. \ No newline at end of file diff --git a/assets/markdown/about-th.md b/assets/markdown/about-th.md deleted file mode 100644 index 3963aa6e..00000000 --- a/assets/markdown/about-th.md +++ /dev/null @@ -1,12 +0,0 @@ -### About us - -More and more corporations want to control our data. -We want to have full control of our **data** on our own. - -### Our mission - -Digital independence and privacy, available to everyone - -### Target - -Develop the program, which will allow everyone to deploy their private services for themselves and their neighbours. \ No newline at end of file diff --git a/assets/markdown/about-uk.md b/assets/markdown/about-uk.md deleted file mode 100644 index fee81d64..00000000 --- a/assets/markdown/about-uk.md +++ /dev/null @@ -1,12 +0,0 @@ -### Про нас - -Все більше корпорацій хочуть контролювати свої дані. -Ми хочемо мати повний контроль над нашими. - -### Наша місія - -Цифрова незалежність і конфіденційність доступні кожному - -### Ціль - -Розробити програму, яка дозволить кожному розгорнути свої приватні послуги для себе та їх сусідів. \ No newline at end of file diff --git a/assets/translations/en.json b/assets/translations/en.json index b89dbc58..e0f281af 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -41,25 +41,34 @@ }, "more_page": { "configuration_wizard": "Setup wizard", - "about_project": "About us", - "about_application": "About", "onboarding": "Onboarding", - "create_ssh_key": "Superuser SSH keys", - "console": "Console", - "application_settings": "Application settings" + "create_ssh_key": "Superuser SSH keys" }, "console_page": { "title": "Console", "waiting": "Waiting for initialization…", "copy": "Copy" }, - "about_us_page": { - "title": "About us" - }, "about_application_page": { - "title": "About", - "application_version_text": "Application version {}", - "api_version_text": "Server API version {}", + "title": "About & support", + "versions": "Versions", + "application_version_text": "Application version", + "api_version_text": "Server API version", + "open_source_licenses": "Open source licenses", + "links": "Links", + "website": "Our website", + "documentation": "Documentation", + "matrix_channel": "Matrix channel", + "telegram_channel": "Telegram channel", + "get_support": "Get support", + "matrix_support_chat": "Matrix support chat", + "telegram_support_chat": "Telegram support chat", + "email_support": "Email support", + "contribute": "Contribute", + "source_code": "Source code", + "help_translate": "Help us translate", + "matrix_contributors_chat": "Matrix contributors chat", + "telegram_contributors_chat": "Telegram contributors chat", "privacy_policy": "Privacy policy" }, "application_settings": { diff --git a/lib/illustrations/stray_deer.dart b/lib/illustrations/stray_deer.dart index 88fd55c3..e7554827 100644 --- a/lib/illustrations/stray_deer.dart +++ b/lib/illustrations/stray_deer.dart @@ -17,10 +17,6 @@ class StrayDeerPainter extends CustomPainter { final Color deerSkin = const Color(0xffe0ac9c).harmonizeWith(colorScheme.primary); - print('deerSkin: $deerSkin'); - print('colorScheme.primary: ${colorScheme.primary}'); - print('colorPalette.tertiary.get(10): ${colorPalette.tertiary.get(50)}'); - final Path path0 = Path(); path0.moveTo(size.width * 0.6099773, size.height * 0.6719577); path0.lineTo(size.width * 0.6088435, size.height * 0.6719577); diff --git a/lib/ui/pages/more/about_application.dart b/lib/ui/pages/more/about_application.dart index 5ec4b9be..06bf1100 100644 --- a/lib/ui/pages/more/about_application.dart +++ b/lib/ui/pages/more/about_application.dart @@ -2,10 +2,9 @@ import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:package_info/package_info.dart'; -import 'package:selfprivacy/logic/api_maps/graphql_maps/server_api/server_api.dart'; -import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; -import 'package:selfprivacy/ui/components/brand_md/brand_md.dart'; +import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; +import 'package:selfprivacy/utils/platform_adapter.dart'; import 'package:url_launcher/url_launcher.dart'; @RoutePage() @@ -13,67 +12,356 @@ class AboutApplicationPage extends StatelessWidget { const AboutApplicationPage({super.key}); @override - Widget build(final BuildContext context) { - final bool isReady = context.watch().state - is ServerInstallationFinished; - - return BrandHeroScreen( - hasBackButton: true, - hasFlashButton: false, - heroTitle: 'about_application_page.title'.tr(), - children: [ - FutureBuilder( - future: _packageVersion(), - builder: (final context, final snapshot) => Text( - 'about_application_page.application_version_text' - .tr(args: [snapshot.data.toString()]), - style: Theme.of(context).textTheme.bodyLarge, - ), - ), - if (isReady) - FutureBuilder( - future: _apiVersion(), - builder: (final context, final snapshot) => Text( - 'about_application_page.api_version_text' - .tr(args: [snapshot.data.toString()]), - style: Theme.of(context).textTheme.bodyLarge, + Widget build(final BuildContext context) => BrandHeroScreen( + hasBackButton: true, + hasFlashButton: false, + heroTitle: 'about_application_page.title'.tr(), + bodyPadding: const EdgeInsets.symmetric(vertical: 16), + children: [ + Padding( + padding: const EdgeInsets.all(16), + child: Text( + 'about_application_page.versions'.tr(), + style: Theme.of(context).textTheme.labelLarge!.copyWith( + color: Theme.of(context).colorScheme.secondary, + ), ), ), - const SizedBox(height: 10), - // Button to call showAboutDialog - TextButton( - onPressed: () => showAboutDialog( - context: context, - applicationName: 'SelfPrivacy', - applicationLegalese: '© 2024 SelfPrivacy', - // Link to privacy policy - children: [ - TextButton( - onPressed: () => launchUrl( - Uri.parse('https://selfprivacy.org/privacy-policy/'), - mode: LaunchMode.externalApplication, - ), - child: Text('about_application_page.privacy_policy'.tr()), + FutureBuilder( + future: _packageVersion(), + builder: (final context, final snapshot) => ListTile( + title: Text( + 'about_application_page.application_version_text'.tr(), ), - ], + subtitle: Text( + snapshot.data.toString(), + ), + leading: const Icon( + Icons.phone_android_outlined, + ), + onLongPress: () { + PlatformAdapter.setClipboard( + snapshot.data.toString(), + ); + getIt().showSnackBar( + 'basis.copied_to_clipboard'.tr(), + ); + }, + ), ), - child: const Text('Show about dialog'), - ), - const SizedBox(height: 8), - const Divider(height: 0), - const SizedBox(height: 8), - const BrandMarkdown( - fileName: 'about', - ), - ], - ); - } + if (getIt().apiData.apiVersion.data != null) + FutureBuilder( + future: _apiVersion(), + builder: (final context, final snapshot) => ListTile( + title: Text( + 'about_application_page.api_version_text'.tr(), + ), + subtitle: Text(snapshot.data.toString()), + leading: const Icon( + Icons.api_outlined, + ), + onLongPress: () { + PlatformAdapter.setClipboard( + snapshot.data.toString(), + ); + getIt().showSnackBar( + 'basis.copied_to_clipboard'.tr(), + ); + }, + ), + ), + FutureBuilder( + future: _packageVersion(), + builder: (final context, final snapshot) => ListTile( + title: Text('about_application_page.open_source_licenses'.tr()), + onTap: () => showLicensePage( + context: context, + applicationName: 'SelfPrivacy', + applicationVersion: snapshot.data.toString(), + applicationLegalese: '© 2024 SelfPrivacy', + ), + leading: const Icon( + Icons.copyright_outlined, + ), + ), + ), + Padding( + padding: const EdgeInsets.all(16), + child: Text( + 'about_application_page.links'.tr(), + style: Theme.of(context).textTheme.labelLarge!.copyWith( + color: Theme.of(context).colorScheme.secondary, + ), + ), + ), + ListTile( + title: Text('about_application_page.website'.tr()), + subtitle: const Text('selfprivacy.org'), + onTap: () => launchUrl( + Uri.parse('https://selfprivacy.org/'), + mode: LaunchMode.externalApplication, + ), + leading: const Icon( + Icons.language_outlined, + ), + onLongPress: () { + PlatformAdapter.setClipboard( + 'https://selfprivacy.org/', + ); + getIt().showSnackBar( + 'basis.copied_to_clipboard'.tr(), + ); + }, + ), + ListTile( + title: Text('about_application_page.documentation'.tr()), + subtitle: const Text('selfprivacy.org/docs'), + onTap: () => launchUrl( + Uri.parse('https://selfprivacy.org/docs/'), + mode: LaunchMode.externalApplication, + ), + leading: const Icon( + Icons.library_books_outlined, + ), + onLongPress: () { + PlatformAdapter.setClipboard( + 'https://selfprivacy.org/docs/', + ); + getIt().showSnackBar( + 'basis.copied_to_clipboard'.tr(), + ); + }, + ), + ListTile( + title: Text('about_application_page.privacy_policy'.tr()), + subtitle: const Text('selfprivacy.org/privacy-policy'), + onTap: () => launchUrl( + Uri.parse('https://selfprivacy.org/privacy-policy/'), + mode: LaunchMode.externalApplication, + ), + leading: const Icon( + Icons.policy_outlined, + ), + onLongPress: () { + PlatformAdapter.setClipboard( + 'https://selfprivacy.org/privacy-policy/', + ); + getIt().showSnackBar( + 'basis.copied_to_clipboard'.tr(), + ); + }, + ), + // Matrix channel + ListTile( + title: Text('about_application_page.matrix_channel'.tr()), + subtitle: const Text('#news:selfprivacy.org'), + onTap: () => launchUrl( + Uri.parse('https://matrix.to/#/#news:selfprivacy.org'), + mode: LaunchMode.externalApplication, + ), + leading: const Icon( + Icons.feed_outlined, + ), + onLongPress: () { + PlatformAdapter.setClipboard( + '#news:selfprivacy.org', + ); + getIt().showSnackBar( + 'basis.copied_to_clipboard'.tr(), + ); + }, + ), + // Telegram channel + ListTile( + title: Text('about_application_page.telegram_channel'.tr()), + subtitle: const Text('@selfprivacy'), + onTap: () => launchUrl( + Uri.parse('https://t.me/selfprivacy'), + mode: LaunchMode.externalApplication, + ), + leading: const Icon( + Icons.feed_outlined, + ), + onLongPress: () { + PlatformAdapter.setClipboard( + '@selfprivacy', + ); + getIt().showSnackBar( + 'basis.copied_to_clipboard'.tr(), + ); + }, + ), + Padding( + padding: const EdgeInsets.all(16), + child: Text( + 'about_application_page.get_support'.tr(), + style: Theme.of(context).textTheme.labelLarge!.copyWith( + color: Theme.of(context).colorScheme.secondary, + ), + ), + ), + // Matrix + ListTile( + title: Text('about_application_page.matrix_support_chat'.tr()), + subtitle: const Text('#chat:selfprivacy.org'), + onTap: () => launchUrl( + Uri.parse('https://matrix.to/#/#chat:selfprivacy.org'), + mode: LaunchMode.externalApplication, + ), + leading: const Icon( + Icons.question_answer_outlined, + ), + onLongPress: () { + PlatformAdapter.setClipboard( + '#chat:selfprivacy.org', + ); + getIt().showSnackBar( + 'basis.copied_to_clipboard'.tr(), + ); + } + ), + // Telegram + ListTile( + title: Text('about_application_page.telegram_support_chat'.tr()), + subtitle: const Text('@selfprivacy_chat'), + onTap: () => launchUrl( + Uri.parse('https://t.me/selfprivacy_chat'), + mode: LaunchMode.externalApplication, + ), + leading: const Icon( + Icons.question_answer_outlined, + ), + onLongPress: () { + PlatformAdapter.setClipboard( + '@selfprivacy_chat', + ); + getIt().showSnackBar( + 'basis.copied_to_clipboard'.tr(), + ); + }, + ), + // Email + ListTile( + title: Text('about_application_page.email_support'.tr()), + subtitle: const Text('support@selfprivacy.org'), + onTap: () => launchUrl( + Uri.parse('mailto:support@selfprivacy.org'), + mode: LaunchMode.externalApplication, + ), + leading: const Icon( + Icons.email_outlined, + ), + onLongPress: () { + PlatformAdapter.setClipboard( + 'support@selfprivacy.org', + ); + getIt().showSnackBar( + 'basis.copied_to_clipboard'.tr(), + ); + }, + ), + Padding( + padding: const EdgeInsets.all(16), + child: Text( + 'about_application_page.contribute'.tr(), + style: Theme.of(context).textTheme.labelLarge!.copyWith( + color: Theme.of(context).colorScheme.secondary, + ), + ), + ), + // Source code + ListTile( + title: Text('about_application_page.source_code'.tr()), + subtitle: const Text('git.selfprivacy.org'), + onTap: () => launchUrl( + Uri.parse( + 'https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app', + ), + mode: LaunchMode.externalApplication, + ), + leading: const Icon( + Icons.code_outlined, + ), + onLongPress: () { + PlatformAdapter.setClipboard( + 'https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app', + ); + getIt().showSnackBar( + 'basis.copied_to_clipboard'.tr(), + ); + }, + ), + // translate + ListTile( + title: Text('about_application_page.help_translate'.tr()), + subtitle: const Text('weblate.selfprivacy.org'), + onTap: () => launchUrl( + Uri.parse( + 'https://weblate.selfprivacy.org/projects/selfprivacy/', + ), + mode: LaunchMode.externalApplication, + ), + leading: const Icon( + Icons.translate_outlined, + ), + onLongPress: () { + PlatformAdapter.setClipboard( + 'https://weblate.selfprivacy.org/projects/selfprivacy/', + ); + getIt().showSnackBar( + 'basis.copied_to_clipboard'.tr(), + ); + }, + ), + // matrix chat + ListTile( + title: Text('about_application_page.matrix_contributors_chat'.tr()), + subtitle: const Text('#dev:selfprivacy.org'), + onTap: () => launchUrl( + Uri.parse('https://matrix.to/#/#dev:selfprivacy.org'), + mode: LaunchMode.externalApplication, + ), + leading: const Icon( + Icons.question_answer_outlined, + ), + onLongPress: () { + PlatformAdapter.setClipboard( + '#dev:selfprivacy.org', + ); + getIt().showSnackBar( + 'basis.copied_to_clipboard'.tr(), + ); + }, + ), + // telegram + ListTile( + title: + Text('about_application_page.telegram_contributors_chat'.tr()), + subtitle: const Text('@selfprivacy_dev'), + onTap: () => launchUrl( + Uri.parse('https://t.me/selfprivacy_dev'), + mode: LaunchMode.externalApplication, + ), + leading: const Icon( + Icons.question_answer_outlined, + ), + onLongPress: () { + PlatformAdapter.setClipboard( + '@selfprivacy_dev', + ); + getIt().showSnackBar( + 'basis.copied_to_clipboard'.tr(), + ); + }, + ), + ], + ); Future _packageVersion() async { String packageVersion = 'unknown'; try { final PackageInfo packageInfo = await PackageInfo.fromPlatform(); - packageVersion = packageInfo.version; + packageVersion = '${packageInfo.version} (${packageInfo.buildNumber})'; } catch (e) { print(e); } @@ -82,12 +370,8 @@ class AboutApplicationPage extends StatelessWidget { } Future _apiVersion() async { - String apiVersion = 'unknown'; - try { - apiVersion = await ServerApi().getApiVersion() ?? apiVersion; - } catch (e) { - print(e); - } + final apiVersion = + getIt().apiData.apiVersion.data ?? 'unknown'; return apiVersion; } diff --git a/lib/ui/pages/more/about_us.dart b/lib/ui/pages/more/about_us.dart deleted file mode 100644 index 54a3151b..00000000 --- a/lib/ui/pages/more/about_us.dart +++ /dev/null @@ -1,30 +0,0 @@ -import 'package:easy_localization/easy_localization.dart'; -import 'package:flutter/material.dart'; -import 'package:selfprivacy/config/brand_theme.dart'; -import 'package:selfprivacy/ui/components/brand_header/brand_header.dart'; -import 'package:selfprivacy/ui/components/brand_md/brand_md.dart'; - -class AboutUsPage extends StatelessWidget { - const AboutUsPage({super.key}); - - @override - Widget build(final BuildContext context) => SafeArea( - child: Scaffold( - appBar: PreferredSize( - preferredSize: const Size.fromHeight(52), - child: BrandHeader( - title: 'about_us_page.title'.tr(), - hasBackButton: true, - ), - ), - body: ListView( - padding: paddingH15V0, - children: const [ - BrandMarkdown( - fileName: 'about', - ), - ], - ), - ), - ); -} diff --git a/lib/ui/pages/more/more.dart b/lib/ui/pages/more/more.dart index 0dfa3e4e..6e72bdd9 100644 --- a/lib/ui/pages/more/more.dart +++ b/lib/ui/pages/more/more.dart @@ -63,12 +63,12 @@ class MorePage extends StatelessWidget { title: 'devices.main_screen.header'.tr(), ), _MoreMenuItem( - title: 'more_page.application_settings'.tr(), + title: 'application_settings.title'.tr(), iconData: Icons.settings_outlined, goTo: () => const AppSettingsRoute(), ), _MoreMenuItem( - title: 'more_page.about_application'.tr(), + title: 'about_application_page.title'.tr(), iconData: BrandIcons.fire, goTo: () => const AboutApplicationRoute(), longGoTo: const DeveloperSettingsRoute(), @@ -80,7 +80,7 @@ class MorePage extends StatelessWidget { goTo: () => const OnboardingRoute(), ), _MoreMenuItem( - title: 'more_page.console'.tr(), + title: 'console_page.title'.tr(), iconData: BrandIcons.terminal, goTo: () => const ConsoleRoute(), ), diff --git a/lib/ui/router/router.dart b/lib/ui/router/router.dart index 764aacd7..48eb2d5a 100644 --- a/lib/ui/router/router.dart +++ b/lib/ui/router/router.dart @@ -132,7 +132,7 @@ String getRouteTitle(final String routeName) { case 'DevicesRoute': return 'devices.main_screen.header'; case 'AboutApplicationRoute': - return 'about_us_page.title'; + return 'about_application_page.title'; case 'ConsoleRoute': return 'console_page.title'; case 'DeveloperSettingsRoute': From b4f700d56a718935e4c01909af9639a8ca362647 Mon Sep 17 00:00:00 2001 From: Inex Code Date: Wed, 21 Feb 2024 02:03:53 +0300 Subject: [PATCH 44/51] feat(ui): Select device icon depending on the platform we are runnning on --- lib/ui/pages/more/about_application.dart | 633 ++++++++++++----------- 1 file changed, 326 insertions(+), 307 deletions(-) diff --git a/lib/ui/pages/more/about_application.dart b/lib/ui/pages/more/about_application.dart index 06bf1100..bcddcfa4 100644 --- a/lib/ui/pages/more/about_application.dart +++ b/lib/ui/pages/more/about_application.dart @@ -1,3 +1,5 @@ +import 'dart:io'; + import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; @@ -12,32 +14,70 @@ class AboutApplicationPage extends StatelessWidget { const AboutApplicationPage({super.key}); @override - Widget build(final BuildContext context) => BrandHeroScreen( - hasBackButton: true, - hasFlashButton: false, - heroTitle: 'about_application_page.title'.tr(), - bodyPadding: const EdgeInsets.symmetric(vertical: 16), - children: [ - Padding( - padding: const EdgeInsets.all(16), - child: Text( - 'about_application_page.versions'.tr(), - style: Theme.of(context).textTheme.labelLarge!.copyWith( - color: Theme.of(context).colorScheme.secondary, - ), - ), + Widget build(final BuildContext context) { + IconData getPlatformIcon() { + if (Platform.isAndroid) { + return Icons.phone_android_outlined; + } else if (Platform.isIOS) { + return Icons.phone_iphone_outlined; + } else if (Platform.isWindows || Platform.isLinux) { + return Icons.desktop_windows_outlined; + } else if (Platform.isMacOS) { + return Icons.desktop_mac_outlined; + } else { + return Icons.devices_other_outlined; + } + } + + final deviceIcon = getPlatformIcon(); + + return BrandHeroScreen( + hasBackButton: true, + hasFlashButton: false, + heroTitle: 'about_application_page.title'.tr(), + bodyPadding: const EdgeInsets.symmetric(vertical: 16), + children: [ + Padding( + padding: const EdgeInsets.all(16), + child: Text( + 'about_application_page.versions'.tr(), + style: Theme.of(context).textTheme.labelLarge!.copyWith( + color: Theme.of(context).colorScheme.secondary, + ), ), + ), + FutureBuilder( + future: _packageVersion(), + builder: (final context, final snapshot) => ListTile( + title: Text( + 'about_application_page.application_version_text'.tr(), + ), + subtitle: Text( + snapshot.data.toString(), + ), + leading: Icon( + deviceIcon, + ), + onLongPress: () { + PlatformAdapter.setClipboard( + snapshot.data.toString(), + ); + getIt().showSnackBar( + 'basis.copied_to_clipboard'.tr(), + ); + }, + ), + ), + if (getIt().apiData.apiVersion.data != null) FutureBuilder( - future: _packageVersion(), + future: _apiVersion(), builder: (final context, final snapshot) => ListTile( title: Text( - 'about_application_page.application_version_text'.tr(), - ), - subtitle: Text( - snapshot.data.toString(), + 'about_application_page.api_version_text'.tr(), ), + subtitle: Text(snapshot.data.toString()), leading: const Icon( - Icons.phone_android_outlined, + Icons.api_outlined, ), onLongPress: () { PlatformAdapter.setClipboard( @@ -49,313 +89,292 @@ class AboutApplicationPage extends StatelessWidget { }, ), ), - if (getIt().apiData.apiVersion.data != null) - FutureBuilder( - future: _apiVersion(), - builder: (final context, final snapshot) => ListTile( - title: Text( - 'about_application_page.api_version_text'.tr(), + FutureBuilder( + future: _packageVersion(), + builder: (final context, final snapshot) => ListTile( + title: Text('about_application_page.open_source_licenses'.tr()), + onTap: () => showLicensePage( + context: context, + applicationName: 'SelfPrivacy', + applicationVersion: snapshot.data.toString(), + applicationLegalese: '© 2024 SelfPrivacy', + ), + leading: const Icon( + Icons.copyright_outlined, + ), + ), + ), + Padding( + padding: const EdgeInsets.all(16), + child: Text( + 'about_application_page.links'.tr(), + style: Theme.of(context).textTheme.labelLarge!.copyWith( + color: Theme.of(context).colorScheme.secondary, ), - subtitle: Text(snapshot.data.toString()), - leading: const Icon( - Icons.api_outlined, + ), + ), + ListTile( + title: Text('about_application_page.website'.tr()), + subtitle: const Text('selfprivacy.org'), + onTap: () => launchUrl( + Uri.parse('https://selfprivacy.org/'), + mode: LaunchMode.externalApplication, + ), + leading: const Icon( + Icons.language_outlined, + ), + onLongPress: () { + PlatformAdapter.setClipboard( + 'https://selfprivacy.org/', + ); + getIt().showSnackBar( + 'basis.copied_to_clipboard'.tr(), + ); + }, + ), + ListTile( + title: Text('about_application_page.documentation'.tr()), + subtitle: const Text('selfprivacy.org/docs'), + onTap: () => launchUrl( + Uri.parse('https://selfprivacy.org/docs/'), + mode: LaunchMode.externalApplication, + ), + leading: const Icon( + Icons.library_books_outlined, + ), + onLongPress: () { + PlatformAdapter.setClipboard( + 'https://selfprivacy.org/docs/', + ); + getIt().showSnackBar( + 'basis.copied_to_clipboard'.tr(), + ); + }, + ), + ListTile( + title: Text('about_application_page.privacy_policy'.tr()), + subtitle: const Text('selfprivacy.org/privacy-policy'), + onTap: () => launchUrl( + Uri.parse('https://selfprivacy.org/privacy-policy/'), + mode: LaunchMode.externalApplication, + ), + leading: const Icon( + Icons.policy_outlined, + ), + onLongPress: () { + PlatformAdapter.setClipboard( + 'https://selfprivacy.org/privacy-policy/', + ); + getIt().showSnackBar( + 'basis.copied_to_clipboard'.tr(), + ); + }, + ), + // Matrix channel + ListTile( + title: Text('about_application_page.matrix_channel'.tr()), + subtitle: const Text('#news:selfprivacy.org'), + onTap: () => launchUrl( + Uri.parse('https://matrix.to/#/#news:selfprivacy.org'), + mode: LaunchMode.externalApplication, + ), + leading: const Icon( + Icons.feed_outlined, + ), + onLongPress: () { + PlatformAdapter.setClipboard( + '#news:selfprivacy.org', + ); + getIt().showSnackBar( + 'basis.copied_to_clipboard'.tr(), + ); + }, + ), + // Telegram channel + ListTile( + title: Text('about_application_page.telegram_channel'.tr()), + subtitle: const Text('@selfprivacy'), + onTap: () => launchUrl( + Uri.parse('https://t.me/selfprivacy'), + mode: LaunchMode.externalApplication, + ), + leading: const Icon( + Icons.feed_outlined, + ), + onLongPress: () { + PlatformAdapter.setClipboard( + '@selfprivacy', + ); + getIt().showSnackBar( + 'basis.copied_to_clipboard'.tr(), + ); + }, + ), + Padding( + padding: const EdgeInsets.all(16), + child: Text( + 'about_application_page.get_support'.tr(), + style: Theme.of(context).textTheme.labelLarge!.copyWith( + color: Theme.of(context).colorScheme.secondary, ), - onLongPress: () { - PlatformAdapter.setClipboard( - snapshot.data.toString(), - ); - getIt().showSnackBar( - 'basis.copied_to_clipboard'.tr(), - ); - }, - ), - ), - FutureBuilder( - future: _packageVersion(), - builder: (final context, final snapshot) => ListTile( - title: Text('about_application_page.open_source_licenses'.tr()), - onTap: () => showLicensePage( - context: context, - applicationName: 'SelfPrivacy', - applicationVersion: snapshot.data.toString(), - applicationLegalese: '© 2024 SelfPrivacy', - ), - leading: const Icon( - Icons.copyright_outlined, - ), - ), ), - Padding( - padding: const EdgeInsets.all(16), - child: Text( - 'about_application_page.links'.tr(), - style: Theme.of(context).textTheme.labelLarge!.copyWith( - color: Theme.of(context).colorScheme.secondary, - ), - ), + ), + // Matrix + ListTile( + title: Text('about_application_page.matrix_support_chat'.tr()), + subtitle: const Text('#chat:selfprivacy.org'), + onTap: () => launchUrl( + Uri.parse('https://matrix.to/#/#chat:selfprivacy.org'), + mode: LaunchMode.externalApplication, ), - ListTile( - title: Text('about_application_page.website'.tr()), - subtitle: const Text('selfprivacy.org'), - onTap: () => launchUrl( - Uri.parse('https://selfprivacy.org/'), - mode: LaunchMode.externalApplication, - ), - leading: const Icon( - Icons.language_outlined, - ), - onLongPress: () { - PlatformAdapter.setClipboard( - 'https://selfprivacy.org/', - ); - getIt().showSnackBar( - 'basis.copied_to_clipboard'.tr(), - ); - }, + leading: const Icon( + Icons.question_answer_outlined, ), - ListTile( - title: Text('about_application_page.documentation'.tr()), - subtitle: const Text('selfprivacy.org/docs'), - onTap: () => launchUrl( - Uri.parse('https://selfprivacy.org/docs/'), - mode: LaunchMode.externalApplication, - ), - leading: const Icon( - Icons.library_books_outlined, - ), - onLongPress: () { - PlatformAdapter.setClipboard( - 'https://selfprivacy.org/docs/', - ); - getIt().showSnackBar( - 'basis.copied_to_clipboard'.tr(), - ); - }, + onLongPress: () { + PlatformAdapter.setClipboard( + '#chat:selfprivacy.org', + ); + getIt().showSnackBar( + 'basis.copied_to_clipboard'.tr(), + ); + }, + ), + // Telegram + ListTile( + title: Text('about_application_page.telegram_support_chat'.tr()), + subtitle: const Text('@selfprivacy_chat'), + onTap: () => launchUrl( + Uri.parse('https://t.me/selfprivacy_chat'), + mode: LaunchMode.externalApplication, ), - ListTile( - title: Text('about_application_page.privacy_policy'.tr()), - subtitle: const Text('selfprivacy.org/privacy-policy'), - onTap: () => launchUrl( - Uri.parse('https://selfprivacy.org/privacy-policy/'), - mode: LaunchMode.externalApplication, - ), - leading: const Icon( - Icons.policy_outlined, - ), - onLongPress: () { - PlatformAdapter.setClipboard( - 'https://selfprivacy.org/privacy-policy/', - ); - getIt().showSnackBar( - 'basis.copied_to_clipboard'.tr(), - ); - }, + leading: const Icon( + Icons.question_answer_outlined, ), - // Matrix channel - ListTile( - title: Text('about_application_page.matrix_channel'.tr()), - subtitle: const Text('#news:selfprivacy.org'), - onTap: () => launchUrl( - Uri.parse('https://matrix.to/#/#news:selfprivacy.org'), - mode: LaunchMode.externalApplication, - ), - leading: const Icon( - Icons.feed_outlined, - ), - onLongPress: () { - PlatformAdapter.setClipboard( - '#news:selfprivacy.org', - ); - getIt().showSnackBar( - 'basis.copied_to_clipboard'.tr(), - ); - }, + onLongPress: () { + PlatformAdapter.setClipboard( + '@selfprivacy_chat', + ); + getIt().showSnackBar( + 'basis.copied_to_clipboard'.tr(), + ); + }, + ), + // Email + ListTile( + title: Text('about_application_page.email_support'.tr()), + subtitle: const Text('support@selfprivacy.org'), + onTap: () => launchUrl( + Uri.parse('mailto:support@selfprivacy.org'), + mode: LaunchMode.externalApplication, ), - // Telegram channel - ListTile( - title: Text('about_application_page.telegram_channel'.tr()), - subtitle: const Text('@selfprivacy'), - onTap: () => launchUrl( - Uri.parse('https://t.me/selfprivacy'), - mode: LaunchMode.externalApplication, - ), - leading: const Icon( - Icons.feed_outlined, - ), - onLongPress: () { - PlatformAdapter.setClipboard( - '@selfprivacy', - ); - getIt().showSnackBar( - 'basis.copied_to_clipboard'.tr(), - ); - }, + leading: const Icon( + Icons.email_outlined, ), - Padding( - padding: const EdgeInsets.all(16), - child: Text( - 'about_application_page.get_support'.tr(), - style: Theme.of(context).textTheme.labelLarge!.copyWith( - color: Theme.of(context).colorScheme.secondary, - ), - ), + onLongPress: () { + PlatformAdapter.setClipboard( + 'support@selfprivacy.org', + ); + getIt().showSnackBar( + 'basis.copied_to_clipboard'.tr(), + ); + }, + ), + Padding( + padding: const EdgeInsets.all(16), + child: Text( + 'about_application_page.contribute'.tr(), + style: Theme.of(context).textTheme.labelLarge!.copyWith( + color: Theme.of(context).colorScheme.secondary, + ), ), - // Matrix - ListTile( - title: Text('about_application_page.matrix_support_chat'.tr()), - subtitle: const Text('#chat:selfprivacy.org'), - onTap: () => launchUrl( - Uri.parse('https://matrix.to/#/#chat:selfprivacy.org'), - mode: LaunchMode.externalApplication, + ), + // Source code + ListTile( + title: Text('about_application_page.source_code'.tr()), + subtitle: const Text('git.selfprivacy.org'), + onTap: () => launchUrl( + Uri.parse( + 'https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app', ), - leading: const Icon( - Icons.question_answer_outlined, - ), - onLongPress: () { - PlatformAdapter.setClipboard( - '#chat:selfprivacy.org', - ); - getIt().showSnackBar( - 'basis.copied_to_clipboard'.tr(), - ); - } + mode: LaunchMode.externalApplication, ), - // Telegram - ListTile( - title: Text('about_application_page.telegram_support_chat'.tr()), - subtitle: const Text('@selfprivacy_chat'), - onTap: () => launchUrl( - Uri.parse('https://t.me/selfprivacy_chat'), - mode: LaunchMode.externalApplication, - ), - leading: const Icon( - Icons.question_answer_outlined, - ), - onLongPress: () { - PlatformAdapter.setClipboard( - '@selfprivacy_chat', - ); - getIt().showSnackBar( - 'basis.copied_to_clipboard'.tr(), - ); - }, + leading: const Icon( + Icons.code_outlined, ), - // Email - ListTile( - title: Text('about_application_page.email_support'.tr()), - subtitle: const Text('support@selfprivacy.org'), - onTap: () => launchUrl( - Uri.parse('mailto:support@selfprivacy.org'), - mode: LaunchMode.externalApplication, + onLongPress: () { + PlatformAdapter.setClipboard( + 'https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app', + ); + getIt().showSnackBar( + 'basis.copied_to_clipboard'.tr(), + ); + }, + ), + // translate + ListTile( + title: Text('about_application_page.help_translate'.tr()), + subtitle: const Text('weblate.selfprivacy.org'), + onTap: () => launchUrl( + Uri.parse( + 'https://weblate.selfprivacy.org/projects/selfprivacy/', ), - leading: const Icon( - Icons.email_outlined, - ), - onLongPress: () { - PlatformAdapter.setClipboard( - 'support@selfprivacy.org', - ); - getIt().showSnackBar( - 'basis.copied_to_clipboard'.tr(), - ); - }, + mode: LaunchMode.externalApplication, ), - Padding( - padding: const EdgeInsets.all(16), - child: Text( - 'about_application_page.contribute'.tr(), - style: Theme.of(context).textTheme.labelLarge!.copyWith( - color: Theme.of(context).colorScheme.secondary, - ), - ), + leading: const Icon( + Icons.translate_outlined, ), - // Source code - ListTile( - title: Text('about_application_page.source_code'.tr()), - subtitle: const Text('git.selfprivacy.org'), - onTap: () => launchUrl( - Uri.parse( - 'https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app', - ), - mode: LaunchMode.externalApplication, - ), - leading: const Icon( - Icons.code_outlined, - ), - onLongPress: () { - PlatformAdapter.setClipboard( - 'https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app', - ); - getIt().showSnackBar( - 'basis.copied_to_clipboard'.tr(), - ); - }, + onLongPress: () { + PlatformAdapter.setClipboard( + 'https://weblate.selfprivacy.org/projects/selfprivacy/', + ); + getIt().showSnackBar( + 'basis.copied_to_clipboard'.tr(), + ); + }, + ), + // matrix chat + ListTile( + title: Text('about_application_page.matrix_contributors_chat'.tr()), + subtitle: const Text('#dev:selfprivacy.org'), + onTap: () => launchUrl( + Uri.parse('https://matrix.to/#/#dev:selfprivacy.org'), + mode: LaunchMode.externalApplication, ), - // translate - ListTile( - title: Text('about_application_page.help_translate'.tr()), - subtitle: const Text('weblate.selfprivacy.org'), - onTap: () => launchUrl( - Uri.parse( - 'https://weblate.selfprivacy.org/projects/selfprivacy/', - ), - mode: LaunchMode.externalApplication, - ), - leading: const Icon( - Icons.translate_outlined, - ), - onLongPress: () { - PlatformAdapter.setClipboard( - 'https://weblate.selfprivacy.org/projects/selfprivacy/', - ); - getIt().showSnackBar( - 'basis.copied_to_clipboard'.tr(), - ); - }, + leading: const Icon( + Icons.question_answer_outlined, ), - // matrix chat - ListTile( - title: Text('about_application_page.matrix_contributors_chat'.tr()), - subtitle: const Text('#dev:selfprivacy.org'), - onTap: () => launchUrl( - Uri.parse('https://matrix.to/#/#dev:selfprivacy.org'), - mode: LaunchMode.externalApplication, - ), - leading: const Icon( - Icons.question_answer_outlined, - ), - onLongPress: () { - PlatformAdapter.setClipboard( - '#dev:selfprivacy.org', - ); - getIt().showSnackBar( - 'basis.copied_to_clipboard'.tr(), - ); - }, + onLongPress: () { + PlatformAdapter.setClipboard( + '#dev:selfprivacy.org', + ); + getIt().showSnackBar( + 'basis.copied_to_clipboard'.tr(), + ); + }, + ), + // telegram + ListTile( + title: Text('about_application_page.telegram_contributors_chat'.tr()), + subtitle: const Text('@selfprivacy_dev'), + onTap: () => launchUrl( + Uri.parse('https://t.me/selfprivacy_dev'), + mode: LaunchMode.externalApplication, ), - // telegram - ListTile( - title: - Text('about_application_page.telegram_contributors_chat'.tr()), - subtitle: const Text('@selfprivacy_dev'), - onTap: () => launchUrl( - Uri.parse('https://t.me/selfprivacy_dev'), - mode: LaunchMode.externalApplication, - ), - leading: const Icon( - Icons.question_answer_outlined, - ), - onLongPress: () { - PlatformAdapter.setClipboard( - '@selfprivacy_dev', - ); - getIt().showSnackBar( - 'basis.copied_to_clipboard'.tr(), - ); - }, + leading: const Icon( + Icons.question_answer_outlined, ), - ], - ); + onLongPress: () { + PlatformAdapter.setClipboard( + '@selfprivacy_dev', + ); + getIt().showSnackBar( + 'basis.copied_to_clipboard'.tr(), + ); + }, + ), + ], + ); + } Future _packageVersion() async { String packageVersion = 'unknown'; From e36cba045a43073fa331776442fa501653c11633 Mon Sep 17 00:00:00 2001 From: Inex Code Date: Wed, 21 Feb 2024 02:11:57 +0300 Subject: [PATCH 45/51] feat(ui): Select device icon depending on the screen width --- lib/ui/pages/more/about_application.dart | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/ui/pages/more/about_application.dart b/lib/ui/pages/more/about_application.dart index bcddcfa4..ad27b332 100644 --- a/lib/ui/pages/more/about_application.dart +++ b/lib/ui/pages/more/about_application.dart @@ -6,6 +6,7 @@ import 'package:flutter/material.dart'; import 'package:package_info/package_info.dart'; import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; +import 'package:selfprivacy/utils/breakpoints.dart'; import 'package:selfprivacy/utils/platform_adapter.dart'; import 'package:url_launcher/url_launcher.dart'; @@ -17,9 +18,15 @@ class AboutApplicationPage extends StatelessWidget { Widget build(final BuildContext context) { IconData getPlatformIcon() { if (Platform.isAndroid) { - return Icons.phone_android_outlined; + if (Breakpoints.small.isActive(context)) { + return Icons.phone_android_outlined; + } + return Icons.tablet_android_outlined; } else if (Platform.isIOS) { - return Icons.phone_iphone_outlined; + if (Breakpoints.small.isActive(context)) { + return Icons.phone_iphone_outlined; + } + return Icons.tablet_mac_outlined; } else if (Platform.isWindows || Platform.isLinux) { return Icons.desktop_windows_outlined; } else if (Platform.isMacOS) { From 490e5f92f35bc06c6f270ed40f34a7ba04abbc5a Mon Sep 17 00:00:00 2001 From: Inex Code Date: Wed, 21 Feb 2024 17:48:31 +0300 Subject: [PATCH 46/51] refactor(ui): Code deduplication in AboutApplicationPage --- assets/translations/en.json | 2 + lib/ui/pages/more/about_application.dart | 397 ++++++++--------------- 2 files changed, 137 insertions(+), 262 deletions(-) diff --git a/assets/translations/en.json b/assets/translations/en.json index e0f281af..2e75f9cd 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -66,6 +66,8 @@ "email_support": "Email support", "contribute": "Contribute", "source_code": "Source code", + "bug_report": "Report a bug", + "bug_report_subtitle": "Due to spam, manual account confirmation is required. Contact us in the support chat to activate your account.", "help_translate": "Help us translate", "matrix_contributors_chat": "Matrix contributors chat", "telegram_contributors_chat": "Telegram contributors chat", diff --git a/lib/ui/pages/more/about_application.dart b/lib/ui/pages/more/about_application.dart index ad27b332..b652b8af 100644 --- a/lib/ui/pages/more/about_application.dart +++ b/lib/ui/pages/more/about_application.dart @@ -44,15 +44,7 @@ class AboutApplicationPage extends StatelessWidget { heroTitle: 'about_application_page.title'.tr(), bodyPadding: const EdgeInsets.symmetric(vertical: 16), children: [ - Padding( - padding: const EdgeInsets.all(16), - child: Text( - 'about_application_page.versions'.tr(), - style: Theme.of(context).textTheme.labelLarge!.copyWith( - color: Theme.of(context).colorScheme.secondary, - ), - ), - ), + SectionTitle(title: 'about_application_page.versions'.tr()), FutureBuilder( future: _packageVersion(), builder: (final context, final snapshot) => ListTile( @@ -111,273 +103,98 @@ class AboutApplicationPage extends StatelessWidget { ), ), ), - Padding( - padding: const EdgeInsets.all(16), - child: Text( - 'about_application_page.links'.tr(), - style: Theme.of(context).textTheme.labelLarge!.copyWith( - color: Theme.of(context).colorScheme.secondary, - ), - ), + SectionTitle( + title: 'about_application_page.links'.tr(), ), - ListTile( - title: Text('about_application_page.website'.tr()), - subtitle: const Text('selfprivacy.org'), - onTap: () => launchUrl( - Uri.parse('https://selfprivacy.org/'), - mode: LaunchMode.externalApplication, - ), - leading: const Icon( - Icons.language_outlined, - ), - onLongPress: () { - PlatformAdapter.setClipboard( - 'https://selfprivacy.org/', - ); - getIt().showSnackBar( - 'basis.copied_to_clipboard'.tr(), - ); - }, + LinkListTile( + title: 'about_application_page.website'.tr(), + subtitle: 'selfprivacy.org', + uri: 'https://selfprivacy.org/', + icon: Icons.language_outlined, ), - ListTile( - title: Text('about_application_page.documentation'.tr()), - subtitle: const Text('selfprivacy.org/docs'), - onTap: () => launchUrl( - Uri.parse('https://selfprivacy.org/docs/'), - mode: LaunchMode.externalApplication, - ), - leading: const Icon( - Icons.library_books_outlined, - ), - onLongPress: () { - PlatformAdapter.setClipboard( - 'https://selfprivacy.org/docs/', - ); - getIt().showSnackBar( - 'basis.copied_to_clipboard'.tr(), - ); - }, + LinkListTile( + title: 'about_application_page.documentation'.tr(), + subtitle: 'selfprivacy.org/docs', + uri: 'https://selfprivacy.org/docs/', + icon: Icons.library_books_outlined, ), - ListTile( - title: Text('about_application_page.privacy_policy'.tr()), - subtitle: const Text('selfprivacy.org/privacy-policy'), - onTap: () => launchUrl( - Uri.parse('https://selfprivacy.org/privacy-policy/'), - mode: LaunchMode.externalApplication, - ), - leading: const Icon( - Icons.policy_outlined, - ), - onLongPress: () { - PlatformAdapter.setClipboard( - 'https://selfprivacy.org/privacy-policy/', - ); - getIt().showSnackBar( - 'basis.copied_to_clipboard'.tr(), - ); - }, + LinkListTile( + title: 'about_application_page.privacy_policy'.tr(), + subtitle: 'selfprivacy.org/privacy-policy', + uri: 'https://selfprivacy.org/privacy-policy/', + icon: Icons.policy_outlined, ), - // Matrix channel - ListTile( - title: Text('about_application_page.matrix_channel'.tr()), - subtitle: const Text('#news:selfprivacy.org'), - onTap: () => launchUrl( - Uri.parse('https://matrix.to/#/#news:selfprivacy.org'), - mode: LaunchMode.externalApplication, - ), - leading: const Icon( - Icons.feed_outlined, - ), - onLongPress: () { - PlatformAdapter.setClipboard( - '#news:selfprivacy.org', - ); - getIt().showSnackBar( - 'basis.copied_to_clipboard'.tr(), - ); - }, + LinkListTile( + title: 'about_application_page.matrix_channel'.tr(), + subtitle: '#news:selfprivacy.org', + uri: 'https://matrix.to/#/#news:selfprivacy.org', + icon: Icons.feed_outlined, + longPressText: '#news:selfprivacy.org', ), - // Telegram channel - ListTile( - title: Text('about_application_page.telegram_channel'.tr()), - subtitle: const Text('@selfprivacy'), - onTap: () => launchUrl( - Uri.parse('https://t.me/selfprivacy'), - mode: LaunchMode.externalApplication, - ), - leading: const Icon( - Icons.feed_outlined, - ), - onLongPress: () { - PlatformAdapter.setClipboard( - '@selfprivacy', - ); - getIt().showSnackBar( - 'basis.copied_to_clipboard'.tr(), - ); - }, + LinkListTile( + title: 'about_application_page.telegram_channel'.tr(), + subtitle: '@selfprivacy', + uri: 'https://t.me/selfprivacy', + icon: Icons.feed_outlined, + longPressText: '@selfprivacy', ), - Padding( - padding: const EdgeInsets.all(16), - child: Text( - 'about_application_page.get_support'.tr(), - style: Theme.of(context).textTheme.labelLarge!.copyWith( - color: Theme.of(context).colorScheme.secondary, - ), - ), + SectionTitle(title: 'about_application_page.get_support'.tr()), + LinkListTile( + title: 'about_application_page.matrix_support_chat'.tr(), + subtitle: '#chat:selfprivacy.org', + uri: 'https://matrix.to/#/#chat:selfprivacy.org', + icon: Icons.question_answer_outlined, + longPressText: '#chat:selfprivacy.org', ), - // Matrix - ListTile( - title: Text('about_application_page.matrix_support_chat'.tr()), - subtitle: const Text('#chat:selfprivacy.org'), - onTap: () => launchUrl( - Uri.parse('https://matrix.to/#/#chat:selfprivacy.org'), - mode: LaunchMode.externalApplication, - ), - leading: const Icon( - Icons.question_answer_outlined, - ), - onLongPress: () { - PlatformAdapter.setClipboard( - '#chat:selfprivacy.org', - ); - getIt().showSnackBar( - 'basis.copied_to_clipboard'.tr(), - ); - }, + LinkListTile( + title: 'about_application_page.telegram_support_chat'.tr(), + subtitle: '@selfprivacy_chat', + uri: 'https://t.me/selfprivacy_chat', + icon: Icons.question_answer_outlined, + longPressText: '@selfprivacy_chat', ), - // Telegram - ListTile( - title: Text('about_application_page.telegram_support_chat'.tr()), - subtitle: const Text('@selfprivacy_chat'), - onTap: () => launchUrl( - Uri.parse('https://t.me/selfprivacy_chat'), - mode: LaunchMode.externalApplication, - ), - leading: const Icon( - Icons.question_answer_outlined, - ), - onLongPress: () { - PlatformAdapter.setClipboard( - '@selfprivacy_chat', - ); - getIt().showSnackBar( - 'basis.copied_to_clipboard'.tr(), - ); - }, + LinkListTile( + title: 'about_application_page.email_support'.tr(), + subtitle: 'support@selfprivacy.org', + uri: 'mailto:support@selfprivacy.org', + icon: Icons.email_outlined, + longPressText: 'support@selfprivacy.org', ), - // Email - ListTile( - title: Text('about_application_page.email_support'.tr()), - subtitle: const Text('support@selfprivacy.org'), - onTap: () => launchUrl( - Uri.parse('mailto:support@selfprivacy.org'), - mode: LaunchMode.externalApplication, - ), - leading: const Icon( - Icons.email_outlined, - ), - onLongPress: () { - PlatformAdapter.setClipboard( - 'support@selfprivacy.org', - ); - getIt().showSnackBar( - 'basis.copied_to_clipboard'.tr(), - ); - }, + SectionTitle( + title: 'about_application_page.contribute'.tr(), ), - Padding( - padding: const EdgeInsets.all(16), - child: Text( - 'about_application_page.contribute'.tr(), - style: Theme.of(context).textTheme.labelLarge!.copyWith( - color: Theme.of(context).colorScheme.secondary, - ), - ), + LinkListTile( + title: 'about_application_page.source_code'.tr(), + subtitle: 'git.selfprivacy.org', + uri: 'https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app', + icon: Icons.code_outlined, ), - // Source code - ListTile( - title: Text('about_application_page.source_code'.tr()), - subtitle: const Text('git.selfprivacy.org'), - onTap: () => launchUrl( - Uri.parse( - 'https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app', - ), - mode: LaunchMode.externalApplication, - ), - leading: const Icon( - Icons.code_outlined, - ), - onLongPress: () { - PlatformAdapter.setClipboard( - 'https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app', - ); - getIt().showSnackBar( - 'basis.copied_to_clipboard'.tr(), - ); - }, + LinkListTile( + title: 'about_application_page.bug_report'.tr(), + subtitle: 'about_application_page.bug_report_subtitle'.tr(), + uri: + 'https://git.selfprivacy.org/SelfPrivacy/selfprivacy.org.app/issues', + icon: Icons.bug_report_outlined, ), - // translate - ListTile( - title: Text('about_application_page.help_translate'.tr()), - subtitle: const Text('weblate.selfprivacy.org'), - onTap: () => launchUrl( - Uri.parse( - 'https://weblate.selfprivacy.org/projects/selfprivacy/', - ), - mode: LaunchMode.externalApplication, - ), - leading: const Icon( - Icons.translate_outlined, - ), - onLongPress: () { - PlatformAdapter.setClipboard( - 'https://weblate.selfprivacy.org/projects/selfprivacy/', - ); - getIt().showSnackBar( - 'basis.copied_to_clipboard'.tr(), - ); - }, + LinkListTile( + title: 'about_application_page.help_translate'.tr(), + subtitle: 'weblate.selfprivacy.org', + uri: 'https://weblate.selfprivacy.org/projects/selfprivacy/', + icon: Icons.translate_outlined, ), - // matrix chat - ListTile( - title: Text('about_application_page.matrix_contributors_chat'.tr()), - subtitle: const Text('#dev:selfprivacy.org'), - onTap: () => launchUrl( - Uri.parse('https://matrix.to/#/#dev:selfprivacy.org'), - mode: LaunchMode.externalApplication, - ), - leading: const Icon( - Icons.question_answer_outlined, - ), - onLongPress: () { - PlatformAdapter.setClipboard( - '#dev:selfprivacy.org', - ); - getIt().showSnackBar( - 'basis.copied_to_clipboard'.tr(), - ); - }, + LinkListTile( + title: 'about_application_page.matrix_contributors_chat'.tr(), + subtitle: '#dev:selfprivacy.org', + uri: 'https://matrix.to/#/#dev:selfprivacy.org', + icon: Icons.question_answer_outlined, + longPressText: '#dev:selfprivacy.org', ), - // telegram - ListTile( - title: Text('about_application_page.telegram_contributors_chat'.tr()), - subtitle: const Text('@selfprivacy_dev'), - onTap: () => launchUrl( - Uri.parse('https://t.me/selfprivacy_dev'), - mode: LaunchMode.externalApplication, - ), - leading: const Icon( - Icons.question_answer_outlined, - ), - onLongPress: () { - PlatformAdapter.setClipboard( - '@selfprivacy_dev', - ); - getIt().showSnackBar( - 'basis.copied_to_clipboard'.tr(), - ); - }, + LinkListTile( + title: 'about_application_page.telegram_contributors_chat'.tr(), + subtitle: '@selfprivacy_dev', + uri: 'https://t.me/selfprivacy_dev', + icon: Icons.question_answer_outlined, + longPressText: '@selfprivacy_dev', ), ], ); @@ -402,3 +219,59 @@ class AboutApplicationPage extends StatelessWidget { return apiVersion; } } + +class SectionTitle extends StatelessWidget { + const SectionTitle({ + required this.title, + super.key, + }); + + final String title; + + @override + Widget build(final BuildContext context) => Padding( + padding: const EdgeInsets.all(16), + child: Text( + title, + style: Theme.of(context).textTheme.labelLarge!.copyWith( + color: Theme.of(context).colorScheme.secondary, + ), + ), + ); +} + +class LinkListTile extends StatelessWidget { + const LinkListTile({ + required this.title, + required this.subtitle, + required this.uri, + required this.icon, + this.longPressText, + super.key, + }); + + final String title; + final String subtitle; + final String uri; + final IconData icon; + final String? longPressText; + + @override + Widget build(final BuildContext context) => ListTile( + title: Text(title), + subtitle: Text(subtitle), + onTap: () => launchUrl( + Uri.parse(uri), + mode: LaunchMode.externalApplication, + ), + leading: Icon(icon), + onLongPress: () { + PlatformAdapter.setClipboard( + longPressText ?? uri, + ); + getIt().showSnackBar( + 'basis.copied_to_clipboard'.tr(), + ); + }, + ); +} From a9a7b04ad5e3cdd848d3ce24ca7632e70e8053dd Mon Sep 17 00:00:00 2001 From: Inex Code Date: Fri, 23 Feb 2024 19:50:28 +0300 Subject: [PATCH 47/51] fix: Return the binds migration interface Turns out, there are still servers that didn't perform the binds migration. The can't perform it anymore because email changed the id. I'm getting back the option to perform the binds migration, with some fallback defaults. --- .../graphql_maps/server_api/volume_api.dart | 11 ++-- .../bloc/server_jobs/server_jobs_bloc.dart | 20 +++++++ .../components/list_tiles/section_title.dart | 21 +++++++ lib/ui/pages/more/about_application.dart | 21 +------ .../more/app_settings/developer_settings.dart | 55 ++++++++++--------- .../binds_migration/services_migration.dart | 26 ++++++--- lib/ui/pages/services/service_page.dart | 1 + lib/ui/router/router.gr.dart | 8 ++- 8 files changed, 103 insertions(+), 60 deletions(-) create mode 100644 lib/ui/components/list_tiles/section_title.dart 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 713544eb..c111baa4 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 @@ -60,17 +60,18 @@ mixin VolumeApi on GraphQLApiMap { Future> migrateToBinds( final Map serviceToDisk, + final String fallbackDrive, ) async { GenericResult? mutation; try { final GraphQLClient client = await getClient(); final input = Input$MigrateToBindsInput( - bitwardenBlockDevice: serviceToDisk['bitwarden']!, - emailBlockDevice: serviceToDisk['mailserver']!, - giteaBlockDevice: serviceToDisk['gitea']!, - nextcloudBlockDevice: serviceToDisk['nextcloud']!, - pleromaBlockDevice: serviceToDisk['pleroma']!, + bitwardenBlockDevice: serviceToDisk['bitwarden'] ?? fallbackDrive, + emailBlockDevice: serviceToDisk['email'] ?? fallbackDrive, + giteaBlockDevice: serviceToDisk['gitea'] ?? fallbackDrive, + nextcloudBlockDevice: serviceToDisk['nextcloud'] ?? fallbackDrive, + pleromaBlockDevice: serviceToDisk['pleroma'] ?? fallbackDrive, ); final variables = Variables$Mutation$MigrateToBinds(input: input); final migrateMutation = diff --git a/lib/logic/bloc/server_jobs/server_jobs_bloc.dart b/lib/logic/bloc/server_jobs/server_jobs_bloc.dart index 43b415e6..3ae45aa9 100644 --- a/lib/logic/bloc/server_jobs/server_jobs_bloc.dart +++ b/lib/logic/bloc/server_jobs/server_jobs_bloc.dart @@ -2,6 +2,7 @@ import 'dart:async'; import 'package:bloc_concurrency/bloc_concurrency.dart'; import 'package:equatable/equatable.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_bloc/flutter_bloc.dart'; import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/logic/models/json/server_job.dart'; @@ -68,6 +69,25 @@ class ServerJobsBloc extends Bloc { await getIt().removeAllFinishedServerJobs(); } + Future migrateToBinds(final Map serviceToDisk) async { + final fallbackDrive = getIt() + .apiData + .volumes + .data + ?.where((final drive) => drive.root) + .first + .name ?? + 'sda1'; + final result = await getIt() + .api + .migrateToBinds(serviceToDisk, fallbackDrive); + if (result.data == null) { + getIt() + .showSnackBar(result.message!, behavior: SnackBarBehavior.floating); + return; + } + } + @override void onChange(final Change change) { super.onChange(change); diff --git a/lib/ui/components/list_tiles/section_title.dart b/lib/ui/components/list_tiles/section_title.dart new file mode 100644 index 00000000..f37fb09d --- /dev/null +++ b/lib/ui/components/list_tiles/section_title.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; + +class SectionTitle extends StatelessWidget { + const SectionTitle({ + required this.title, + super.key, + }); + + final String title; + + @override + Widget build(final BuildContext context) => Padding( + padding: const EdgeInsets.all(16), + child: Text( + title, + style: Theme.of(context).textTheme.labelLarge!.copyWith( + color: Theme.of(context).colorScheme.secondary, + ), + ), + ); +} diff --git a/lib/ui/pages/more/about_application.dart b/lib/ui/pages/more/about_application.dart index b652b8af..9e6cae65 100644 --- a/lib/ui/pages/more/about_application.dart +++ b/lib/ui/pages/more/about_application.dart @@ -5,6 +5,7 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:package_info/package_info.dart'; import 'package:selfprivacy/config/get_it_config.dart'; +import 'package:selfprivacy/ui/components/list_tiles/section_title.dart'; import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; import 'package:selfprivacy/utils/breakpoints.dart'; import 'package:selfprivacy/utils/platform_adapter.dart'; @@ -220,26 +221,6 @@ class AboutApplicationPage extends StatelessWidget { } } -class SectionTitle extends StatelessWidget { - const SectionTitle({ - required this.title, - super.key, - }); - - final String title; - - @override - Widget build(final BuildContext context) => Padding( - padding: const EdgeInsets.all(16), - child: Text( - title, - style: Theme.of(context).textTheme.labelLarge!.copyWith( - color: Theme.of(context).colorScheme.secondary, - ), - ), - ); -} - class LinkListTile extends StatelessWidget { const LinkListTile({ required this.title, diff --git a/lib/ui/pages/more/app_settings/developer_settings.dart b/lib/ui/pages/more/app_settings/developer_settings.dart index 8acf16a4..cd1c6811 100644 --- a/lib/ui/pages/more/app_settings/developer_settings.dart +++ b/lib/ui/pages/more/app_settings/developer_settings.dart @@ -3,8 +3,12 @@ import 'package:easy_localization/easy_localization.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/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/ui/components/list_tiles/section_title.dart'; import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; +import 'package:selfprivacy/ui/router/router.dart'; @RoutePage() class DeveloperSettingsPage extends StatefulWidget { @@ -23,15 +27,7 @@ class _DeveloperSettingsPageState extends State { heroTitle: 'developer_settings.title'.tr(), heroSubtitle: 'developer_settings.subtitle'.tr(), children: [ - Padding( - padding: const EdgeInsets.all(16), - child: Text( - 'developer_settings.server_setup'.tr(), - style: Theme.of(context).textTheme.labelLarge!.copyWith( - color: Theme.of(context).colorScheme.secondary, - ), - ), - ), + SectionTitle(title: 'developer_settings.server_setup'.tr()), SwitchListTile( title: Text('developer_settings.use_staging_acme'.tr()), subtitle: @@ -59,15 +55,7 @@ class _DeveloperSettingsPageState extends State { () => TlsOptions.allowCustomSshKeyDuringSetup = value, ), ), - Padding( - padding: const EdgeInsets.all(16), - child: Text( - 'developer_settings.routing'.tr(), - style: Theme.of(context).textTheme.labelLarge!.copyWith( - color: Theme.of(context).colorScheme.secondary, - ), - ), - ), + SectionTitle(title: 'developer_settings.routing'.tr()), ListTile( title: Text('developer_settings.reset_onboarding'.tr()), subtitle: @@ -78,15 +66,32 @@ class _DeveloperSettingsPageState extends State { .read() .turnOffOnboarding(isOnboardingShowing: true), ), - Padding( - padding: const EdgeInsets.all(16), - child: Text( - 'developer_settings.cubit_statuses'.tr(), - style: Theme.of(context).textTheme.labelLarge!.copyWith( - color: Theme.of(context).colorScheme.secondary, - ), + ListTile( + title: Text('storage.start_migration_button'.tr()), + subtitle: Text('storage.data_migration_notice'.tr()), + enabled: + !context.watch().state.isOnboardingShowing, + onTap: () => context.pushRoute( + 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 == 'email' || + service.id == 'nextcloud', + ) + .toList(), + isMigration: true, + ), ), ), + SectionTitle(title: 'developer_settings.cubit_statuses'.tr()), ListTile( title: const Text('ApiConnectionRepository status'), subtitle: Text( 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 53e72d91..695b8de4 100644 --- a/lib/ui/pages/server_storage/binds_migration/services_migration.dart +++ b/lib/ui/pages/server_storage/binds_migration/services_migration.dart @@ -18,11 +18,13 @@ 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(); @@ -169,18 +171,24 @@ class _ServicesMigrationPageState extends State { ), ), const SizedBox(height: 16), - if (isVolumePicked) + if (widget.isMigration || (!widget.isMigration && isVolumePicked)) BrandButton.filled( child: Text('storage.start_migration_button'.tr()), onPressed: () { - for (final service in widget.services) { - if (serviceToDisk[service.id] != null) { - context.read().add( - ServiceMove( - service, - serviceToDisk[service.id]!, - ), - ); + if (widget.isMigration) { + context.read().migrateToBinds( + serviceToDisk, + ); + } else { + for (final service in widget.services) { + if (serviceToDisk[service.id] != null) { + context.read().add( + ServiceMove( + service, + 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 41e7f7ef..c0064eef 100644 --- a/lib/ui/pages/services/service_page.dart +++ b/lib/ui/pages/services/service_page.dart @@ -114,6 +114,7 @@ class _ServicePageState extends State { ServicesMigrationRoute( 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 1ff6d09c..98f4453a 100644 --- a/lib/ui/router/router.gr.dart +++ b/lib/ui/router/router.gr.dart @@ -159,6 +159,7 @@ abstract class _$RootRouter extends RootStackRouter { child: ServicesMigrationPage( services: args.services, diskStatus: args.diskStatus, + isMigration: args.isMigration, key: args.key, ), ); @@ -575,6 +576,7 @@ class ServicesMigrationRoute extends PageRouteInfo { ServicesMigrationRoute({ required List services, required DiskStatus diskStatus, + required bool isMigration, Key? key, List? children, }) : super( @@ -582,6 +584,7 @@ class ServicesMigrationRoute extends PageRouteInfo { args: ServicesMigrationRouteArgs( services: services, diskStatus: diskStatus, + isMigration: isMigration, key: key, ), initialChildren: children, @@ -597,6 +600,7 @@ class ServicesMigrationRouteArgs { const ServicesMigrationRouteArgs({ required this.services, required this.diskStatus, + required this.isMigration, this.key, }); @@ -604,11 +608,13 @@ class ServicesMigrationRouteArgs { final DiskStatus diskStatus; + final bool isMigration; + final Key? key; @override String toString() { - return 'ServicesMigrationRouteArgs{services: $services, diskStatus: $diskStatus, key: $key}'; + return 'ServicesMigrationRouteArgs{services: $services, diskStatus: $diskStatus, isMigration: $isMigration, key: $key}'; } } From c8577b3bdfa38ad6217dec3e1a12ace62690067b Mon Sep 17 00:00:00 2001 From: Inex Code Date: Fri, 23 Feb 2024 20:15:39 +0300 Subject: [PATCH 48/51] fix: When using fallback upgrade, UI showed that upgrade failed --- lib/logic/cubit/client_jobs/client_jobs_cubit.dart | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/logic/cubit/client_jobs/client_jobs_cubit.dart b/lib/logic/cubit/client_jobs/client_jobs_cubit.dart index 16cadfd0..81d1b0f2 100644 --- a/lib/logic/cubit/client_jobs/client_jobs_cubit.dart +++ b/lib/logic/cubit/client_jobs/client_jobs_cubit.dart @@ -107,6 +107,14 @@ class JobsCubit extends Cubit { const [], ), ); + } else if (result.success) { + emit( + JobsStateFinished( + [UpgradeServerJob(status: JobStatusEnum.finished)], + null, + const [], + ), + ); } else { emit( JobsStateFinished( From 643020ebd73b3a40907c40e8a41fa1792e1e649b Mon Sep 17 00:00:00 2001 From: Inex Code Date: Fri, 1 Mar 2024 11:54:27 +0300 Subject: [PATCH 49/51] fix: Detect the situation when we have faulty link-local IPv6 records --- .../cubit/dns_records/dns_records_cubit.dart | 46 ++++++++++++++++++- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/lib/logic/cubit/dns_records/dns_records_cubit.dart b/lib/logic/cubit/dns_records/dns_records_cubit.dart index cdb4e9b1..1b682dea 100644 --- a/lib/logic/cubit/dns_records/dns_records_cubit.dart +++ b/lib/logic/cubit/dns_records/dns_records_cubit.dart @@ -37,13 +37,22 @@ class DnsRecordsCubit extends ServerConnectionDependentCubit { } 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.message == 'link-local') { + emit( + DnsRecordsState( + dnsState: DnsRecordsStatus.error, + dnsRecords: foundRecords.data, + ), + ); + return; + } + if (!foundRecords.success || foundRecords.data.isEmpty) { emit(const DnsRecordsState()); return; @@ -140,6 +149,17 @@ class DnsRecordsCubit extends ServerConnectionDependentCubit { message: e.toString(), ); } + // If providerDnsRecords contains a link-local ipv6 record, return an error + if (providerDnsRecords.any( + (final r) => + r.type == 'AAAA' && (r.content?.trim().startsWith('fe80::') ?? false), + )) { + return GenericResult( + data: foundRecords, + success: false, + message: 'link-local', + ); + } return GenericResult( data: foundRecords, success: true, @@ -166,6 +186,28 @@ class DnsRecordsCubit extends ServerConnectionDependentCubit { emit(state.copyWith(dnsState: DnsRecordsStatus.refreshing)); final List records = await api.getDnsRecords(); + // If there are explicit link-local ipv6 records, remove them from the list + records.removeWhere( + (final r) => + r.type == 'AAAA' && (r.content?.trim().startsWith('fe80::') ?? false), + ); + + // If there are no AAAA records, make empty copies of A records + if (!records.any((final r) => r.type == 'AAAA')) { + final recordsToAdd = records + .where((final r) => r.type == 'A') + .map( + (final r) => DnsRecord( + name: r.name, + type: 'AAAA', + content: null, + ), + ) + .toList(); + records.addAll(recordsToAdd); + } + + /// TODO: Error handling? final ServerDomain? domain = getIt().serverDomain; await ProvidersController.currentDnsProvider!.removeDomainRecords( @@ -173,7 +215,7 @@ class DnsRecordsCubit extends ServerConnectionDependentCubit { domain: domain!, ); await ProvidersController.currentDnsProvider!.createDomainRecords( - records: records, + records: records.where((final r) => r.content != null).toList(), domain: domain, ); From b29ee2e90ed063ef3eb63cdc0c316f9e57c0ac08 Mon Sep 17 00:00:00 2001 From: Inex Code Date: Fri, 23 Feb 2024 20:04:34 +0300 Subject: [PATCH 50/51] fix: Misleading value of "Do not verify TLS" --- lib/ui/pages/more/app_settings/developer_settings.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/ui/pages/more/app_settings/developer_settings.dart b/lib/ui/pages/more/app_settings/developer_settings.dart index cd1c6811..46149e15 100644 --- a/lib/ui/pages/more/app_settings/developer_settings.dart +++ b/lib/ui/pages/more/app_settings/developer_settings.dart @@ -40,9 +40,9 @@ class _DeveloperSettingsPageState extends State { SwitchListTile( title: Text('developer_settings.ignore_tls'.tr()), subtitle: Text('developer_settings.ignore_tls_description'.tr()), - value: TlsOptions.verifyCertificate, + value: !TlsOptions.verifyCertificate, onChanged: (final bool value) => setState( - () => TlsOptions.verifyCertificate = value, + () => TlsOptions.verifyCertificate = !value, ), ), SwitchListTile( From 60c6736487bd168be661e6d14db47a3b76e0ea97 Mon Sep 17 00:00:00 2001 From: Inex Code Date: Sat, 2 Mar 2024 19:49:36 +0300 Subject: [PATCH 51/51] fix: Empty server confirmation screen during recovery --- lib/ui/pages/setup/recovering/recovery_confirm_server.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/ui/pages/setup/recovering/recovery_confirm_server.dart b/lib/ui/pages/setup/recovering/recovery_confirm_server.dart index 8e0ff022..e9c53f94 100644 --- a/lib/ui/pages/setup/recovering/recovery_confirm_server.dart +++ b/lib/ui/pages/setup/recovering/recovery_confirm_server.dart @@ -61,8 +61,7 @@ class _RecoveryConfirmServerState extends State { _firstValidServer(servers), servers.length > 1, ), - if (servers.length > 1 && - (_isExtended || !_isServerFound(servers))) + if (_isExtended || !_isServerFound(servers)) chooseServer(context, servers), ], ),