diff --git a/assets/translations/en.json b/assets/translations/en.json index 172fce4e..b89dbc58 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", @@ -305,6 +306,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", @@ -390,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:", @@ -590,6 +596,8 @@ "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", + "job_removed": "Job removed", "run_jobs": "Run jobs", "reboot_success": "Server is rebooting", "reboot_failed": "Couldn't reboot the server. Check the app logs.", @@ -602,7 +610,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", @@ -634,4 +646,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 ba5a74b5..06cb7244 100644 --- a/lib/config/bloc_config.dart +++ b/lib/config/bloc_config.dart @@ -1,43 +1,64 @@ 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/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/backups/backups_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/provider_volumes/provider_volume_cubit.dart'; -import 'package:selfprivacy/logic/cubit/providers/providers_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/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'; 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 UsersBloc usersBloc; + late final ServicesBloc servicesBloc; + late final BackupsBloc backupsBloc; + late final DnsRecordsCubit dnsRecordsCubit; + late final RecoveryKeyBloc recoveryKeyBloc; + late final DevicesBloc devicesBloc; + late final ServerJobsBloc serverJobsBloc; + late final ConnectionStatusBloc connectionStatusBloc; + late final ServerDetailsCubit serverDetailsCubit; + late final VolumesBloc volumesBloc; + + @override + void initState() { + super.initState(); + serverInstallationCubit = ServerInstallationCubit()..load(); + supportSystemCubit = SupportSystemCubit(); + usersBloc = UsersBloc(); + servicesBloc = ServicesBloc(); + backupsBloc = BackupsBloc(); + dnsRecordsCubit = DnsRecordsCubit(); + recoveryKeyBloc = RecoveryKeyBloc(); + devicesBloc = DevicesBloc(); + serverJobsBloc = ServerJobsBloc(); + connectionStatusBloc = ConnectionStatusBloc(); + serverDetailsCubit = ServerDetailsCubit(); + volumesBloc = VolumesBloc(); + } + @override Widget build(final BuildContext context) { const isDark = false; const isAutoDark = true; - final serverInstallationCubit = ServerInstallationCubit()..load(); - final supportSystemCubit = SupportSystemCubit(); - final usersCubit = UsersCubit(serverInstallationCubit); - final servicesCubit = ServicesCubit(serverInstallationCubit); - final backupsCubit = BackupsCubit(serverInstallationCubit); - final dnsRecordsCubit = DnsRecordsCubit(serverInstallationCubit); - final recoveryKeyCubit = RecoveryKeyCubit(serverInstallationCubit); - final apiDevicesCubit = ApiDevicesCubit(serverInstallationCubit); - final apiVolumesCubit = ApiProviderVolumeCubit(serverInstallationCubit); - final apiServerVolumesCubit = - ApiServerVolumeCubit(serverInstallationCubit, apiVolumesCubit); - final serverJobsCubit = ServerJobsCubit(serverInstallationCubit); - final serverDetailsCubit = ServerDetailsCubit(serverInstallationCubit); return MultiProvider( providers: [ @@ -56,49 +77,37 @@ class BlocAndProviderConfig extends StatelessWidget { lazy: false, ), BlocProvider( - create: (final _) => ProvidersCubit(), - ), - BlocProvider( - create: (final _) => usersCubit..load(), + create: (final _) => usersBloc, lazy: false, ), BlocProvider( - create: (final _) => servicesCubit..load(), - lazy: false, + create: (final _) => servicesBloc, ), BlocProvider( - create: (final _) => backupsCubit..load(), - lazy: false, + create: (final _) => backupsBloc, ), BlocProvider( - create: (final _) => dnsRecordsCubit..load(), + create: (final _) => dnsRecordsCubit, ), BlocProvider( - create: (final _) => recoveryKeyCubit..load(), + create: (final _) => recoveryKeyBloc, ), BlocProvider( - create: (final _) => apiDevicesCubit..load(), + create: (final _) => devicesBloc, ), BlocProvider( - create: (final _) => apiVolumesCubit..load(), + create: (final _) => serverJobsBloc, ), + BlocProvider(create: (final _) => connectionStatusBloc), BlocProvider( - create: (final _) => apiServerVolumesCubit..load(), + create: (final _) => serverDetailsCubit, ), + BlocProvider(create: (final _) => volumesBloc), BlocProvider( - create: (final _) => serverJobsCubit..load(), - ), - BlocProvider( - create: (final _) => serverDetailsCubit..load(), - ), - BlocProvider( - create: (final _) => JobsCubit( - usersCubit: usersCubit, - servicesCubit: servicesCubit, - ), + create: (final _) => JobsCubit(), ), ], - child: child, + child: widget.child, ); } } diff --git a/lib/config/get_it_config.dart b/lib/config/get_it_config.dart index 6961ea94..78e40261 100644 --- a/lib/config/get_it_config.dart +++ b/lib/config/get_it_config.dart @@ -1,13 +1,13 @@ 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'; -import 'package:selfprivacy/logic/get_it/timer.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/timer.dart'; final GetIt getIt = GetIt.instance; @@ -15,8 +15,11 @@ Future getItSetup() async { getIt.registerSingleton(NavigationService()); getIt.registerSingleton(ConsoleModel()); - getIt.registerSingleton(TimerModel()); getIt.registerSingleton(ApiConfigModel()..init()); + getIt.registerSingleton( + ApiConnectionRepository()..init(), + ); + await getIt.allReady(); } 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/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..d8e21485 100644 --- a/lib/logic/api_maps/graphql_maps/schema/server_api.graphql +++ b/lib/logic/api_maps/graphql_maps/schema/server_api.graphql @@ -42,6 +42,17 @@ mutation RemoveJob($jobId: String!) { } mutation RunSystemRebuild { + system { + runSystemRebuild { + ...basicMutationReturnFields + job { + ...basicApiJobsFields + } + } + } +} + +mutation RunSystemRebuildFallback { system { runSystemRebuild { ...basicMutationReturnFields @@ -58,6 +69,17 @@ mutation RunSystemRollback { } mutation RunSystemUpgrade { + system { + runSystemUpgrade { + ...basicMutationReturnFields + job { + ...basicApiJobsFields + } + } + } +} + +mutation RunSystemUpgradeFallback { system { runSystemUpgrade { ...basicMutationReturnFields 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..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 @@ -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,652 @@ 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)), + ); + } + + final int code; + + final String message; + + final bool success; + + final String $__typename; + + final Fragment$basicApiJobsFields? job; + + 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; + final l$job = job; + _resultData['job'] = l$job?.toJson(); + return _resultData; + } + + @override + int get hashCode { + final l$code = code; + 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, + ]); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (!(other is Mutation$RunSystemRebuild$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; + } + final l$job = job; + final lOther$job = other.job; + if (l$job != lOther$job) { + return false; + } + return true; + } +} + +extension UtilityExtension$Mutation$RunSystemRebuild$system$runSystemRebuild + on Mutation$RunSystemRebuild$system$runSystemRebuild { + CopyWith$Mutation$RunSystemRebuild$system$runSystemRebuild< + Mutation$RunSystemRebuild$system$runSystemRebuild> + get copyWith => + CopyWith$Mutation$RunSystemRebuild$system$runSystemRebuild( + this, + (i) => i, + ); +} + +abstract class CopyWith$Mutation$RunSystemRebuild$system$runSystemRebuild< + TRes> { + factory CopyWith$Mutation$RunSystemRebuild$system$runSystemRebuild( + Mutation$RunSystemRebuild$system$runSystemRebuild instance, + TRes Function(Mutation$RunSystemRebuild$system$runSystemRebuild) then, + ) = _CopyWithImpl$Mutation$RunSystemRebuild$system$runSystemRebuild; + + factory CopyWith$Mutation$RunSystemRebuild$system$runSystemRebuild.stub( + TRes res) = + _CopyWithStubImpl$Mutation$RunSystemRebuild$system$runSystemRebuild; + + TRes call({ + int? code, + String? message, + bool? success, + String? $__typename, + Fragment$basicApiJobsFields? job, + }); + CopyWith$Fragment$basicApiJobsFields get job; +} + +class _CopyWithImpl$Mutation$RunSystemRebuild$system$runSystemRebuild + implements + CopyWith$Mutation$RunSystemRebuild$system$runSystemRebuild { + _CopyWithImpl$Mutation$RunSystemRebuild$system$runSystemRebuild( + this._instance, + this._then, + ); + + final Mutation$RunSystemRebuild$system$runSystemRebuild _instance; + + final TRes Function(Mutation$RunSystemRebuild$system$runSystemRebuild) _then; + + static const _undefined = {}; + + TRes call({ + Object? code = _undefined, + Object? message = _undefined, + Object? success = _undefined, + Object? $__typename = _undefined, + Object? job = _undefined, + }) => + _then(Mutation$RunSystemRebuild$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), + 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 + implements + CopyWith$Mutation$RunSystemRebuild$system$runSystemRebuild { + _CopyWithStubImpl$Mutation$RunSystemRebuild$system$runSystemRebuild( + this._res); + + TRes _res; + + call({ + int? code, + String? message, + bool? success, + String? $__typename, + Fragment$basicApiJobsFields? job, + }) => + _res; + + CopyWith$Fragment$basicApiJobsFields get job => + 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), ); } @@ -4809,7 +5672,7 @@ class Mutation$RunSystemRebuild$system$runSystemRebuild if (identical(this, other)) { return true; } - if (!(other is Mutation$RunSystemRebuild$system$runSystemRebuild) || + if (!(other is Mutation$RunSystemRebuildFallback$system$runSystemRebuild) || runtimeType != other.runtimeType) { return false; } @@ -4837,27 +5700,28 @@ class Mutation$RunSystemRebuild$system$runSystemRebuild } } -extension UtilityExtension$Mutation$RunSystemRebuild$system$runSystemRebuild - on Mutation$RunSystemRebuild$system$runSystemRebuild { - CopyWith$Mutation$RunSystemRebuild$system$runSystemRebuild< - Mutation$RunSystemRebuild$system$runSystemRebuild> +extension UtilityExtension$Mutation$RunSystemRebuildFallback$system$runSystemRebuild + on Mutation$RunSystemRebuildFallback$system$runSystemRebuild { + CopyWith$Mutation$RunSystemRebuildFallback$system$runSystemRebuild< + Mutation$RunSystemRebuildFallback$system$runSystemRebuild> get copyWith => - CopyWith$Mutation$RunSystemRebuild$system$runSystemRebuild( + CopyWith$Mutation$RunSystemRebuildFallback$system$runSystemRebuild( this, (i) => i, ); } -abstract class CopyWith$Mutation$RunSystemRebuild$system$runSystemRebuild< +abstract class CopyWith$Mutation$RunSystemRebuildFallback$system$runSystemRebuild< TRes> { - factory CopyWith$Mutation$RunSystemRebuild$system$runSystemRebuild( - Mutation$RunSystemRebuild$system$runSystemRebuild instance, - TRes Function(Mutation$RunSystemRebuild$system$runSystemRebuild) then, - ) = _CopyWithImpl$Mutation$RunSystemRebuild$system$runSystemRebuild; + 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$RunSystemRebuild$system$runSystemRebuild.stub( + factory CopyWith$Mutation$RunSystemRebuildFallback$system$runSystemRebuild.stub( TRes res) = - _CopyWithStubImpl$Mutation$RunSystemRebuild$system$runSystemRebuild; + _CopyWithStubImpl$Mutation$RunSystemRebuildFallback$system$runSystemRebuild; TRes call({ int? code, @@ -4867,17 +5731,20 @@ abstract class CopyWith$Mutation$RunSystemRebuild$system$runSystemRebuild< }); } -class _CopyWithImpl$Mutation$RunSystemRebuild$system$runSystemRebuild +class _CopyWithImpl$Mutation$RunSystemRebuildFallback$system$runSystemRebuild< + TRes> implements - CopyWith$Mutation$RunSystemRebuild$system$runSystemRebuild { - _CopyWithImpl$Mutation$RunSystemRebuild$system$runSystemRebuild( + CopyWith$Mutation$RunSystemRebuildFallback$system$runSystemRebuild< + TRes> { + _CopyWithImpl$Mutation$RunSystemRebuildFallback$system$runSystemRebuild( this._instance, this._then, ); - final Mutation$RunSystemRebuild$system$runSystemRebuild _instance; + final Mutation$RunSystemRebuildFallback$system$runSystemRebuild _instance; - final TRes Function(Mutation$RunSystemRebuild$system$runSystemRebuild) _then; + final TRes Function(Mutation$RunSystemRebuildFallback$system$runSystemRebuild) + _then; static const _undefined = {}; @@ -4887,7 +5754,7 @@ class _CopyWithImpl$Mutation$RunSystemRebuild$system$runSystemRebuild Object? success = _undefined, Object? $__typename = _undefined, }) => - _then(Mutation$RunSystemRebuild$system$runSystemRebuild( + _then(Mutation$RunSystemRebuildFallback$system$runSystemRebuild( code: code == _undefined || code == null ? _instance.code : (code as int), message: message == _undefined || message == null @@ -4902,10 +5769,12 @@ class _CopyWithImpl$Mutation$RunSystemRebuild$system$runSystemRebuild )); } -class _CopyWithStubImpl$Mutation$RunSystemRebuild$system$runSystemRebuild +class _CopyWithStubImpl$Mutation$RunSystemRebuildFallback$system$runSystemRebuild< + TRes> implements - CopyWith$Mutation$RunSystemRebuild$system$runSystemRebuild { - _CopyWithStubImpl$Mutation$RunSystemRebuild$system$runSystemRebuild( + CopyWith$Mutation$RunSystemRebuildFallback$system$runSystemRebuild< + TRes> { + _CopyWithStubImpl$Mutation$RunSystemRebuildFallback$system$runSystemRebuild( this._res); TRes _res; @@ -5681,6 +6550,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 +6597,7 @@ const documentNodeMutationRunSystemUpgrade = DocumentNode(definitions: [ ]), ), fragmentDefinitionbasicMutationReturnFields, + fragmentDefinitionbasicApiJobsFields, ]); Mutation$RunSystemUpgrade _parserFn$Mutation$RunSystemUpgrade( Map data) => @@ -5948,12 +6837,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 +6852,652 @@ 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)), + ); + } + + final int code; + + final String message; + + final bool success; + + final String $__typename; + + final Fragment$basicApiJobsFields? job; + + 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; + final l$job = job; + _resultData['job'] = l$job?.toJson(); + return _resultData; + } + + @override + int get hashCode { + final l$code = code; + 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, + ]); + } + + @override + bool operator ==(Object other) { + if (identical(this, other)) { + return true; + } + if (!(other is Mutation$RunSystemUpgrade$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; + } + final l$job = job; + final lOther$job = other.job; + if (l$job != lOther$job) { + return false; + } + return true; + } +} + +extension UtilityExtension$Mutation$RunSystemUpgrade$system$runSystemUpgrade + on Mutation$RunSystemUpgrade$system$runSystemUpgrade { + CopyWith$Mutation$RunSystemUpgrade$system$runSystemUpgrade< + Mutation$RunSystemUpgrade$system$runSystemUpgrade> + get copyWith => + CopyWith$Mutation$RunSystemUpgrade$system$runSystemUpgrade( + this, + (i) => i, + ); +} + +abstract class CopyWith$Mutation$RunSystemUpgrade$system$runSystemUpgrade< + TRes> { + factory CopyWith$Mutation$RunSystemUpgrade$system$runSystemUpgrade( + Mutation$RunSystemUpgrade$system$runSystemUpgrade instance, + TRes Function(Mutation$RunSystemUpgrade$system$runSystemUpgrade) then, + ) = _CopyWithImpl$Mutation$RunSystemUpgrade$system$runSystemUpgrade; + + factory CopyWith$Mutation$RunSystemUpgrade$system$runSystemUpgrade.stub( + TRes res) = + _CopyWithStubImpl$Mutation$RunSystemUpgrade$system$runSystemUpgrade; + + TRes call({ + int? code, + String? message, + bool? success, + String? $__typename, + Fragment$basicApiJobsFields? job, + }); + CopyWith$Fragment$basicApiJobsFields get job; +} + +class _CopyWithImpl$Mutation$RunSystemUpgrade$system$runSystemUpgrade + implements + CopyWith$Mutation$RunSystemUpgrade$system$runSystemUpgrade { + _CopyWithImpl$Mutation$RunSystemUpgrade$system$runSystemUpgrade( + this._instance, + this._then, + ); + + final Mutation$RunSystemUpgrade$system$runSystemUpgrade _instance; + + final TRes Function(Mutation$RunSystemUpgrade$system$runSystemUpgrade) _then; + + static const _undefined = {}; + + TRes call({ + Object? code = _undefined, + Object? message = _undefined, + Object? success = _undefined, + Object? $__typename = _undefined, + Object? job = _undefined, + }) => + _then(Mutation$RunSystemUpgrade$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), + 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 + implements + CopyWith$Mutation$RunSystemUpgrade$system$runSystemUpgrade { + _CopyWithStubImpl$Mutation$RunSystemUpgrade$system$runSystemUpgrade( + this._res); + + TRes _res; + + call({ + int? code, + String? message, + bool? success, + String? $__typename, + Fragment$basicApiJobsFields? job, + }) => + _res; + + CopyWith$Fragment$basicApiJobsFields get job => + 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), ); } @@ -6010,7 +7541,7 @@ class Mutation$RunSystemUpgrade$system$runSystemUpgrade if (identical(this, other)) { return true; } - if (!(other is Mutation$RunSystemUpgrade$system$runSystemUpgrade) || + if (!(other is Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade) || runtimeType != other.runtimeType) { return false; } @@ -6038,27 +7569,28 @@ class Mutation$RunSystemUpgrade$system$runSystemUpgrade } } -extension UtilityExtension$Mutation$RunSystemUpgrade$system$runSystemUpgrade - on Mutation$RunSystemUpgrade$system$runSystemUpgrade { - CopyWith$Mutation$RunSystemUpgrade$system$runSystemUpgrade< - Mutation$RunSystemUpgrade$system$runSystemUpgrade> +extension UtilityExtension$Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade + on Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade { + CopyWith$Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade< + Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade> get copyWith => - CopyWith$Mutation$RunSystemUpgrade$system$runSystemUpgrade( + CopyWith$Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade( this, (i) => i, ); } -abstract class CopyWith$Mutation$RunSystemUpgrade$system$runSystemUpgrade< +abstract class CopyWith$Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade< TRes> { - factory CopyWith$Mutation$RunSystemUpgrade$system$runSystemUpgrade( - Mutation$RunSystemUpgrade$system$runSystemUpgrade instance, - TRes Function(Mutation$RunSystemUpgrade$system$runSystemUpgrade) then, - ) = _CopyWithImpl$Mutation$RunSystemUpgrade$system$runSystemUpgrade; + 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$RunSystemUpgrade$system$runSystemUpgrade.stub( + factory CopyWith$Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade.stub( TRes res) = - _CopyWithStubImpl$Mutation$RunSystemUpgrade$system$runSystemUpgrade; + _CopyWithStubImpl$Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade; TRes call({ int? code, @@ -6068,17 +7600,20 @@ abstract class CopyWith$Mutation$RunSystemUpgrade$system$runSystemUpgrade< }); } -class _CopyWithImpl$Mutation$RunSystemUpgrade$system$runSystemUpgrade +class _CopyWithImpl$Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade< + TRes> implements - CopyWith$Mutation$RunSystemUpgrade$system$runSystemUpgrade { - _CopyWithImpl$Mutation$RunSystemUpgrade$system$runSystemUpgrade( + CopyWith$Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade< + TRes> { + _CopyWithImpl$Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade( this._instance, this._then, ); - final Mutation$RunSystemUpgrade$system$runSystemUpgrade _instance; + final Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade _instance; - final TRes Function(Mutation$RunSystemUpgrade$system$runSystemUpgrade) _then; + final TRes Function(Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade) + _then; static const _undefined = {}; @@ -6088,7 +7623,7 @@ class _CopyWithImpl$Mutation$RunSystemUpgrade$system$runSystemUpgrade Object? success = _undefined, Object? $__typename = _undefined, }) => - _then(Mutation$RunSystemUpgrade$system$runSystemUpgrade( + _then(Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade( code: code == _undefined || code == null ? _instance.code : (code as int), message: message == _undefined || message == null @@ -6103,10 +7638,12 @@ class _CopyWithImpl$Mutation$RunSystemUpgrade$system$runSystemUpgrade )); } -class _CopyWithStubImpl$Mutation$RunSystemUpgrade$system$runSystemUpgrade +class _CopyWithStubImpl$Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade< + TRes> implements - CopyWith$Mutation$RunSystemUpgrade$system$runSystemUpgrade { - _CopyWithStubImpl$Mutation$RunSystemUpgrade$system$runSystemUpgrade( + CopyWith$Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade< + TRes> { + _CopyWithStubImpl$Mutation$RunSystemUpgradeFallback$system$runSystemUpgrade( this._res); TRes _res; 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/api_maps/graphql_maps/server_api/server_api.dart b/lib/logic/api_maps/graphql_maps/server_api/server_api.dart index b511b6a9..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,24 +132,55 @@ 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, + ); } } - Future setAutoUpgradeSettings( + Future> setAutoUpgradeSettings( final AutoUpgradeSettings settings, ) async { try { @@ -164,13 +195,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 +235,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/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/backups/backups_bloc.dart b/lib/logic/bloc/backups/backups_bloc.dart new file mode 100644 index 00000000..5c0e9e64 --- /dev/null +++ b/lib/logic/bloc/backups/backups_bloc.dart @@ -0,0 +1,408 @@ +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/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()) { + 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(); + + _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; + } + } + + 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) { + // 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( + 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()); + } + emit(currentState); + } + } + + @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..7cdfe023 --- /dev/null +++ b/lib/logic/bloc/backups/backups_state.dart @@ -0,0 +1,170 @@ +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; + } catch (_) { + 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/bloc/connection_status/connection_status_bloc.dart b/lib/logic/bloc/connection_status/connection_status_bloc.dart new file mode 100644 index 00000000..868f05d0 --- /dev/null +++ b/lib/logic/bloc/connection_status/connection_status_bloc.dart @@ -0,0 +1,39 @@ +import 'dart:async'; + +import 'package:equatable/equatable.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:selfprivacy/config/get_it_config.dart'; + +part 'connection_status_event.dart'; +part 'connection_status_state.dart'; + +class ConnectionStatusBloc + extends Bloc { + ConnectionStatusBloc() + : super( + const ConnectionStatusState( + connectionStatus: ConnectionStatus.nonexistent, + ), + ) { + on((final event, final emit) { + emit(ConnectionStatusState(connectionStatus: event.connectionStatus)); + }); + final apiConnectionRepository = getIt(); + _apiConnectionStatusSubscription = + apiConnectionRepository.connectionStatusStream.listen( + (final ConnectionStatus connectionStatus) { + add( + ConnectionStatusChanged(connectionStatus), + ); + }, + ); + } + + StreamSubscription? _apiConnectionStatusSubscription; + + @override + Future close() { + _apiConnectionStatusSubscription?.cancel(); + return super.close(); + } +} diff --git a/lib/logic/bloc/connection_status/connection_status_event.dart b/lib/logic/bloc/connection_status/connection_status_event.dart new file mode 100644 index 00000000..0fc6e72f --- /dev/null +++ b/lib/logic/bloc/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/bloc/connection_status/connection_status_state.dart b/lib/logic/bloc/connection_status/connection_status_state.dart new file mode 100644 index 00000000..258765c1 --- /dev/null +++ b/lib/logic/bloc/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/bloc/devices/devices_bloc.dart b/lib/logic/bloc/devices/devices_bloc.dart new file mode 100644 index 00000000..fdba66e9 --- /dev/null +++ b/lib/logic/bloc/devices/devices_bloc.dart @@ -0,0 +1,110 @@ +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) { + add( + DevicesListChanged(apiData.devices.data), + ); + }, + ); + } + + StreamSubscription? _apiDataSubscription; + + Future _mapDevicesListChangedToState( + final DevicesListChanged event, + final Emitter emit, + ) async { + if (state is DevicesDeleting) { + return; + } + 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); + } + + @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..b31bdf19 --- /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 = Object.hashAll(devices); + + 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 new file mode 100644 index 00000000..eedcdaff --- /dev/null +++ b/lib/logic/bloc/recovery_key/recovery_key_bloc.dart @@ -0,0 +1,88 @@ +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( + _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 (event.recoveryKeyStatus == null) { + emit(RecoveryKeyError()); + return; + } + emit(RecoveryKeyLoaded(keyStatus: event.recoveryKeyStatus)); + } + + Future generateRecoveryKey({ + final DateTime? expirationDate, + final int? numberOfUses, + }) async { + final GenericResult response = + await getIt() + .api + .generateRecoveryToken(expirationDate, numberOfUses); + if (response.success) { + getIt().apiData.recoveryKeyStatus.invalidate(); + unawaited(getIt().reload(null)); + return response.data; + } else { + throw GenerationError(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(); + } +} + +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 new file mode 100644 index 00000000..bd54ff06 --- /dev/null +++ b/lib/logic/bloc/recovery_key/recovery_key_event.dart @@ -0,0 +1,21 @@ +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 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..5a5730b2 --- /dev/null +++ b/lib/logic/bloc/recovery_key/recovery_key_state.dart @@ -0,0 +1,56 @@ +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]; +} diff --git a/lib/logic/bloc/server_jobs/server_jobs_bloc.dart b/lib/logic/bloc/server_jobs/server_jobs_bloc.dart new file mode 100644 index 00000000..43b415e6 --- /dev/null +++ b/lib/logic/bloc/server_jobs/server_jobs_bloc.dart @@ -0,0 +1,81 @@ +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/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(), + ) { + on( + _mapServerJobsListChangedToState, + transformer: sequential(), + ); + on( + _mapRemoveServerJobToState, + transformer: sequential(), + ); + on( + _mapRemoveAllFinishedJobsToState, + transformer: droppable(), + ); + + final apiConnectionRepository = getIt(); + _apiDataSubscription = apiConnectionRepository.dataStream.listen( + (final ApiData apiData) { + add( + ServerJobsListChanged([...apiData.serverJobs.data ?? []]), + ); + }, + ); + } + + 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 { + await getIt().removeAllFinishedServerJobs(); + } + + @override + void onChange(final Change change) { + super.onChange(change); + } + + @override + Future close() { + _apiDataSubscription?.cancel(); + return super.close(); + } +} diff --git a/lib/logic/bloc/server_jobs/server_jobs_event.dart b/lib/logic/bloc/server_jobs/server_jobs_event.dart new file mode 100644 index 00000000..368be822 --- /dev/null +++ b/lib/logic/bloc/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/bloc/server_jobs/server_jobs_state.dart b/lib/logic/bloc/server_jobs/server_jobs_state.dart new file mode 100644 index 00000000..09f7e9ff --- /dev/null +++ b/lib/logic/bloc/server_jobs/server_jobs_state.dart @@ -0,0 +1,55 @@ +part of 'server_jobs_bloc.dart'; + +sealed class ServerJobsState extends Equatable { + ServerJobsState({ + final int? hashCode, + }) : _hashCode = hashCode ?? Object.hashAll([]); + + final int? _hashCode; + + final apiConnectionRepository = getIt(); + + List get _serverJobList => + apiConnectionRepository.apiData.serverJobs.data ?? []; + + List get serverJobList { + try { + final List list = _serverJobList; + list.sort((final a, final b) => b.createdAt.compareTo(a.createdAt)); + return list; + } on UnsupportedError { + return _serverJobList; + } + } + + List get backupJobList => serverJobList + .where( + // The backup jobs has the format of 'service..backup' + (final job) => + job.typeId.contains('backup') || job.typeId.contains('restore'), + ) + .toList(); + + bool get hasRemovableJobs => serverJobList.any( + (final job) => + job.status == JobStatusEnum.finished || + job.status == JobStatusEnum.error, + ); + + @override + 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/bloc/services/services_bloc.dart b/lib/logic/bloc/services/services_bloc.dart new file mode 100644 index 00000000..7f84eca7 --- /dev/null +++ b/lib/logic/bloc/services/services_bloc.dart @@ -0,0 +1,149 @@ +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/service.dart'; + +part 'services_event.dart'; +part 'services_state.dart'; + +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(); + + _apiDataSubscription = connectionRepository.dataStream.listen( + (final ApiData apiData) { + add( + ServicesListUpdate([...apiData.services.data ?? []]), + ); + }, + ); + + if (connectionRepository.connectionStatus == ConnectionStatus.connected) { + add( + ServicesListUpdate( + [...connectionRepository.apiData.services.data ?? []], + ), + ); + } + } + + 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..8e45a395 --- /dev/null +++ b/lib/logic/bloc/services/services_event.dart @@ -0,0 +1,40 @@ +part of 'services_bloc.dart'; + +sealed 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..9c1e3f32 --- /dev/null +++ b/lib/logic/bloc/services/services_state.dart @@ -0,0 +1,115 @@ +part of 'services_bloc.dart'; + +sealed 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/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/bloc/volumes/volumes_bloc.dart b/lib/logic/bloc/volumes/volumes_bloc.dart new file mode 100644 index 00000000..55fb15e7 --- /dev/null +++ b/lib/logic/bloc/volumes/volumes_bloc.dart @@ -0,0 +1,246 @@ +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_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'; +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(), + ); + on( + _resizeVolume, + 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; + if (state is VolumesLoading) { + emit( + VolumesLoaded( + diskStatus: DiskStatus.fromVolumes( + serverVolumes, + providerVolumes, + ), + providerVolumes: providerVolumes, + serverVolumesHashCode: Object.hashAll(serverVolumes), + ), + ); + return; + } + emit( + state.copyWith( + diskStatus: DiskStatus.fromVolumes( + serverVolumes, + providerVolumes, + ), + providerVolumes: providerVolumes, + serverVolumesHashCode: Object.hashAll(serverVolumes), + ), + ); + } + + 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 new file mode 100644 index 00000000..2531b08f --- /dev/null +++ b/lib/logic/bloc/volumes/volumes_event.dart @@ -0,0 +1,43 @@ +part of 'volumes_bloc.dart'; + +sealed 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]; +} + +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 new file mode 100644 index 00000000..04c745e4 --- /dev/null +++ b/lib/logic/bloc/volumes/volumes_state.dart @@ -0,0 +1,122 @@ +part of 'volumes_bloc.dart'; + +sealed 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!, + ); +} + +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/app_config_dependent/authentication_dependend_cubit.dart b/lib/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart deleted file mode 100644 index 97ff4353..00000000 --- a/lib/logic/cubit/app_config_dependent/authentication_dependend_cubit.dart +++ /dev/null @@ -1,41 +0,0 @@ -import 'dart:async'; - -import 'package:equatable/equatable.dart'; -import 'package:flutter_bloc/flutter_bloc.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 deleted file mode 100644 index a0893a05..00000000 --- a/lib/logic/cubit/backups/backups_cubit.dart +++ /dev/null @@ -1,279 +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/api_maps/rest_maps/backblaze.dart'; -import 'package:selfprivacy/logic/cubit/app_config_dependent/authentication_dependend_cubit.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/service.dart'; - -part 'backups_state.dart'; - -class BackupsCubit extends ServerInstallationDependendCubit { - BackupsCubit(final ServerInstallationCubit serverInstallationCubit) - : super( - serverInstallationCubit, - const BackupsState(preventActions: true), - ); - - final ServerApi api = ServerApi(); - final BackblazeApi backblaze = BackblazeApi(); - - @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, - ), - ); - } - } - - 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 = serverInstallationCubit - .state.serverDomain!.domainName - .replaceAll(RegExp(r'[^a-zA-Z0-9]'), '-'); - final int serverId = serverInstallationCubit.state.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/client_jobs/client_jobs_cubit.dart b/lib/logic/cubit/client_jobs/client_jobs_cubit.dart index 59a33673..16cadfd0 100644 --- a/lib/logic/cubit/client_jobs/client_jobs_cubit.dart +++ b/lib/logic/cubit/client_jobs/client_jobs_cubit.dart @@ -1,36 +1,52 @@ 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/cubit/services/services_cubit.dart'; -import 'package:selfprivacy/logic/cubit/users/users_cubit.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.usersCubit, - required this.servicesCubit, - }) : 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!); + } + }, + ); + } - final ServerApi api = ServerApi(); - final UsersCubit usersCubit; - final ServicesCubit servicesCubit; + StreamSubscription? _apiDataSubscription; - 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) { @@ -38,61 +54,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 getIt().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(); + 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(); - await servicesCubit.load(); - - 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..c7ab2c0c 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,135 @@ 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(); + 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)) { + 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) { + if (job is ReplaceableJob) { + final List newPostponedJobs = postponedJobs + .where((final element) => element.runtimeType != job.runtimeType) + .toList(); + 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)) { + 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(); + 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)) { + final List newPostponedJobs = [...postponedJobs, job]; + getIt().showSnackBar('jobs.job_added'.tr()); + return JobsStateWithJobs(newPostponedJobs); + } + return this; + } } diff --git a/lib/logic/cubit/devices/devices_cubit.dart b/lib/logic/cubit/devices/devices_cubit.dart deleted file mode 100644 index 7713254b..00000000 --- a/lib/logic/cubit/devices/devices_cubit.dart +++ /dev/null @@ -1,77 +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/app_config_dependent/authentication_dependend_cubit.dart'; -import 'package:selfprivacy/logic/models/json/api_token.dart'; - -part 'devices_state.dart'; - -class ApiDevicesCubit - extends ServerInstallationDependendCubit { - ApiDevicesCubit(final ServerInstallationCubit serverInstallationCubit) - : super(serverInstallationCubit, 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/dns_records/dns_records_cubit.dart b/lib/logic/cubit/dns_records/dns_records_cubit.dart index 2a9e44a0..cdb4e9b1 100644 --- a/lib/logic/cubit/dns_records/dns_records_cubit.dart +++ b/lib/logic/cubit/dns_records/dns_records_cubit.dart @@ -1,9 +1,8 @@ import 'package:cubit_form/cubit_form.dart'; -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/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/models/hive/server_domain.dart'; import 'package:selfprivacy/logic/models/json/dns_records.dart'; import 'package:selfprivacy/logic/providers/providers_controller.dart'; @@ -11,11 +10,9 @@ import 'package:selfprivacy/utils/network_utils.dart'; part 'dns_records_state.dart'; -class DnsRecordsCubit - extends ServerInstallationDependendCubit { - DnsRecordsCubit(final ServerInstallationCubit serverInstallationCubit) +class DnsRecordsCubit extends ServerConnectionDependentCubit { + DnsRecordsCubit() : super( - serverInstallationCubit, const DnsRecordsState(dnsState: DnsRecordsStatus.refreshing), ); @@ -30,39 +27,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, - ipAddress, - ); - - if (!foundRecords.success || foundRecords.data.isEmpty) { - emit(const DnsRecordsState(dnsState: DnsRecordsStatus.error)); - 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 @@ -74,19 +68,7 @@ class DnsRecordsCubit final ServerDomain domain, final String dkimPublicKey, final List pendingDnsRecords, - final String ip4, ) async { - final matchMap = await validateDnsMatch(domain.domainName, ['api'], ip4); - if (matchMap.values.any((final status) => status != DnsRecordStatus.ok)) { - getIt().showSnackBar( - 'domain.domain_validation_failure'.tr(), - ); - return GenericResult( - success: false, - data: [], - ); - } - final result = await ProvidersController.currentDnsProvider! .getDnsRecords(domain: domain); if (result.data.isEmpty || !result.success) { @@ -185,7 +167,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/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/provider_volumes/provider_volume_cubit.dart b/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart deleted file mode 100644 index fdf18ed2..00000000 --- a/lib/logic/cubit/provider_volumes/provider_volume_cubit.dart +++ /dev/null @@ -1,150 +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/app_config_dependent/authentication_dependend_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 ApiProviderVolumeCubit - extends ServerInstallationDependendCubit { - ApiProviderVolumeCubit(final ServerInstallationCubit serverInstallationCubit) - : super(serverInstallationCubit, const ApiProviderVolumeState.initial()); - final ServerApi serverApi = ServerApi(); - - @override - Future load() async { - if (serverInstallationCubit.state is ServerInstallationFinished) { - 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 ApiProviderVolumeState([], LoadingStatus.refreshing, false)); - unawaited(_refetch()); - } - - Future _refetch() async { - if (ProvidersController.currentServerProvider == null) { - return emit(const ApiProviderVolumeState([], LoadingStatus.error, false)); - } - - final volumesResult = - await ProvidersController.currentServerProvider!.getVolumes(); - - if (!volumesResult.success || volumesResult.data.isEmpty) { - return emit(const ApiProviderVolumeState([], LoadingStatus.error, false)); - } - - emit( - ApiProviderVolumeState( - 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 ServerVolume? 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()); - } -} 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 3858ef14..00000000 --- a/lib/logic/cubit/provider_volumes/provider_volume_state.dart +++ /dev/null @@ -1,27 +0,0 @@ -part of 'provider_volume_cubit.dart'; - -class ApiProviderVolumeState extends ServerInstallationDependendState { - const ApiProviderVolumeState(this._volumes, this.status, this.isResizing); - - const ApiProviderVolumeState.initial() - : this(const [], LoadingStatus.uninitialized, false); - final List _volumes; - final LoadingStatus status; - final bool isResizing; - - List get volumes => _volumes; - - ApiProviderVolumeState copyWith({ - final List? volumes, - final LoadingStatus? status, - final bool? isResizing, - }) => - ApiProviderVolumeState( - volumes ?? _volumes, - status ?? this.status, - isResizing ?? this.isResizing, - ); - - @override - List get props => [_volumes, status, isResizing]; -} diff --git a/lib/logic/cubit/providers/providers_cubit.dart b/lib/logic/cubit/providers/providers_cubit.dart deleted file mode 100644 index 66a8c261..00000000 --- a/lib/logic/cubit/providers/providers_cubit.dart +++ /dev/null @@ -1,19 +0,0 @@ -import 'package:equatable/equatable.dart'; -import 'package:flutter_bloc/flutter_bloc.dart'; -import 'package:selfprivacy/logic/models/provider.dart'; -import 'package:selfprivacy/logic/models/state_types.dart'; - -export 'package:provider/provider.dart'; -export 'package:selfprivacy/logic/models/state_types.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/logic/cubit/recovery_key/recovery_key_cubit.dart b/lib/logic/cubit/recovery_key/recovery_key_cubit.dart deleted file mode 100644 index c5f68d57..00000000 --- a/lib/logic/cubit/recovery_key/recovery_key_cubit.dart +++ /dev/null @@ -1,81 +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/app_config_dependent/authentication_dependend_cubit.dart'; -import 'package:selfprivacy/logic/models/json/recovery_token_status.dart'; - -part 'recovery_key_state.dart'; - -class RecoveryKeyCubit - extends ServerInstallationDependendCubit { - RecoveryKeyCubit(final ServerInstallationCubit serverInstallationCubit) - : super(serverInstallationCubit, 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/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..073c9bdd --- /dev/null +++ b/lib/logic/cubit/server_connection_dependent/server_connection_dependent_cubit.dart @@ -0,0 +1,51 @@ +import 'dart:async'; + +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'; + +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..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,35 +1,75 @@ +import 'dart:async'; + 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_detailed_info/server_detailed_info_repository.dart'; +import 'package:selfprivacy/logic/cubit/server_connection_dependent/server_connection_dependent_cubit.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 ServerInstallationDependendCubit { - ServerDetailsCubit(final ServerInstallationCubit serverInstallationCubit) - : super(serverInstallationCubit, ServerDetailsInitial()); + extends ServerConnectionDependentCubit { + 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'); @@ -38,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 0d2d80e3..00000000 --- a/lib/logic/cubit/server_detailed_info/server_detailed_info_repository.dart +++ /dev/null @@ -1,68 +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; - } - - 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 { - 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/cubit/server_installation/server_installation_cubit.dart b/lib/logic/cubit/server_installation/server_installation_cubit.dart index d3963290..5476a755 100644 --- a/lib/logic/cubit/server_installation/server_installation_cubit.dart +++ b/lib/logic/cubit/server_installation/server_installation_cubit.dart @@ -233,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); } @@ -484,6 +484,7 @@ class ServerInstallationCubit extends Cubit { if (dkimCreated) { await repository.saveHasFinalChecked(true); emit(dataState.finish()); + getIt().init(); } else { runDelayed( finishCheckIfServerIsOkay, @@ -724,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, @@ -802,6 +803,7 @@ class ServerInstallationCubit extends Cubit { serverTypeIdentificator: serverType.data!.identifier, ); emit(updatedState.finish()); + getIt().init(); } @override diff --git a/lib/logic/cubit/server_installation/server_installation_repository.dart b/lib/logic/cubit/server_installation/server_installation_repository.dart index 7567da7e..90035fea 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, @@ -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/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_state.dart b/lib/logic/cubit/server_jobs/server_jobs_state.dart deleted file mode 100644 index 9a18dd51..00000000 --- a/lib/logic/cubit/server_jobs/server_jobs_state.dart +++ /dev/null @@ -1,49 +0,0 @@ -part of 'server_jobs_cubit.dart'; - -class ServerJobsState extends ServerInstallationDependendState { - ServerJobsState({ - final serverJobList = const [], - this.migrationJobUid, - }) { - _serverJobList = serverJobList; - } - - late final List _serverJobList; - final String? migrationJobUid; - - List get serverJobList { - try { - final List list = _serverJobList; - list.sort((final a, final b) => b.createdAt.compareTo(a.createdAt)); - return list; - } on UnsupportedError { - return _serverJobList; - } - } - - List get backupJobList => serverJobList - .where( - // The backup jobs has the format of 'service..backup' - (final job) => - job.typeId.contains('backup') || job.typeId.contains('restore'), - ) - .toList(); - - bool get hasRemovableJobs => serverJobList.any( - (final job) => - job.status == JobStatusEnum.finished || - job.status == JobStatusEnum.error, - ); - - @override - List get props => [migrationJobUid, _serverJobList]; - - ServerJobsState copyWith({ - final List? serverJobList, - final String? migrationJobUid, - }) => - ServerJobsState( - serverJobList: serverJobList ?? _serverJobList, - migrationJobUid: migrationJobUid ?? this.migrationJobUid, - ); -} 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 bf30c8c0..00000000 --- a/lib/logic/cubit/server_volumes/server_volume_cubit.dart +++ /dev/null @@ -1,78 +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/app_config_dependent/authentication_dependend_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'; - -part 'server_volume_state.dart'; - -class ApiServerVolumeCubit - extends ServerInstallationDependendCubit { - ApiServerVolumeCubit( - final ServerInstallationCubit serverInstallationCubit, - this.providerVolumeCubit, - ) : super(serverInstallationCubit, ApiServerVolumeState.initial()) { - _providerVolumeSubscription = - providerVolumeCubit.stream.listen(checkProviderVolumes); - } - - final ServerApi serverApi = ServerApi(); - - @override - Future load() async { - if (serverInstallationCubit.state is ServerInstallationFinished) { - unawaited(reload()); - } - } - - late StreamSubscription _providerVolumeSubscription; - final ApiProviderVolumeCubit providerVolumeCubit; - - void checkProviderVolumes(final ApiProviderVolumeState state) { - emit( - ApiServerVolumeState( - this.state._volumes, - this.state.status, - this.state.usesBinds, - DiskStatus.fromVolumes(this.state._volumes, state.volumes), - ), - ); - return; - } - - Future reload() async { - final volumes = await serverApi.getServerDiskVolumes(); - final usesBinds = await serverApi.isUsingBinds(); - var status = LoadingStatus.error; - - if (volumes.isNotEmpty) { - status = LoadingStatus.success; - } - - emit( - ApiServerVolumeState( - volumes, - status, - usesBinds, - 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 d68daa58..00000000 --- a/lib/logic/cubit/server_volumes/server_volume_state.dart +++ /dev/null @@ -1,42 +0,0 @@ -part of 'server_volume_cubit.dart'; - -class ApiServerVolumeState extends ServerInstallationDependendState { - const ApiServerVolumeState( - this._volumes, - this.status, - this.usesBinds, - this._diskStatus, - ); - - ApiServerVolumeState.initial() - : this(const [], LoadingStatus.uninitialized, null, DiskStatus()); - - final List _volumes; - final DiskStatus _diskStatus; - final bool? usesBinds; - 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 bool? usesBinds, - final DiskStatus? diskStatus, - }) => - ApiServerVolumeState( - volumes ?? _volumes, - status ?? this.status, - usesBinds ?? this.usesBinds, - diskStatus ?? _diskStatus, - ); - - @override - List get props => [_volumes, status, usesBinds]; -} diff --git a/lib/logic/cubit/services/services_cubit.dart b/lib/logic/cubit/services/services_cubit.dart deleted file mode 100644 index 60476c2d..00000000 --- a/lib/logic/cubit/services/services_cubit.dart +++ /dev/null @@ -1,83 +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/service.dart'; - -part 'services_state.dart'; - -class ServicesCubit extends ServerInstallationDependendCubit { - ServicesCubit(final ServerInstallationCubit serverInstallationCubit) - : super(serverInstallationCubit, 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)); - } - } - - 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/cubit/users/users_cubit.dart b/lib/logic/cubit/users/users_cubit.dart deleted file mode 100644 index cb717441..00000000 --- a/lib/logic/cubit/users/users_cubit.dart +++ /dev/null @@ -1,186 +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/app_config_dependent/authentication_dependend_cubit.dart'; -import 'package:selfprivacy/logic/models/hive/user.dart'; - -export 'package:provider/provider.dart'; - -part 'users_state.dart'; - -class UsersCubit extends ServerInstallationDependendCubit { - UsersCubit(final ServerInstallationCubit serverInstallationCubit) - : super( - serverInstallationCubit, - const UsersState( - [], - false, - ), - ); - Box box = Hive.box(BNames.usersBox); - Box serverInstallationBox = Hive.box(BNames.serverInstallationBox); - - final ServerApi api = ServerApi(); - - @override - Future load() async { - if (serverInstallationCubit.state is! ServerInstallationFinished) { - return; - } - final List loadedUsers = box.values.toList(); - if (loadedUsers.isNotEmpty) { - emit( - UsersState( - loadedUsers, - false, - ), - ); - } - - unawaited(refresh()); - } - - Future refresh() async { - if (serverInstallationCubit.state is! ServerInstallationFinished) { - 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_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 new file mode 100644 index 00000000..78968a45 --- /dev/null +++ b/lib/logic/get_it/api_connection_repository.dart @@ -0,0 +1,435 @@ +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'; +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'; +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'; +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. +class ApiConnectionRepository { + Box box = Hive.box(BNames.serverInstallationBox); + final ServerApi api = ServerApi(); + + final ApiData _apiData = ApiData(ServerApi()); + + 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); + } + + 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), + ); + } + + Future<(bool, String)> createUser(final User user) async { + final List? loadedUsers = _apiData.users.data; + if (loadedUsers == null) { + 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 (false, 'users.user_already_exists'.tr()); + } + final String? password = user.password; + if (password == null) { + 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) { + 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<(bool, String)> deleteUser(final User user) async { + final List? loadedUsers = _apiData.users.data; + if (loadedUsers == null) { + return (false, 'basis.network_error'.tr()); + } + // If user is primary or root, don't delete + if (user.type != UserType.normal) { + return (false, 'users.could_not_delete_user'.tr()); + } + 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) { + return (false, result.message ?? 'jobs.generic_error'.tr()); + } + + return (true, result.message ?? 'basis.done'.tr()); + } + + Future<(bool, String)> changeUserPassword( + final User user, + final String newPassword, + ) async { + if (user.type == UserType.root) { + return (false, 'users.could_not_change_password'.tr()); + } + final GenericResult result = await api.updateUser( + user.login, + newPassword, + ); + if (result.data == null) { + 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<(bool, String)> addSshKey( + final User user, + final String publicKey, + ) async { + final List? loadedUsers = _apiData.users.data; + if (loadedUsers == null) { + return (false, 'basis.network_error'.tr()); + } + 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 { + return (false, result.message ?? 'users.could_not_add_ssh_key'.tr()); + } + + return (true, result.message ?? 'basis.done'.tr()); + } + + Future<(bool, String)> deleteSshKey( + final User user, + final String publicKey, + ) async { + final List? loadedUsers = _apiData.users.data; + if (loadedUsers == null) { + return (false, 'basis.network_error'.tr()); + } + 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 { + return (false, result.message ?? 'jobs.generic_error'.tr()); + } + 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(); + _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 { + _apiData.apiVersion.data = apiVersion; + _dataStream.add(_apiData); + } + + await _refetchEverything(Version.parse(apiVersion)); + + connectionStatus = ConnectionStatus.connected; + _connectionStatusStream.add(connectionStatus); + + // Use timer to periodically check for new jobs + _timer = Timer.periodic( + const Duration(seconds: 10), + reload, + ); + } + + 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)); + } + + Future 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; + } + final Version version = Version.parse(apiVersion); + await _refetchEverything(version); + } + + void emitData() { + _dataStream.add(_apiData); + } +} + +class ApiData { + 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', + ttl: 120, + ), + backups = ApiDataElement>( + fetchData: () async => api.getBackups(), + requiredApiVersion: '>=2.4.2', + ttl: 120, + ), + services = ApiDataElement>( + fetchData: () async => api.getAllServices(), + requiredApiVersion: '>=2.4.3', + ), + volumes = ApiDataElement>( + fetchData: () async => api.getServerDiskVolumes(), + ), + recoveryKeyStatus = ApiDataElement( + fetchData: () async => (await api.getRecoveryTokenStatus()).data, + ttl: 300, + ), + devices = ApiDataElement>( + fetchData: () async => (await api.getApiTokens()).data, + ), + users = ApiDataElement>( + fetchData: () async => api.getAllUsers(), + ), + settings = ApiDataElement( + fetchData: () async => api.getSystemSettings(), + ttl: 600, + ); + + ApiDataElement> serverJobs; + ApiDataElement apiVersion; + ApiDataElement backupConfig; + ApiDataElement> backups; + ApiDataElement> services; + ApiDataElement> volumes; + ApiDataElement recoveryKeyStatus; + ApiDataElement> devices; + ApiDataElement> users; + ApiDataElement settings; +} + +enum ConnectionStatus { + nonexistent, + connected, + reconnecting, + offline, + unauthorized, +} + +class ApiDataElement { + ApiDataElement({ + required this.fetchData, + final T? data, + this.requiredApiVersion = '>=2.3.0', + this.ttl = 60, + }) : _data = data, + _lastUpdated = DateTime.now(); + + T? _data; + final String requiredApiVersion; + + final Future Function() fetchData; + + Future refetchData( + final Version version, + final Function callback, + ) async { + if (VersionConstraint.parse(requiredApiVersion).allows(version)) { + if (isExpired || _data == null) { + final newData = await fetchData(); + 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; + + 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/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(); - } -} 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/disk_status.dart b/lib/logic/models/disk_status.dart index 2a37ad77..6708cf12 100644 --- a/lib/logic/models/disk_status.dart +++ b/lib/logic/models/disk_status.dart @@ -9,13 +9,12 @@ class DiskVolume { this.sizeUsed = const DiskSize(byte: 0), this.root = false, this.isResizable = false, - this.serverDiskVolume, this.providerVolume, }); DiskVolume.fromServerDiscVolume( final ServerDiskVolume volume, - final ServerVolume? providerVolume, + final ServerProviderVolume? providerVolume, ) : this( name: volume.name, sizeTotal: DiskSize( @@ -27,7 +26,6 @@ class DiskVolume { ), root: volume.root, isResizable: providerVolume != null, - serverDiskVolume: volume, providerVolume: providerVolume, ); @@ -51,8 +49,7 @@ class DiskVolume { String name; bool root; bool isResizable; - ServerDiskVolume? serverDiskVolume; - ServerVolume? providerVolume; + ServerProviderVolume? providerVolume; /// from 0.0 to 1.0 double get percentage => @@ -67,7 +64,7 @@ class DiskVolume { final bool? root, final bool? isResizable, final ServerDiskVolume? serverDiskVolume, - final ServerVolume? providerVolume, + final ServerProviderVolume? providerVolume, }) => DiskVolume( sizeUsed: sizeUsed ?? this.sizeUsed, @@ -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, ); } @@ -83,14 +79,15 @@ 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/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/logic/models/hive/server_details.dart b/lib/logic/models/hive/server_details.dart index 0e9d825a..c19be012 100644 --- a/lib/logic/models/hive/server_details.dart +++ b/lib/logic/models/hive/server_details.dart @@ -27,8 +27,9 @@ class ServerHostingDetails { @HiveField(2) final DateTime? startTime; + // TODO: Check if it is still needed @HiveField(4) - final ServerVolume volume; + final ServerProviderVolume volume; @HiveField(5) final String apiToken; @@ -52,8 +53,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/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; } diff --git a/lib/logic/models/job.dart b/lib/logic/models/job.dart index e1dd9354..0cda1407 100644 --- a/lib/logic/models/job.dart +++ b/lib/logic/models/job.dart @@ -1,8 +1,9 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:equatable/equatable.dart'; import 'package:flutter/material.dart'; -import 'package:selfprivacy/logic/cubit/client_jobs/client_jobs_cubit.dart'; +import 'package:selfprivacy/config/get_it_config.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'; @@ -11,69 +12,148 @@ 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(); @override - List get props => [id, title]; + List get props => [id, title, status]; + + ClientJob copyWithNewStatus({ + required final JobStatusEnum status, + final String? message, + }); } -class RebuildServerJob extends ClientJob { - RebuildServerJob({ - required super.title, +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 RebuildServerJob); + !jobs.any((final job) => job is UpgradeServerJob); @override - void execute(final JobsCubit cubit) async { - await cubit.upgradeServer(); - } + Future<(bool, String)> execute() 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() 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 cubit.usersCubit.createUser(user); - } + Future<(bool, String)> execute() 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 cubit.usersCubit.changeUserPassword(user, user.password!); - } + Future<(bool, String)> execute() 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; @@ -84,18 +164,32 @@ class DeleteUserJob extends ClientJob { ); @override - void execute(final JobsCubit cubit) async { - await cubit.usersCubit.deleteUser(user); - } + Future<(bool, String)> execute() 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}', @@ -110,36 +204,70 @@ class ServiceToggleJob extends ClientJob { ); @override - void execute(final JobsCubit cubit) async { - await cubit.api.switchService(service.id, needToTurnOn); + Future<(bool, String)> execute() async { + final result = await getIt() + .api + .switchService(service.id, needToTurnOn); + return (result.success, result.message ?? 'jobs.generic_error'.tr()); } @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 cubit.usersCubit.addSshKey(user, publicKey); - } + Future<(bool, String)> execute() 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; @@ -154,10 +282,120 @@ class DeleteSSHKeyJob extends ClientJob { ); @override - void execute(final JobsCubit cubit) async { - await cubit.usersCubit.deleteSshKey(user, publicKey); + Future<(bool, String)> execute() 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() async => getIt() + .setAutoUpgradeSettings(enable, allowReboot); + + @override + bool shouldRemoveInsteadOfAdd(final List jobs) { + final currentSettings = getIt() + .apiData + .settings + .data + ?.autoUpgradeSettings; + if (currentSettings == null) { + return false; + } + return currentSettings.enable == enable && + currentSettings.allowReboot == allowReboot; } @override - List get props => [id, title, user, publicKey]; + 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() async => + getIt().setServerTimezone(timezone); + + @override + bool shouldRemoveInsteadOfAdd(final List jobs) { + final currentSettings = + getIt().apiData.settings.data?.timezone; + if (currentSettings == null) { + return false; + } + return currentSettings == timezone; + } + + @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/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/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/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/logic/models/service.dart b/lib/logic/models/service.dart index e4ee27b4..d5b4e0af 100644 --- a/lib/logic/models/service.dart +++ b/lib/logic/models/service.dart @@ -1,13 +1,14 @@ 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'; -class Service { +class Service extends Equatable { Service.fromGraphQL(final Query$AllServices$services$allServices service) : this( id: service.id, @@ -36,7 +37,7 @@ class Service { [], url: service.url, ); - Service({ + const Service({ required this.id, required this.displayName, required this.description, @@ -71,7 +72,7 @@ class Service { return ''; } - static Service empty = Service( + static Service empty = const Service( id: 'empty', displayName: '', description: '', @@ -82,7 +83,7 @@ class Service { backupDescription: '', status: ServiceStatus.off, storageUsage: ServiceStorageUsage( - used: const DiskSize(byte: 0), + used: DiskSize(byte: 0), volume: '', ), svgIcon: '', @@ -103,16 +104,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/logic/providers/server_providers/digital_ocean.dart b/lib/logic/providers/server_providers/digital_ocean.dart index 741e35ac..c69dfa8c 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,10 @@ 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 +625,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 +640,10 @@ class DigitalOceanServerProvider extends ServerProvider { ); } - Future> getVolume( + Future> getVolume( final String volumeUuid, ) async { - ServerVolume? requestedVolume; + ServerProviderVolume? requestedVolume; final result = await getVolumes(); @@ -668,7 +670,7 @@ class DigitalOceanServerProvider extends ServerProvider { @override Future> attachVolume( - final ServerVolume volume, + final ServerProviderVolume volume, final int serverId, ) async => _adapter.api().attachVolume( @@ -678,7 +680,7 @@ class DigitalOceanServerProvider extends ServerProvider { @override Future> detachVolume( - final ServerVolume volume, + final ServerProviderVolume volume, ) async => _adapter.api().detachVolume( volume.name, @@ -687,7 +689,7 @@ class DigitalOceanServerProvider extends ServerProvider { @override Future> deleteVolume( - final ServerVolume volume, + final ServerProviderVolume volume, ) async => _adapter.api().deleteVolume( volume.uuid!, @@ -695,7 +697,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..d6e4f1ba 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,10 @@ 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 +646,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 +671,14 @@ 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 +694,7 @@ class HetznerServerProvider extends ServerProvider { @override Future> attachVolume( - final ServerVolume volume, + final ServerProviderVolume volume, final int serverId, ) async => _adapter.api().attachVolume( @@ -706,7 +710,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..bdb56936 100644 --- a/lib/logic/providers/server_providers/server_provider.dart +++ b/lib/logic/providers/server_providers/server_provider.dart @@ -94,35 +94,37 @@ 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. diff --git a/lib/ui/components/jobs_content/jobs_content.dart b/lib/ui/components/jobs_content/jobs_content.dart index 6a7e0cab..e3c1c9de 100644 --- a/lib/ui/components/jobs_content/jobs_content.dart +++ b/lib/ui/components/jobs_content/jobs_content.dart @@ -1,12 +1,12 @@ +import 'package:collection/collection.dart'; 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/cubit/server_jobs/server_jobs_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,13 +19,39 @@ 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 = - 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) { @@ -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(), @@ -152,8 +456,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, @@ -161,21 +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().removeServerJob(job.uid); - }, - ), - ), const SizedBox(height: 24), ], ); diff --git a/lib/ui/pages/backups/backup_details.dart b/lib/ui/pages/backups/backup_details.dart index 885bba3e..dad45db4 100644 --- a/lib/ui/pages/backups/backup_details.dart +++ b/lib/ui/pages/backups/backup_details.dart @@ -2,10 +2,10 @@ 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/cubit/backups/backups_cubit.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/server_installation/server_installation_cubit.dart'; -import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_cubit.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'; import 'package:selfprivacy/logic/models/service.dart'; @@ -31,19 +31,18 @@ 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; + context.watch().state.servicesThatCanBeBackedUp; final Duration? autobackupPeriod = backupsState.autobackupPeriod; final List backupJobs = context - .watch() + .watch() .state .backupJobList .where((final job) => job.status != JobStatusEnum.finished) @@ -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(), ), @@ -297,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( @@ -334,11 +335,12 @@ class BackupDetailsPage extends StatelessWidget { 'backup.forget_snapshot_alert'.tr(), actionButtonTitle: 'backup.forget_snapshot'.tr(), - actionButtonOnPressed: () => { - context.read().forgetSnapshot( - backup.id, - ), - }, + actionButtonOnPressed: () => + context.read().add( + ForgetSnapshot( + backup.id, + ), + ), ); }, title: Text( @@ -391,18 +393,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,32 +415,11 @@ class BackupDetailsPage extends StatelessWidget { ), onTap: preventActions ? null - : () => {context.read().forceUpdateBackups()}, - ), - 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()}, + : () => context + .read() + .add(const ForceSnapshotListUpdate()), ), + // TODO: Return reupload key button in some form ], ), ], diff --git a/lib/ui/pages/backups/backups_list.dart b/lib/ui/pages/backups/backups_list.dart index 128c8ed6..20843ef4 100644 --- a/lib/ui/pages/backups/backups_list.dart +++ b/lib/ui/pages/backups/backups_list.dart @@ -3,8 +3,8 @@ 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/cubit/services/services_cubit.dart'; +import 'package:selfprivacy/logic/bloc/backups/backups_bloc.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'; @@ -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, @@ -43,7 +43,7 @@ class BackupsListPage extends StatelessWidget { ...backups.map( (final Backup backup) { final service = context - .read() + .read() .state .getServiceById(backup.serviceId); return ListTile( @@ -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().forgetSnapshot( - backup.id, - ), - }, + actionButtonOnPressed: () => context + .read() + .add(ForgetSnapshot(backup.id)), ); }, title: Text( diff --git a/lib/ui/pages/backups/change_period_modal.dart b/lib/ui/pages/backups/change_period_modal.dart index 5cfeb079..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/cubit/backups/backups_cubit.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/server_installation/server_installation_cubit.dart'; -import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_cubit.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 25c589bd..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/cubit/backups/backups_cubit.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/server_installation/server_installation_cubit.dart'; -import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_cubit.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 63bce5b3..b5ceb2fe 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/cubit/backups/backups_cubit.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/server_installation/server_installation_cubit.dart'; -import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_cubit.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 3f461da1..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_cubit.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'; @@ -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( @@ -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 37b10a17..54dbaae4 100644 --- a/lib/ui/pages/backups/snapshot_modal.dart +++ b/lib/ui/pages/backups/snapshot_modal.dart @@ -2,9 +2,9 @@ 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_cubit.dart'; -import 'package:selfprivacy/logic/cubit/services/services_cubit.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/models/backup.dart'; import 'package:selfprivacy/logic/models/json/server_job.dart'; import 'package:selfprivacy/logic/models/service.dart'; @@ -34,7 +34,7 @@ class _SnapshotModalState extends State { @override Widget build(final BuildContext context) { final List busyServices = context - .watch() + .watch() .state .backupJobList .where( @@ -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,9 +153,11 @@ 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() 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/more/app_settings/developer_settings.dart b/lib/ui/pages/more/app_settings/developer_settings.dart index 1a8807ca..8acf16a4 100644 --- a/lib/ui/pages/more/app_settings/developer_settings.dart +++ b/lib/ui/pages/more/app_settings/developer_settings.dart @@ -1,10 +1,9 @@ import 'package:auto_route/auto_route.dart'; 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/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,15 +88,11 @@ class _DeveloperSettingsPageState extends State { ), ), ListTile( - title: const Text('ApiDevicesCubit'), + title: const Text('ApiConnectionRepository status'), subtitle: Text( - context.watch().state.status.toString(), - ), - ), - ListTile( - title: const Text('RecoveryKeyCubit'), - subtitle: Text( - context.watch().state.loadingStatus.toString(), + getIt() + .currentConnectionStatus + .toString(), ), ), ], diff --git a/lib/ui/pages/more/more.dart b/lib/ui/pages/more/more.dart index 6a9213c9..0dfa3e4e 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/brand_header/brand_header.dart'; import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart'; import 'package:selfprivacy/ui/components/cards/filled_card.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/providers/providers.dart b/lib/ui/pages/providers/providers.dart index 5b2285f3..b03c5b55 100644 --- a/lib/ui/pages/providers/providers.dart +++ b/lib/ui/pages/providers/providers.dart @@ -2,11 +2,11 @@ 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/bloc/volumes/volumes_bloc.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'; @@ -30,11 +30,11 @@ 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; - final diskStatus = context.watch().state.diskStatus; + final diskStatus = context.watch().state.diskStatus; final ServerInstallationState appConfig = context.watch().state; diff --git a/lib/ui/pages/recovery_key/recovery_key.dart b/lib/ui/pages/recovery_key/recovery_key.dart index ff449377..b211bad7 100644 --- a/lib/ui/pages/recovery_key/recovery_key.dart +++ b/lib/ui/pages/recovery_key/recovery_key.dart @@ -3,8 +3,7 @@ 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 +20,28 @@ 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(): 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: [ @@ -243,7 +236,7 @@ class _RecoveryKeyConfigurationState extends State { }); try { final String token = - await context.read().generateRecoveryKey( + await context.read().generateRecoveryKey( numberOfUses: _isAmountToggled ? int.tryParse(_amountController.text) : null, @@ -257,7 +250,7 @@ class _RecoveryKeyConfigurationState extends State { }); await Navigator.of(context).push( materialRoute( - RecoveryKeyReceiving(recoveryKey: token), // TO DO + RecoveryKeyReceiving(recoveryKey: token), ), ); } on GenerationError catch (e) { diff --git a/lib/ui/pages/recovery_key/recovery_key_receiving.dart b/lib/ui/pages/recovery_key/recovery_key_receiving.dart index 334ba308..e7983bb3 100644 --- a/lib/ui/pages/recovery_key/recovery_key_receiving.dart +++ b/lib/ui/pages/recovery_key/recovery_key_receiving.dart @@ -13,7 +13,7 @@ class RecoveryKeyReceiving extends StatelessWidget { Widget build(final BuildContext context) => BrandHeroScreen( heroTitle: 'recovery_key.key_main_header'.tr(), heroSubtitle: 'recovery_key.key_receiving_description'.tr(), - hasBackButton: true, + hasBackButton: false, hasFlashButton: false, children: [ const Divider(), diff --git a/lib/ui/pages/server_details/server_details_screen.dart b/lib/ui/pages/server_details/server_details_screen.dart index d1d5b8a4..ab1f64d3 100644 --- a/lib/ui/pages/server_details/server_details_screen.dart +++ b/lib/ui/pages/server_details/server_details_screen.dart @@ -2,13 +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'; import 'package:selfprivacy/ui/components/brand_loader/brand_loader.dart'; @@ -81,7 +80,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_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/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 3f7bf4c7..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/brand_linear_indicator/brand_linear_indicator.dart'; -import 'package:selfprivacy/ui/components/buttons/brand_button.dart'; -import 'package:selfprivacy/ui/layouts/brand_hero_screen.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 80d61e50..53e72d91 100644 --- a/lib/ui/pages/server_storage/binds_migration/services_migration.dart +++ b/lib/ui/pages/server_storage/binds_migration/services_migration.dart @@ -1,8 +1,8 @@ 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/services/services_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/models/disk_size.dart'; import 'package:selfprivacy/logic/models/disk_status.dart'; import 'package:selfprivacy/logic/models/service.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,18 @@ 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, + 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/server_storage/extending_volume.dart b/lib/ui/pages/server_storage/extending_volume.dart index 6c1c88b8..e6961a95 100644 --- a/lib/ui/pages/server_storage/extending_volume.dart +++ b/lib/ui/pages/server_storage/extending_volume.dart @@ -1,9 +1,8 @@ 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/provider_volumes/provider_volume_cubit.dart'; -import 'package:selfprivacy/logic/cubit/server_volumes/server_volume_cubit.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package:selfprivacy/logic/bloc/volumes/volumes_bloc.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().reload, ); context.router.popUntilRoot(); }, diff --git a/lib/ui/pages/server_storage/server_storage.dart b/lib/ui/pages/server_storage/server_storage.dart index fbd238d1..2d356e38 100644 --- a/lib/ui/pages/server_storage/server_storage.dart +++ b/lib/ui/pages/server_storage/server_storage.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_svg/svg.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/cubit/services/services_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'; @@ -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/server_storage/storage_card.dart b/lib/ui/pages/server_storage/storage_card.dart index e5f56d23..15ac54fc 100644 --- a/lib/ui/pages/server_storage/storage_card.dart +++ b/lib/ui/pages/server_storage/storage_card.dart @@ -1,9 +1,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/app_config_dependent/authentication_dependend_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/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/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 b51edb31..41e7f7ef 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/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/cubit/services/services_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'; @@ -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, @@ -80,9 +80,8 @@ class _ServicePageState extends State { const SizedBox(height: 8), ListTile( iconColor: Theme.of(context).colorScheme.onBackground, - onTap: () => { - context.read().restart(service.id), - }, + 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 @@ -116,9 +113,7 @@ class _ServicePageState extends State { onTap: () => context.pushRoute( ServicesMigrationRoute( services: [service], - diskStatus: - context.read().state.diskStatus, - isMigration: false, + diskStatus: context.read().state.diskStatus, ), ), leading: const Icon(Icons.drive_file_move_outlined), @@ -131,7 +126,7 @@ class _ServicePageState extends State { namedArgs: { 'usage': service.storageUsage.used.toString(), 'volume': context - .read() + .read() .state .getVolume(service.storageUsage.volume ?? '') .displayName, diff --git a/lib/ui/pages/services/services.dart b/lib/ui/pages/services/services.dart index d0f6a5ba..994e2040 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/bloc/services/services_bloc.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/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)); @@ -51,9 +51,7 @@ class _ServicesPageState extends State { iconData: BrandIcons.box, ) : RefreshIndicator( - onRefresh: () async { - await context.read().reload(); - }, + onRefresh: context.read().awaitReload, child: ListView( padding: paddingH15V0, children: [ 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'; diff --git a/lib/ui/pages/users/new_user.dart b/lib/ui/pages/users/new_user.dart index d35efbbd..161ec807 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 88727c7e..f09aac70 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, @@ -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(), 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: [ 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}'; } } diff --git a/pubspec.lock b/pubspec.lock index a3ebff18..9399b1cf 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: @@ -194,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 32e0b41c..09af8e4b 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -11,7 +11,9 @@ 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 + collection: ^1.18.0 cubit_form: ^2.0.1 device_info_plus: ^9.1.1 dio: ^5.4.0