From 62b7a0ee7ec2b1100406a818255c2435ab61d12c Mon Sep 17 00:00:00 2001 From: Inex Code Date: Sun, 2 Jul 2023 14:41:31 +0300 Subject: [PATCH] feat(backups): Add descriptions for backups --- .../graphql_maps/schema/backups.graphql.dart | 2 +- .../schema/disk_volumes.graphql.dart | 1 - .../graphql_maps/schema/schema.graphql | 1 + .../schema/server_api.graphql.dart | 2 +- .../schema/server_settings.graphql.dart | 2 +- .../graphql_maps/schema/services.graphql | 1 + .../graphql_maps/schema/services.graphql.dart | 29 ++++ .../graphql_maps/schema/users.graphql.dart | 2 +- lib/logic/cubit/backups/backups_cubit.dart | 2 + lib/logic/cubit/backups/backups_state.dart | 2 +- lib/logic/models/service.dart | 4 + lib/ui/layouts/brand_hero_screen.dart | 3 +- .../pages/backup_details/backup_details.dart | 125 +++++++++++------- 13 files changed, 121 insertions(+), 55 deletions(-) diff --git a/lib/logic/api_maps/graphql_maps/schema/backups.graphql.dart b/lib/logic/api_maps/graphql_maps/schema/backups.graphql.dart index 52bf45eb..14d1ab1a 100644 --- a/lib/logic/api_maps/graphql_maps/schema/backups.graphql.dart +++ b/lib/logic/api_maps/graphql_maps/schema/backups.graphql.dart @@ -1,10 +1,10 @@ import 'dart:async'; +import 'disk_volumes.graphql.dart'; import 'package:gql/ast.dart'; import 'package:graphql/client.dart' as graphql; import 'package:selfprivacy/utils/scalars.dart'; import 'schema.graphql.dart'; import 'server_api.graphql.dart'; -import 'services.graphql.dart'; class Fragment$genericBackupConfigReturn { Fragment$genericBackupConfigReturn({ diff --git a/lib/logic/api_maps/graphql_maps/schema/disk_volumes.graphql.dart b/lib/logic/api_maps/graphql_maps/schema/disk_volumes.graphql.dart index 2d7939a5..aae28508 100644 --- a/lib/logic/api_maps/graphql_maps/schema/disk_volumes.graphql.dart +++ b/lib/logic/api_maps/graphql_maps/schema/disk_volumes.graphql.dart @@ -3,7 +3,6 @@ import 'package:gql/ast.dart'; import 'package:graphql/client.dart' as graphql; import 'schema.graphql.dart'; import 'server_api.graphql.dart'; -import 'services.graphql.dart'; class Fragment$basicMutationReturnFields { Fragment$basicMutationReturnFields({ diff --git a/lib/logic/api_maps/graphql_maps/schema/schema.graphql b/lib/logic/api_maps/graphql_maps/schema/schema.graphql index 643dca4f..1f57a51a 100644 --- a/lib/logic/api_maps/graphql_maps/schema/schema.graphql +++ b/lib/logic/api_maps/graphql_maps/schema/schema.graphql @@ -255,6 +255,7 @@ type Service { isRequired: Boolean! isEnabled: Boolean! canBeBackedUp: Boolean! + backupDescription: String! status: ServiceStatusEnum! url: String dnsRecords: [DnsRecord!] 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 75b1f344..eaddf55b 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 @@ -1,9 +1,9 @@ import 'dart:async'; +import 'disk_volumes.graphql.dart'; import 'package:gql/ast.dart'; import 'package:graphql/client.dart' as graphql; import 'package:selfprivacy/utils/scalars.dart'; import 'schema.graphql.dart'; -import 'services.graphql.dart'; class Fragment$basicMutationReturnFields { Fragment$basicMutationReturnFields({ diff --git a/lib/logic/api_maps/graphql_maps/schema/server_settings.graphql.dart b/lib/logic/api_maps/graphql_maps/schema/server_settings.graphql.dart index abbba037..ae8aabcb 100644 --- a/lib/logic/api_maps/graphql_maps/schema/server_settings.graphql.dart +++ b/lib/logic/api_maps/graphql_maps/schema/server_settings.graphql.dart @@ -1,8 +1,8 @@ import 'dart:async'; +import 'disk_volumes.graphql.dart'; import 'package:gql/ast.dart'; import 'package:graphql/client.dart' as graphql; import 'schema.graphql.dart'; -import 'services.graphql.dart'; class Fragment$basicMutationReturnFields { Fragment$basicMutationReturnFields({ diff --git a/lib/logic/api_maps/graphql_maps/schema/services.graphql b/lib/logic/api_maps/graphql_maps/schema/services.graphql index 104d22b8..ee5ee245 100644 --- a/lib/logic/api_maps/graphql_maps/schema/services.graphql +++ b/lib/logic/api_maps/graphql_maps/schema/services.graphql @@ -17,6 +17,7 @@ query AllServices { isMovable isRequired canBeBackedUp + backupDescription status storageUsage { title diff --git a/lib/logic/api_maps/graphql_maps/schema/services.graphql.dart b/lib/logic/api_maps/graphql_maps/schema/services.graphql.dart index e2cd4056..4d33f536 100644 --- a/lib/logic/api_maps/graphql_maps/schema/services.graphql.dart +++ b/lib/logic/api_maps/graphql_maps/schema/services.graphql.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'disk_volumes.graphql.dart'; import 'package:gql/ast.dart'; import 'package:graphql/client.dart' as graphql; import 'schema.graphql.dart'; @@ -2502,6 +2503,13 @@ const documentNodeQueryAllServices = DocumentNode(definitions: [ directives: [], selectionSet: null, ), + FieldNode( + name: NameNode(value: 'backupDescription'), + alias: null, + arguments: [], + directives: [], + selectionSet: null, + ), FieldNode( name: NameNode(value: 'status'), alias: null, @@ -2884,6 +2892,7 @@ class Query$AllServices$services$allServices { required this.isMovable, required this.isRequired, required this.canBeBackedUp, + required this.backupDescription, required this.status, required this.storageUsage, required this.svgIcon, @@ -2901,6 +2910,7 @@ class Query$AllServices$services$allServices { final l$isMovable = json['isMovable']; final l$isRequired = json['isRequired']; final l$canBeBackedUp = json['canBeBackedUp']; + final l$backupDescription = json['backupDescription']; final l$status = json['status']; final l$storageUsage = json['storageUsage']; final l$svgIcon = json['svgIcon']; @@ -2918,6 +2928,7 @@ class Query$AllServices$services$allServices { isMovable: (l$isMovable as bool), isRequired: (l$isRequired as bool), canBeBackedUp: (l$canBeBackedUp as bool), + backupDescription: (l$backupDescription as String), status: fromJson$Enum$ServiceStatusEnum((l$status as String)), storageUsage: Query$AllServices$services$allServices$storageUsage.fromJson( @@ -2944,6 +2955,8 @@ class Query$AllServices$services$allServices { final bool canBeBackedUp; + final String backupDescription; + final Enum$ServiceStatusEnum status; final Query$AllServices$services$allServices$storageUsage storageUsage; @@ -2972,6 +2985,8 @@ class Query$AllServices$services$allServices { _resultData['isRequired'] = l$isRequired; final l$canBeBackedUp = canBeBackedUp; _resultData['canBeBackedUp'] = l$canBeBackedUp; + final l$backupDescription = backupDescription; + _resultData['backupDescription'] = l$backupDescription; final l$status = status; _resultData['status'] = toJson$Enum$ServiceStatusEnum(l$status); final l$storageUsage = storageUsage; @@ -2995,6 +3010,7 @@ class Query$AllServices$services$allServices { final l$isMovable = isMovable; final l$isRequired = isRequired; final l$canBeBackedUp = canBeBackedUp; + final l$backupDescription = backupDescription; final l$status = status; final l$storageUsage = storageUsage; final l$svgIcon = svgIcon; @@ -3009,6 +3025,7 @@ class Query$AllServices$services$allServices { l$isMovable, l$isRequired, l$canBeBackedUp, + l$backupDescription, l$status, l$storageUsage, l$svgIcon, @@ -3077,6 +3094,11 @@ class Query$AllServices$services$allServices { if (l$canBeBackedUp != lOther$canBeBackedUp) { return false; } + final l$backupDescription = backupDescription; + final lOther$backupDescription = other.backupDescription; + if (l$backupDescription != lOther$backupDescription) { + return false; + } final l$status = status; final lOther$status = other.status; if (l$status != lOther$status) { @@ -3134,6 +3156,7 @@ abstract class CopyWith$Query$AllServices$services$allServices { bool? isMovable, bool? isRequired, bool? canBeBackedUp, + String? backupDescription, Enum$ServiceStatusEnum? status, Query$AllServices$services$allServices$storageUsage? storageUsage, String? svgIcon, @@ -3172,6 +3195,7 @@ class _CopyWithImpl$Query$AllServices$services$allServices Object? isMovable = _undefined, Object? isRequired = _undefined, Object? canBeBackedUp = _undefined, + Object? backupDescription = _undefined, Object? status = _undefined, Object? storageUsage = _undefined, Object? svgIcon = _undefined, @@ -3201,6 +3225,10 @@ class _CopyWithImpl$Query$AllServices$services$allServices canBeBackedUp: canBeBackedUp == _undefined || canBeBackedUp == null ? _instance.canBeBackedUp : (canBeBackedUp as bool), + backupDescription: + backupDescription == _undefined || backupDescription == null + ? _instance.backupDescription + : (backupDescription as String), status: status == _undefined || status == null ? _instance.status : (status as Enum$ServiceStatusEnum), @@ -3251,6 +3279,7 @@ class _CopyWithStubImpl$Query$AllServices$services$allServices bool? isMovable, bool? isRequired, bool? canBeBackedUp, + String? backupDescription, Enum$ServiceStatusEnum? status, Query$AllServices$services$allServices$storageUsage? storageUsage, String? svgIcon, diff --git a/lib/logic/api_maps/graphql_maps/schema/users.graphql.dart b/lib/logic/api_maps/graphql_maps/schema/users.graphql.dart index 4df77d6f..d17316df 100644 --- a/lib/logic/api_maps/graphql_maps/schema/users.graphql.dart +++ b/lib/logic/api_maps/graphql_maps/schema/users.graphql.dart @@ -1,8 +1,8 @@ import 'dart:async'; +import 'disk_volumes.graphql.dart'; import 'package:gql/ast.dart'; import 'package:graphql/client.dart' as graphql; import 'schema.graphql.dart'; -import 'services.graphql.dart'; class Fragment$basicMutationReturnFields { Fragment$basicMutationReturnFields({ diff --git a/lib/logic/cubit/backups/backups_cubit.dart b/lib/logic/cubit/backups/backups_cubit.dart index 61dcc576..6e34b410 100644 --- a/lib/logic/cubit/backups/backups_cubit.dart +++ b/lib/logic/cubit/backups/backups_cubit.dart @@ -30,6 +30,7 @@ class BackupsCubit extends ServerInstallationDependendCubit { 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, @@ -144,6 +145,7 @@ class BackupsCubit extends ServerInstallationDependendCubit { 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( diff --git a/lib/logic/cubit/backups/backups_state.dart b/lib/logic/cubit/backups/backups_state.dart index 988f669b..909e7f21 100644 --- a/lib/logic/cubit/backups/backups_state.dart +++ b/lib/logic/cubit/backups/backups_state.dart @@ -25,7 +25,7 @@ class BackupsState extends ServerInstallationDependendState { backups, preventActions, refreshTimer, - refreshing + refreshing, ]; BackupsState copyWith({ diff --git a/lib/logic/models/service.dart b/lib/logic/models/service.dart index 086d527d..d79615e8 100644 --- a/lib/logic/models/service.dart +++ b/lib/logic/models/service.dart @@ -18,6 +18,7 @@ class Service { isRequired: service.isRequired, isMovable: service.isMovable, canBeBackedUp: service.canBeBackedUp, + backupDescription: service.backupDescription, status: ServiceStatus.fromGraphQL(service.status), storageUsage: ServiceStorageUsage( used: DiskSize(byte: int.parse(service.storageUsage.usedSpace)), @@ -44,6 +45,7 @@ class Service { required this.isRequired, required this.isMovable, required this.canBeBackedUp, + required this.backupDescription, required this.status, required this.storageUsage, required this.svgIcon, @@ -78,6 +80,7 @@ class Service { isRequired: false, isMovable: false, canBeBackedUp: false, + backupDescription: '', status: ServiceStatus.off, storageUsage: ServiceStorageUsage( used: const DiskSize(byte: 0), @@ -95,6 +98,7 @@ class Service { final bool isRequired; final bool isMovable; final bool canBeBackedUp; + final String backupDescription; final ServiceStatus status; final ServiceStorageUsage storageUsage; final String svgIcon; diff --git a/lib/ui/layouts/brand_hero_screen.dart b/lib/ui/layouts/brand_hero_screen.dart index e7b320c0..a7b8be74 100644 --- a/lib/ui/layouts/brand_hero_screen.dart +++ b/lib/ui/layouts/brand_hero_screen.dart @@ -147,7 +147,8 @@ class _HeroSliverAppBarState extends State { context: context, useRootNavigator: true, isScrollControlled: true, - builder: (final BuildContext context) => DraggableScrollableSheet( + builder: (final BuildContext context) => + DraggableScrollableSheet( expand: false, maxChildSize: 0.9, minChildSize: 0.4, diff --git a/lib/ui/pages/backup_details/backup_details.dart b/lib/ui/pages/backup_details/backup_details.dart index b3fa2348..92491763 100644 --- a/lib/ui/pages/backup_details/backup_details.dart +++ b/lib/ui/pages/backup_details/backup_details.dart @@ -93,59 +93,88 @@ class _BackupDetailsPageState extends State // Each list item has a date // When clicked, starts the restore action if (isBackupInitialized) - OutlinedCard( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ListTile( + title: Text( + 'backups.latest_snapshots'.tr(), + style: Theme.of(context).textTheme.headlineSmall!.copyWith( + color: Theme.of(context).colorScheme.secondary, + ), + ), + subtitle: Text( + 'backups.latest_snapshots_subtitle'.tr(), + style: Theme.of(context).textTheme.labelMedium, + ), + ), + if (backups.isEmpty) ListTile( leading: const Icon( - Icons.refresh, - ), - title: Text( - 'backup.restore'.tr(), - style: Theme.of(context).textTheme.titleLarge, + Icons.error_outline, ), + title: Text('backup.no_backups'.tr()), ), - const Divider( - height: 1.0, - ), - if (backups.isEmpty) - ListTile( - leading: const Icon( - Icons.error_outline, - ), - title: Text('backup.no_backups'.tr()), - ), - if (backups.isNotEmpty) - Column( - children: backups - .map( - (final Backup backup) => ListTile( - onTap: preventActions - ? null - : () { - showPopUpAlert( - alertTitle: 'backup.restoring'.tr(), - description: 'backup.restore_alert'.tr( - args: [backup.time.toString()], - ), - actionButtonTitle: 'modals.yes'.tr(), - actionButtonOnPressed: () => { - context - .read() - .restoreBackup(backup.id) - }, - ); + if (backups.isNotEmpty) + Column( + children: backups.take(20).map( + (final Backup backup) { + final service = context + .read() + .state + .getServiceById(backup.serviceId); + return ListTile( + onTap: preventActions + ? null + : () { + showPopUpAlert( + alertTitle: 'backup.restoring'.tr(), + description: 'backup.restore_alert'.tr( + args: [backup.time.toString()], + ), + actionButtonTitle: 'modals.yes'.tr(), + actionButtonOnPressed: () => { + context + .read() + .restoreBackup(backup.id) }, - title: Text( - '${MaterialLocalizations.of(context).formatShortDate(backup.time)} ${TimeOfDay.fromDateTime(backup.time).format(context)}', - ), - ), - ) - .toList(), + ); + }, + title: Text( + '${MaterialLocalizations.of(context).formatShortDate(backup.time)} ${TimeOfDay.fromDateTime(backup.time).format(context)}', + ), + subtitle: Text( + service?.displayName ?? backup.fallbackServiceName, + ), + leading: service != null + ? SvgPicture.string( + service.svgIcon, + height: 24, + width: 24, + colorFilter: ColorFilter.mode( + Theme.of(context).colorScheme.onBackground, + BlendMode.srcIn, + ), + ) + : const Icon( + Icons.question_mark_outlined, + ), + ); + }, + ).toList(), + ), + if (backups.isNotEmpty && backups.length > 20) + ListTile( + title: Text( + 'backups.show_more'.tr(), + style: Theme.of(context).textTheme.labelMedium, ), - ], - ), + leading: const Icon( + Icons.arrow_drop_down, + ), + onTap: null, + ) + ], ), const SizedBox(height: 16), OutlinedCard( @@ -317,7 +346,7 @@ class _CreateBackupsModalState extends State { service.displayName, ), subtitle: Text( - busy ? 'backup.service_busy'.tr() : service.description, + busy ? 'backup.service_busy'.tr() : service.backupDescription, ), secondary: SvgPicture.string( service.svgIcon,