diff --git a/assets/translations/en.json b/assets/translations/en.json index 4ca6159a..70756bfa 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -162,6 +162,7 @@ }, "backup": { "card_title": "Backup", + "card_subtitle": "Manage your backups", "description": "Will save your day in case of incident: hackers attack, server deletion, etc.", "reupload_key": "Force reupload key", "reuploaded_key": "Key reuploaded", @@ -176,7 +177,27 @@ "restore_alert": "You are about to restore from backup created on {}. All current data will be lost. Are you sure?", "refresh": "Refresh status", "refetch_backups": "Refetch backup list", - "refetching_list": "In a few minutes list will be updated" + "refetch_backups_subtitle": "Invalidate cache and refetch data from your storage provider. May cause additional charges.", + "reupload_key_subtitle": "Will instruct the server to initialize backup storage again. Use if something is broken.", + "refetching_list": "In a few minutes list will be updated", + "select_all": "Backup everything", + "create_new_select_heading": "Select what to backup", + "start": "Start backup", + "service_busy": "Another backup operation is in progress", + "latest_snapshots": "Latest snapshots", + "latest_snapshots_subtitle": "Showing last 15 snapshots", + "show_more": "Show more", + "autobackup_period_title": "Automatic backups period", + "autobackup_period_subtitle": "Backups created every {period}", + "autobackup_period_never": "Automatic backups are disabled", + "autobackup_period_every": "Every {period}", + "autobackup_period_disable": "Disable automatic backups", + "autobackup_custom": "Custom", + "autobackup_custom_hint": "Enter custom period in minutes", + "autobackup_set_period": "Set period", + "autobackup_period_set": "Period set", + "pending_jobs": "Currently running backup jobs", + "snapshots_title": "Snapshot list" }, "storage": { "card_title": "Server Storage", @@ -210,6 +231,7 @@ "enable": "Enable service", "move": "Move to another volume", "uses": "Uses {usage} on {volume}", + "snapshots": "Backup snapshots", "status": { "active": "Up and running", "inactive": "Stopped", @@ -514,4 +536,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/ui/pages/backup_details/backup_details.dart b/lib/ui/pages/backups/backup_details.dart similarity index 55% rename from lib/ui/pages/backup_details/backup_details.dart rename to lib/ui/pages/backups/backup_details.dart index 92491763..0e23e2fe 100644 --- a/lib/ui/pages/backup_details/backup_details.dart +++ b/lib/ui/pages/backups/backup_details.dart @@ -11,10 +11,14 @@ import 'package:selfprivacy/logic/models/json/server_job.dart'; import 'package:selfprivacy/logic/models/service.dart'; import 'package:selfprivacy/logic/models/state_types.dart'; import 'package:selfprivacy/ui/components/buttons/brand_button.dart'; -import 'package:selfprivacy/ui/components/cards/outlined_card.dart'; +import 'package:selfprivacy/ui/components/jobs_content/server_job_card.dart'; import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart'; import 'package:selfprivacy/ui/helpers/modals.dart'; +import 'package:selfprivacy/ui/pages/backups/change_period_modal.dart'; +import 'package:selfprivacy/ui/pages/backups/create_backups_modal.dart'; +import 'package:selfprivacy/ui/router/router.dart'; +import 'package:selfprivacy/utils/extensions/duration.dart'; GlobalKey navigatorKey = GlobalKey(); @@ -43,13 +47,30 @@ class _BackupDetailsPageState extends State final bool refreshing = context.watch().state.refreshing; final List services = context.watch().state.servicesThatCanBeBackedUp; + final Duration? autobackupPeriod = + context.watch().state.autobackupPeriod; + final List backupJobs = context + .watch() + .state + .backupJobList + .where((final job) => job.status != JobStatusEnum.finished) + .toList(); - return BrandHeroScreen( - heroIcon: BrandIcons.save, - heroTitle: 'backup.card_title'.tr(), - heroSubtitle: 'backup.description'.tr(), - children: [ - if (isReady && !isBackupInitialized) + if (!isReady) { + return BrandHeroScreen( + heroIcon: BrandIcons.save, + heroTitle: 'backup.card_title'.tr(), + heroSubtitle: 'not_ready_card.in_menu'.tr(), + children: const [], + ); + } + + if (!isBackupInitialized) { + return BrandHeroScreen( + heroIcon: BrandIcons.save, + heroTitle: 'backup.card_title'.tr(), + heroSubtitle: 'backup.description'.tr(), + children: [ BrandButton.rised( onPressed: preventActions ? null @@ -58,11 +79,19 @@ class _BackupDetailsPageState extends State }, text: 'backup.initialize'.tr(), ), + ], + ); + } + + return BrandHeroScreen( + heroIcon: BrandIcons.save, + heroTitle: 'backup.card_title'.tr(), + heroSubtitle: 'backup.description'.tr(), + children: [ ListTile( onTap: preventActions ? null : () { - // await context.read().createBackup(); showModalBottomSheet( useRootNavigator: true, context: context, @@ -88,7 +117,66 @@ class _BackupDetailsPageState extends State 'backup.create_new'.tr(), ), ), + ListTile( + onTap: preventActions + ? null + : () { + // await context.read().createBackup(); + showModalBottomSheet( + useRootNavigator: true, + context: context, + isScrollControlled: true, + builder: (final BuildContext context) => + DraggableScrollableSheet( + expand: false, + maxChildSize: 0.9, + minChildSize: 0.4, + initialChildSize: 0.6, + builder: (final context, final scrollController) => + ChangeAutobackupsPeriodModal( + scrollController: scrollController, + ), + ), + ); + }, + leading: const Icon( + Icons.manage_history_outlined, + ), + title: Text( + 'backup.autobackup_period_title'.tr(), + ), + subtitle: Text( + autobackupPeriod != null + ? 'backup.autobackup_period_subtitle'.tr( + namedArgs: { + 'period': autobackupPeriod.toPrettyString(context.locale) + }, + ) + : 'backup.autobackup_period_never'.tr(), + ), + ), const SizedBox(height: 16), + if (backupJobs.isNotEmpty) + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ListTile( + title: Text( + 'backup.pending_jobs'.tr(), + style: Theme.of(context).textTheme.headlineSmall!.copyWith( + color: Theme.of(context).colorScheme.secondary, + ), + ), + ), + for (final job in backupJobs) + Padding( + padding: const EdgeInsets.symmetric(horizontal: 16), + child: ServerJobCard( + serverJob: job, + ), + ), + ], + ), // Card with a list of existing backups // Each list item has a date // When clicked, starts the restore action @@ -98,13 +186,13 @@ class _BackupDetailsPageState extends State children: [ ListTile( title: Text( - 'backups.latest_snapshots'.tr(), + 'backup.latest_snapshots'.tr(), style: Theme.of(context).textTheme.headlineSmall!.copyWith( color: Theme.of(context).colorScheme.secondary, ), ), subtitle: Text( - 'backups.latest_snapshots_subtitle'.tr(), + 'backup.latest_snapshots_subtitle'.tr(), style: Theme.of(context).textTheme.labelMedium, ), ), @@ -117,7 +205,7 @@ class _BackupDetailsPageState extends State ), if (backups.isNotEmpty) Column( - children: backups.take(20).map( + children: backups.take(15).map( (final Backup backup) { final service = context .read() @@ -163,221 +251,71 @@ class _BackupDetailsPageState extends State }, ).toList(), ), - if (backups.isNotEmpty && backups.length > 20) + if (backups.isNotEmpty && backups.length > 15) ListTile( title: Text( - 'backups.show_more'.tr(), + 'backup.show_more'.tr(), style: Theme.of(context).textTheme.labelMedium, ), leading: const Icon( Icons.arrow_drop_down, ), - onTap: null, + onTap: () => + context.pushRoute(BackupsListRoute(service: null)), ) ], ), - const SizedBox(height: 16), - OutlinedCard( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + 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: [ ListTile( title: Text( - 'backup.refresh'.tr(), + 'backup.refetch_backups'.tr(), ), - onTap: refreshing + subtitle: Text( + 'backup.refetch_backups_subtitle'.tr(), + ), + leading: const Icon( + Icons.cached_outlined, + ), + onTap: preventActions ? null - : () => {context.read().updateBackups()}, - enabled: !refreshing, + : () => {context.read().forceUpdateBackups()}, ), - if (providerState != StateType.uninitialized) - Column( - children: [ - const Divider( - height: 1.0, - ), - ListTile( - title: Text( - 'backup.refetch_backups'.tr(), - ), - onTap: preventActions - ? null - : () => { - context - .read() - .forceUpdateBackups() - }, - ), - const Divider( - height: 1.0, - ), - ListTile( - title: Text( - 'backup.reupload_key'.tr(), - ), - onTap: preventActions - ? null - : () => {context.read().reuploadKey()}, - ), - ], + const SizedBox(height: 8), + const Divider(), + const SizedBox(height: 8), + ListTile( + title: Text( + 'backup.reupload_key'.tr(), ), + subtitle: Text( + 'backup.reupload_key_subtitle'.tr(), + ), + leading: const Icon( + Icons.warning_amber_outlined, + ), + onTap: preventActions + ? null + : () => {context.read().reuploadKey()}, + ), ], ), - ), - ], - ); - } -} - -class CreateBackupsModal extends StatefulWidget { - const CreateBackupsModal({ - required this.services, - required this.scrollController, - super.key, - }); - - final List services; - final ScrollController scrollController; - - @override - State createState() => _CreateBackupsModalState(); -} - -class _CreateBackupsModalState extends State { - // Store in state the selected services to backup - List selectedServices = []; - - // Select all services on modal open - @override - void initState() { - super.initState(); - final List busyServices = context - .read() - .state - .backupJobList - .where( - (final ServerJob job) => - job.status == JobStatusEnum.running || - job.status == JobStatusEnum.created, - ) - .map((final ServerJob job) => job.typeId.split('.')[1]) - .toList(); - selectedServices.addAll( - widget.services - .where((final Service service) => !busyServices.contains(service.id)), - ); - } - - @override - Widget build(final BuildContext context) { - final List busyServices = context - .watch() - .state - .backupJobList - .where( - (final ServerJob job) => - job.status == JobStatusEnum.running || - job.status == JobStatusEnum.created, - ) - .map((final ServerJob job) => job.typeId.split('.')[1]) - .toList(); - - return ListView( - controller: widget.scrollController, - padding: const EdgeInsets.all(16), - children: [ - const SizedBox(height: 16), - Text( - 'backup.create_new_select_heading'.tr(), - style: Theme.of(context).textTheme.headlineSmall, - textAlign: TextAlign.center, - ), - const SizedBox(height: 16), - // Select all services tile - CheckboxListTile( - onChanged: (final bool? value) { - setState(() { - if (value ?? true) { - setState(() { - selectedServices.clear(); - selectedServices.addAll( - widget.services.where( - (final service) => !busyServices.contains(service.id), - ), - ); - }); - } else { - selectedServices.clear(); - } - }); - }, - title: Text( - 'backup.select_all'.tr(), - ), - secondary: const Icon( - Icons.checklist_outlined, - ), - value: selectedServices.length >= - widget.services.length - busyServices.length, - ), - const Divider( - height: 1.0, - ), - ...widget.services.map( - (final Service service) { - final bool busy = busyServices.contains(service.id); - return CheckboxListTile( - onChanged: !busy - ? (final bool? value) { - setState(() { - if (value ?? true) { - setState(() { - selectedServices.add(service); - }); - } else { - setState(() { - selectedServices.remove(service); - }); - } - }); - } - : null, - title: Text( - service.displayName, - ), - subtitle: Text( - busy ? 'backup.service_busy'.tr() : service.backupDescription, - ), - secondary: SvgPicture.string( - service.svgIcon, - height: 24, - width: 24, - colorFilter: ColorFilter.mode( - busy - ? Theme.of(context).colorScheme.outlineVariant - : Theme.of(context).colorScheme.onBackground, - BlendMode.srcIn, - ), - ), - value: selectedServices.contains(service), - ); - }, - ), - const SizedBox(height: 16), - // Create backup button - FilledButton( - onPressed: selectedServices.isEmpty - ? null - : () { - context - .read() - .createMultipleBackups(selectedServices); - Navigator.of(context).pop(); - }, - child: Text( - 'backup.start'.tr(), - ), - ), ], ); } diff --git a/lib/ui/pages/backups/backups_list.dart b/lib/ui/pages/backups/backups_list.dart new file mode 100644 index 00000000..5241693a --- /dev/null +++ b/lib/ui/pages/backups/backups_list.dart @@ -0,0 +1,85 @@ +import 'package:auto_route/auto_route.dart'; +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import 'package: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/models/backup.dart'; +import 'package:selfprivacy/logic/models/service.dart'; +import 'package:selfprivacy/ui/helpers/modals.dart'; +import 'package:selfprivacy/ui/layouts/brand_hero_screen.dart'; + +@RoutePage() +class BackupsListPage extends StatelessWidget { + const BackupsListPage({ + required this.service, + super.key, + }); + + final Service? service; + + @override + Widget build(final BuildContext context) { + // 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); + final bool preventActions = + context.watch().state.preventActions; + return BrandHeroScreen( + heroTitle: 'backup.snapshots_title'.tr(), + children: [ + if (backups.isEmpty) + Center( + child: Text( + 'backup.no_backups'.tr(), + ), + ) + else + ...backups.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)}', + ), + 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, + ), + ); + }) + ], + ); + } +} diff --git a/lib/ui/pages/backups/change_period_modal.dart b/lib/ui/pages/backups/change_period_modal.dart new file mode 100644 index 00000000..f3fb2ce3 --- /dev/null +++ b/lib/ui/pages/backups/change_period_modal.dart @@ -0,0 +1,108 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; +import 'package:selfprivacy/logic/cubit/backups/backups_cubit.dart'; +import 'package:selfprivacy/logic/cubit/server_jobs/server_jobs_cubit.dart'; +import 'package:selfprivacy/utils/extensions/duration.dart'; + +class ChangeAutobackupsPeriodModal extends StatefulWidget { + const ChangeAutobackupsPeriodModal({ + required this.scrollController, + super.key, + }); + + final ScrollController scrollController; + + @override + State createState() => + _ChangeAutobackupsPeriodModalState(); +} + +class _ChangeAutobackupsPeriodModalState + extends State { + // This is a modal with radio buttons to select the autobackup period + // Period might be none, selected from predefined list or custom + // Store in state the selected period + Duration? selectedPeriod; + + static const List autobackupPeriods = [ + Duration(hours: 12), + Duration(days: 1), + Duration(days: 2), + Duration(days: 3), + Duration(days: 7), + ]; + + // Set initial period to the one currently set + @override + void initState() { + super.initState(); + selectedPeriod = context.read().state.autobackupPeriod; + } + + @override + Widget build(final BuildContext context) { + final Duration? initialAutobackupPeriod = + context.watch().state.autobackupPeriod; + return ListView( + controller: widget.scrollController, + padding: const EdgeInsets.all(16), + children: [ + const SizedBox(height: 16), + Text( + 'backup.autobackup_period_title'.tr(), + style: Theme.of(context).textTheme.headlineSmall, + textAlign: TextAlign.center, + ), + const SizedBox(height: 16), + // Select all services tile + RadioListTile( + onChanged: (final Duration? value) { + setState(() { + selectedPeriod = value; + }); + }, + title: Text( + 'backup.autobackup_period_disable'.tr(), + ), + value: null, + groupValue: selectedPeriod, + ), + const Divider( + height: 1.0, + ), + ...autobackupPeriods.map( + (final Duration period) => RadioListTile( + onChanged: (final Duration? value) { + setState(() { + selectedPeriod = value; + }); + }, + title: Text( + 'backup.autobackup_period_every'.tr( + namedArgs: {'period': period.toPrettyString(context.locale)}, + ), + ), + value: period, + groupValue: selectedPeriod, + ), + ), + const SizedBox(height: 16), + // Create backup button + FilledButton( + onPressed: selectedPeriod == initialAutobackupPeriod + ? null + : () { + context + .read() + .setAutobackupPeriod(selectedPeriod); + Navigator.of(context).pop(); + }, + child: Text( + 'backup.autobackup_set_period'.tr(), + ), + ), + ], + ); + } +} diff --git a/lib/ui/pages/backups/create_backups_modal.dart b/lib/ui/pages/backups/create_backups_modal.dart new file mode 100644 index 00000000..3f461da1 --- /dev/null +++ b/lib/ui/pages/backups/create_backups_modal.dart @@ -0,0 +1,161 @@ +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/models/json/server_job.dart'; +import 'package:selfprivacy/logic/models/service.dart'; + +class CreateBackupsModal extends StatefulWidget { + const CreateBackupsModal({ + required this.services, + required this.scrollController, + super.key, + }); + + final List services; + final ScrollController scrollController; + + @override + State createState() => _CreateBackupsModalState(); +} + +class _CreateBackupsModalState extends State { + // Store in state the selected services to backup + List selectedServices = []; + + // Select all services on modal open + @override + void initState() { + super.initState(); + final List busyServices = context + .read() + .state + .backupJobList + .where( + (final ServerJob job) => + job.status == JobStatusEnum.running || + job.status == JobStatusEnum.created, + ) + .map((final ServerJob job) => job.typeId.split('.')[1]) + .toList(); + selectedServices.addAll( + widget.services + .where((final Service service) => !busyServices.contains(service.id)), + ); + } + + @override + Widget build(final BuildContext context) { + final List busyServices = context + .watch() + .state + .backupJobList + .where( + (final ServerJob job) => + job.status == JobStatusEnum.running || + job.status == JobStatusEnum.created, + ) + .map((final ServerJob job) => job.typeId.split('.')[1]) + .toList(); + + return ListView( + controller: widget.scrollController, + padding: const EdgeInsets.all(16), + children: [ + const SizedBox(height: 16), + Text( + 'backup.create_new_select_heading'.tr(), + style: Theme.of(context).textTheme.headlineSmall, + textAlign: TextAlign.center, + ), + const SizedBox(height: 16), + // Select all services tile + CheckboxListTile( + onChanged: (final bool? value) { + setState(() { + if (value ?? true) { + setState(() { + selectedServices.clear(); + selectedServices.addAll( + widget.services.where( + (final service) => !busyServices.contains(service.id), + ), + ); + }); + } else { + selectedServices.clear(); + } + }); + }, + title: Text( + 'backup.select_all'.tr(), + ), + secondary: const Icon( + Icons.checklist_outlined, + ), + value: selectedServices.length >= + widget.services.length - busyServices.length, + ), + const Divider( + height: 1.0, + ), + ...widget.services.map( + (final Service service) { + final bool busy = busyServices.contains(service.id); + return CheckboxListTile( + onChanged: !busy + ? (final bool? value) { + setState(() { + if (value ?? true) { + setState(() { + selectedServices.add(service); + }); + } else { + setState(() { + selectedServices.remove(service); + }); + } + }); + } + : null, + title: Text( + service.displayName, + ), + subtitle: Text( + busy ? 'backup.service_busy'.tr() : service.backupDescription, + ), + secondary: SvgPicture.string( + service.svgIcon, + height: 24, + width: 24, + colorFilter: ColorFilter.mode( + busy + ? Theme.of(context).colorScheme.outlineVariant + : Theme.of(context).colorScheme.onBackground, + BlendMode.srcIn, + ), + ), + value: selectedServices.contains(service), + ); + }, + ), + const SizedBox(height: 16), + // Create backup button + FilledButton( + onPressed: selectedServices.isEmpty + ? null + : () { + context + .read() + .createMultipleBackups(selectedServices); + Navigator.of(context).pop(); + }, + child: Text( + 'backup.start'.tr(), + ), + ), + ], + ); + } +} 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 6863bfe8..5eb369bc 100644 --- a/lib/ui/pages/server_details/time_zone/time_zone.dart +++ b/lib/ui/pages/server_details/time_zone/time_zone.dart @@ -107,7 +107,7 @@ class _SelectTimezoneState extends State { Duration( milliseconds: location.currentTimeZone.offset, ) - .toDayHourMinuteFormat() + .toTimezoneOffsetFormat() .contains(timezoneFilterValue!), ) .toList() @@ -137,7 +137,7 @@ class _SelectTimezoneState extends State { location.name, ), subtitle: Text( - 'GMT ${duration.toDayHourMinuteFormat()} ${area.isNotEmpty ? '($area)' : ''}', + 'GMT ${duration.toTimezoneOffsetFormat()} ${area.isNotEmpty ? '($area)' : ''}', ), onTap: () { context.read().repository.setTimezone( diff --git a/lib/ui/pages/services/service_page.dart b/lib/ui/pages/services/service_page.dart index 0c7beafb..0b713e79 100644 --- a/lib/ui/pages/services/service_page.dart +++ b/lib/ui/pages/services/service_page.dart @@ -141,6 +141,19 @@ class _ServicePageState extends State { ), enabled: !serviceDisabled && !serviceLocked, ), + if (service.canBeBackedUp) + ListTile( + iconColor: Theme.of(context).colorScheme.onBackground, + // Open page ServicesMigrationPage + onTap: () => context.pushRoute( + BackupsListRoute(service: service), + ), + leading: const Icon(Icons.settings_backup_restore_outlined), + title: Text( + 'service_page.snapshots'.tr(), + style: Theme.of(context).textTheme.titleMedium, + ), + ), ], ); } diff --git a/lib/ui/router/router.dart b/lib/ui/router/router.dart index af0743ca..764aacd7 100644 --- a/lib/ui/router/router.dart +++ b/lib/ui/router/router.dart @@ -3,7 +3,8 @@ import 'package:auto_route/auto_route.dart'; import 'package:flutter/material.dart'; import 'package:selfprivacy/logic/models/disk_status.dart'; import 'package:selfprivacy/logic/models/service.dart'; -import 'package:selfprivacy/ui/pages/backup_details/backup_details.dart'; +import 'package:selfprivacy/ui/pages/backups/backup_details.dart'; +import 'package:selfprivacy/ui/pages/backups/backups_list.dart'; import 'package:selfprivacy/ui/pages/devices/devices.dart'; import 'package:selfprivacy/ui/pages/dns_details/dns_details.dart'; import 'package:selfprivacy/ui/pages/more/about_application.dart'; @@ -96,6 +97,7 @@ class RootRouter extends _$RootRouter { AutoRoute(page: ServerDetailsRoute.page), AutoRoute(page: DnsDetailsRoute.page), AutoRoute(page: BackupDetailsRoute.page), + AutoRoute(page: BackupsListRoute.page), AutoRoute(page: ServerStorageRoute.page), AutoRoute(page: ExtendingVolumeRoute.page), ], @@ -141,6 +143,8 @@ String getRouteTitle(final String routeName) { return 'server.card_title'; case 'BackupDetailsRoute': return 'backup.card_title'; + case 'BackupsListRoute': + return 'backup.snapshots_title'; case 'ServerStorageRoute': return 'storage.card_title'; case 'ExtendingVolumeRoute': diff --git a/lib/ui/router/router.gr.dart b/lib/ui/router/router.gr.dart index 6fbfd79b..ef06ccf6 100644 --- a/lib/ui/router/router.gr.dart +++ b/lib/ui/router/router.gr.dart @@ -15,16 +15,103 @@ abstract class _$RootRouter extends RootStackRouter { @override final Map pagesMap = { - BackupDetailsRoute.name: (routeData) { + DevicesRoute.name: (routeData) { return AutoRoutePage( routeData: routeData, - child: const BackupDetailsPage(), + child: const DevicesScreen(), ); }, - RootRoute.name: (routeData) { + DnsDetailsRoute.name: (routeData) { return AutoRoutePage( routeData: routeData, - child: WrappedRoute(child: const RootPage()), + child: const DnsDetailsPage(), + ); + }, + AppSettingsRoute.name: (routeData) { + return AutoRoutePage( + routeData: routeData, + child: const AppSettingsPage(), + ); + }, + DeveloperSettingsRoute.name: (routeData) { + return AutoRoutePage( + routeData: routeData, + child: const DeveloperSettingsPage(), + ); + }, + AboutApplicationRoute.name: (routeData) { + return AutoRoutePage( + routeData: routeData, + child: const AboutApplicationPage(), + ); + }, + MoreRoute.name: (routeData) { + return AutoRoutePage( + routeData: routeData, + child: const MorePage(), + ); + }, + ConsoleRoute.name: (routeData) { + return AutoRoutePage( + routeData: routeData, + child: const ConsolePage(), + ); + }, + OnboardingRoute.name: (routeData) { + return AutoRoutePage( + routeData: routeData, + child: const OnboardingPage(), + ); + }, + ProvidersRoute.name: (routeData) { + return AutoRoutePage( + routeData: routeData, + child: const ProvidersPage(), + ); + }, + RecoveryKeyRoute.name: (routeData) { + return AutoRoutePage( + routeData: routeData, + child: const RecoveryKeyPage(), + ); + }, + ServerDetailsRoute.name: (routeData) { + return AutoRoutePage( + routeData: routeData, + child: const ServerDetailsScreen(), + ); + }, + ServicesMigrationRoute.name: (routeData) { + final args = routeData.argsAs(); + return AutoRoutePage( + routeData: routeData, + child: ServicesMigrationPage( + services: args.services, + diskStatus: args.diskStatus, + isMigration: args.isMigration, + key: args.key, + ), + ); + }, + ServerStorageRoute.name: (routeData) { + final args = routeData.argsAs(); + return AutoRoutePage( + routeData: routeData, + child: ServerStoragePage( + diskStatus: args.diskStatus, + key: args.key, + ), + ); + }, + ExtendingVolumeRoute.name: (routeData) { + final args = routeData.argsAs(); + return AutoRoutePage( + routeData: routeData, + child: ExtendingVolumePage( + diskVolumeToResize: args.diskVolumeToResize, + diskStatus: args.diskStatus, + key: args.key, + ), ); }, ServiceRoute.name: (routeData) { @@ -43,10 +130,16 @@ abstract class _$RootRouter extends RootStackRouter { child: const ServicesPage(), ); }, - ServerDetailsRoute.name: (routeData) { + InitializingRoute.name: (routeData) { return AutoRoutePage( routeData: routeData, - child: const ServerDetailsScreen(), + child: const InitializingPage(), + ); + }, + RecoveryRoute.name: (routeData) { + return AutoRoutePage( + routeData: routeData, + child: const RecoveryRouting(), ); }, UsersRoute.name: (routeData) { @@ -71,274 +164,59 @@ abstract class _$RootRouter extends RootStackRouter { ), ); }, - AppSettingsRoute.name: (routeData) { + RootRoute.name: (routeData) { return AutoRoutePage( routeData: routeData, - child: const AppSettingsPage(), + child: WrappedRoute(child: const RootPage()), ); }, - DeveloperSettingsRoute.name: (routeData) { + BackupDetailsRoute.name: (routeData) { return AutoRoutePage( routeData: routeData, - child: const DeveloperSettingsPage(), + child: const BackupDetailsPage(), ); }, - MoreRoute.name: (routeData) { + BackupsListRoute.name: (routeData) { + final args = routeData.argsAs(); return AutoRoutePage( routeData: routeData, - child: const MorePage(), - ); - }, - AboutApplicationRoute.name: (routeData) { - return AutoRoutePage( - routeData: routeData, - child: const AboutApplicationPage(), - ); - }, - ConsoleRoute.name: (routeData) { - return AutoRoutePage( - routeData: routeData, - child: const ConsolePage(), - ); - }, - ProvidersRoute.name: (routeData) { - return AutoRoutePage( - routeData: routeData, - child: const ProvidersPage(), - ); - }, - RecoveryKeyRoute.name: (routeData) { - return AutoRoutePage( - routeData: routeData, - child: const RecoveryKeyPage(), - ); - }, - DnsDetailsRoute.name: (routeData) { - return AutoRoutePage( - routeData: routeData, - child: const DnsDetailsPage(), - ); - }, - RecoveryRoute.name: (routeData) { - return AutoRoutePage( - routeData: routeData, - child: const RecoveryRouting(), - ); - }, - InitializingRoute.name: (routeData) { - return AutoRoutePage( - routeData: routeData, - child: const InitializingPage(), - ); - }, - ServerStorageRoute.name: (routeData) { - final args = routeData.argsAs(); - return AutoRoutePage( - routeData: routeData, - child: ServerStoragePage( - diskStatus: args.diskStatus, + child: BackupsListPage( + service: args.service, key: args.key, ), ); }, - ExtendingVolumeRoute.name: (routeData) { - final args = routeData.argsAs(); - return AutoRoutePage( - routeData: routeData, - child: ExtendingVolumePage( - diskVolumeToResize: args.diskVolumeToResize, - diskStatus: args.diskStatus, - key: args.key, - ), - ); - }, - ServicesMigrationRoute.name: (routeData) { - final args = routeData.argsAs(); - return AutoRoutePage( - routeData: routeData, - child: ServicesMigrationPage( - services: args.services, - diskStatus: args.diskStatus, - isMigration: args.isMigration, - key: args.key, - ), - ); - }, - DevicesRoute.name: (routeData) { - return AutoRoutePage( - routeData: routeData, - child: const DevicesScreen(), - ); - }, - OnboardingRoute.name: (routeData) { - return AutoRoutePage( - routeData: routeData, - child: const OnboardingPage(), - ); - }, }; } /// generated route for -/// [BackupDetailsPage] -class BackupDetailsRoute extends PageRouteInfo { - const BackupDetailsRoute({List? children}) +/// [DevicesScreen] +class DevicesRoute extends PageRouteInfo { + const DevicesRoute({List? children}) : super( - BackupDetailsRoute.name, + DevicesRoute.name, initialChildren: children, ); - static const String name = 'BackupDetailsRoute'; + static const String name = 'DevicesRoute'; static const PageInfo page = PageInfo(name); } /// generated route for -/// [RootPage] -class RootRoute extends PageRouteInfo { - const RootRoute({List? children}) +/// [DnsDetailsPage] +class DnsDetailsRoute extends PageRouteInfo { + const DnsDetailsRoute({List? children}) : super( - RootRoute.name, + DnsDetailsRoute.name, initialChildren: children, ); - static const String name = 'RootRoute'; + static const String name = 'DnsDetailsRoute'; static const PageInfo page = PageInfo(name); } -/// generated route for -/// [ServicePage] -class ServiceRoute extends PageRouteInfo { - ServiceRoute({ - required String serviceId, - Key? key, - List? children, - }) : super( - ServiceRoute.name, - args: ServiceRouteArgs( - serviceId: serviceId, - key: key, - ), - initialChildren: children, - ); - - static const String name = 'ServiceRoute'; - - static const PageInfo page = - PageInfo(name); -} - -class ServiceRouteArgs { - const ServiceRouteArgs({ - required this.serviceId, - this.key, - }); - - final String serviceId; - - final Key? key; - - @override - String toString() { - return 'ServiceRouteArgs{serviceId: $serviceId, key: $key}'; - } -} - -/// generated route for -/// [ServicesPage] -class ServicesRoute extends PageRouteInfo { - const ServicesRoute({List? children}) - : super( - ServicesRoute.name, - initialChildren: children, - ); - - static const String name = 'ServicesRoute'; - - static const PageInfo page = PageInfo(name); -} - -/// generated route for -/// [ServerDetailsScreen] -class ServerDetailsRoute extends PageRouteInfo { - const ServerDetailsRoute({List? children}) - : super( - ServerDetailsRoute.name, - initialChildren: children, - ); - - static const String name = 'ServerDetailsRoute'; - - static const PageInfo page = PageInfo(name); -} - -/// generated route for -/// [UsersPage] -class UsersRoute extends PageRouteInfo { - const UsersRoute({List? children}) - : super( - UsersRoute.name, - initialChildren: children, - ); - - static const String name = 'UsersRoute'; - - static const PageInfo page = PageInfo(name); -} - -/// generated route for -/// [NewUserPage] -class NewUserRoute extends PageRouteInfo { - const NewUserRoute({List? children}) - : super( - NewUserRoute.name, - initialChildren: children, - ); - - static const String name = 'NewUserRoute'; - - static const PageInfo page = PageInfo(name); -} - -/// generated route for -/// [UserDetailsPage] -class UserDetailsRoute extends PageRouteInfo { - UserDetailsRoute({ - required String login, - Key? key, - List? children, - }) : super( - UserDetailsRoute.name, - args: UserDetailsRouteArgs( - login: login, - key: key, - ), - initialChildren: children, - ); - - static const String name = 'UserDetailsRoute'; - - static const PageInfo page = - PageInfo(name); -} - -class UserDetailsRouteArgs { - const UserDetailsRouteArgs({ - required this.login, - this.key, - }); - - final String login; - - final Key? key; - - @override - String toString() { - return 'UserDetailsRouteArgs{login: $login, key: $key}'; - } -} - /// generated route for /// [AppSettingsPage] class AppSettingsRoute extends PageRouteInfo { @@ -367,20 +245,6 @@ class DeveloperSettingsRoute extends PageRouteInfo { static const PageInfo page = PageInfo(name); } -/// generated route for -/// [MorePage] -class MoreRoute extends PageRouteInfo { - const MoreRoute({List? children}) - : super( - MoreRoute.name, - initialChildren: children, - ); - - static const String name = 'MoreRoute'; - - static const PageInfo page = PageInfo(name); -} - /// generated route for /// [AboutApplicationPage] class AboutApplicationRoute extends PageRouteInfo { @@ -395,6 +259,20 @@ class AboutApplicationRoute extends PageRouteInfo { static const PageInfo page = PageInfo(name); } +/// generated route for +/// [MorePage] +class MoreRoute extends PageRouteInfo { + const MoreRoute({List? children}) + : super( + MoreRoute.name, + initialChildren: children, + ); + + static const String name = 'MoreRoute'; + + static const PageInfo page = PageInfo(name); +} + /// generated route for /// [ConsolePage] class ConsoleRoute extends PageRouteInfo { @@ -409,6 +287,20 @@ class ConsoleRoute extends PageRouteInfo { static const PageInfo page = PageInfo(name); } +/// generated route for +/// [OnboardingPage] +class OnboardingRoute extends PageRouteInfo { + const OnboardingRoute({List? children}) + : super( + OnboardingRoute.name, + initialChildren: children, + ); + + static const String name = 'OnboardingRoute'; + + static const PageInfo page = PageInfo(name); +} + /// generated route for /// [ProvidersPage] class ProvidersRoute extends PageRouteInfo { @@ -438,45 +330,65 @@ class RecoveryKeyRoute extends PageRouteInfo { } /// generated route for -/// [DnsDetailsPage] -class DnsDetailsRoute extends PageRouteInfo { - const DnsDetailsRoute({List? children}) +/// [ServerDetailsScreen] +class ServerDetailsRoute extends PageRouteInfo { + const ServerDetailsRoute({List? children}) : super( - DnsDetailsRoute.name, + ServerDetailsRoute.name, initialChildren: children, ); - static const String name = 'DnsDetailsRoute'; + static const String name = 'ServerDetailsRoute'; static const PageInfo page = PageInfo(name); } /// generated route for -/// [RecoveryRouting] -class RecoveryRoute extends PageRouteInfo { - const RecoveryRoute({List? children}) - : super( - RecoveryRoute.name, +/// [ServicesMigrationPage] +class ServicesMigrationRoute extends PageRouteInfo { + ServicesMigrationRoute({ + required List services, + required DiskStatus diskStatus, + required bool isMigration, + Key? key, + List? children, + }) : super( + ServicesMigrationRoute.name, + args: ServicesMigrationRouteArgs( + services: services, + diskStatus: diskStatus, + isMigration: isMigration, + key: key, + ), initialChildren: children, ); - static const String name = 'RecoveryRoute'; + static const String name = 'ServicesMigrationRoute'; - static const PageInfo page = PageInfo(name); + static const PageInfo page = + PageInfo(name); } -/// generated route for -/// [InitializingPage] -class InitializingRoute extends PageRouteInfo { - const InitializingRoute({List? children}) - : super( - InitializingRoute.name, - initialChildren: children, - ); +class ServicesMigrationRouteArgs { + const ServicesMigrationRouteArgs({ + required this.services, + required this.diskStatus, + required this.isMigration, + this.key, + }); - static const String name = 'InitializingRoute'; + final List services; - static const PageInfo page = PageInfo(name); + final DiskStatus diskStatus; + + final bool isMigration; + + final Key? key; + + @override + String toString() { + return 'ServicesMigrationRouteArgs{services: $services, diskStatus: $diskStatus, isMigration: $isMigration, key: $key}'; + } } /// generated route for @@ -561,77 +473,213 @@ class ExtendingVolumeRouteArgs { } /// generated route for -/// [ServicesMigrationPage] -class ServicesMigrationRoute extends PageRouteInfo { - ServicesMigrationRoute({ - required List services, - required DiskStatus diskStatus, - required bool isMigration, +/// [ServicePage] +class ServiceRoute extends PageRouteInfo { + ServiceRoute({ + required String serviceId, Key? key, List? children, }) : super( - ServicesMigrationRoute.name, - args: ServicesMigrationRouteArgs( - services: services, - diskStatus: diskStatus, - isMigration: isMigration, + ServiceRoute.name, + args: ServiceRouteArgs( + serviceId: serviceId, key: key, ), initialChildren: children, ); - static const String name = 'ServicesMigrationRoute'; + static const String name = 'ServiceRoute'; - static const PageInfo page = - PageInfo(name); + static const PageInfo page = + PageInfo(name); } -class ServicesMigrationRouteArgs { - const ServicesMigrationRouteArgs({ - required this.services, - required this.diskStatus, - required this.isMigration, +class ServiceRouteArgs { + const ServiceRouteArgs({ + required this.serviceId, this.key, }); - final List services; - - final DiskStatus diskStatus; - - final bool isMigration; + final String serviceId; final Key? key; @override String toString() { - return 'ServicesMigrationRouteArgs{services: $services, diskStatus: $diskStatus, isMigration: $isMigration, key: $key}'; + return 'ServiceRouteArgs{serviceId: $serviceId, key: $key}'; } } /// generated route for -/// [DevicesScreen] -class DevicesRoute extends PageRouteInfo { - const DevicesRoute({List? children}) +/// [ServicesPage] +class ServicesRoute extends PageRouteInfo { + const ServicesRoute({List? children}) : super( - DevicesRoute.name, + ServicesRoute.name, initialChildren: children, ); - static const String name = 'DevicesRoute'; + static const String name = 'ServicesRoute'; static const PageInfo page = PageInfo(name); } /// generated route for -/// [OnboardingPage] -class OnboardingRoute extends PageRouteInfo { - const OnboardingRoute({List? children}) +/// [InitializingPage] +class InitializingRoute extends PageRouteInfo { + const InitializingRoute({List? children}) : super( - OnboardingRoute.name, + InitializingRoute.name, initialChildren: children, ); - static const String name = 'OnboardingRoute'; + static const String name = 'InitializingRoute'; static const PageInfo page = PageInfo(name); } + +/// generated route for +/// [RecoveryRouting] +class RecoveryRoute extends PageRouteInfo { + const RecoveryRoute({List? children}) + : super( + RecoveryRoute.name, + initialChildren: children, + ); + + static const String name = 'RecoveryRoute'; + + static const PageInfo page = PageInfo(name); +} + +/// generated route for +/// [UsersPage] +class UsersRoute extends PageRouteInfo { + const UsersRoute({List? children}) + : super( + UsersRoute.name, + initialChildren: children, + ); + + static const String name = 'UsersRoute'; + + static const PageInfo page = PageInfo(name); +} + +/// generated route for +/// [NewUserPage] +class NewUserRoute extends PageRouteInfo { + const NewUserRoute({List? children}) + : super( + NewUserRoute.name, + initialChildren: children, + ); + + static const String name = 'NewUserRoute'; + + static const PageInfo page = PageInfo(name); +} + +/// generated route for +/// [UserDetailsPage] +class UserDetailsRoute extends PageRouteInfo { + UserDetailsRoute({ + required String login, + Key? key, + List? children, + }) : super( + UserDetailsRoute.name, + args: UserDetailsRouteArgs( + login: login, + key: key, + ), + initialChildren: children, + ); + + static const String name = 'UserDetailsRoute'; + + static const PageInfo page = + PageInfo(name); +} + +class UserDetailsRouteArgs { + const UserDetailsRouteArgs({ + required this.login, + this.key, + }); + + final String login; + + final Key? key; + + @override + String toString() { + return 'UserDetailsRouteArgs{login: $login, key: $key}'; + } +} + +/// generated route for +/// [RootPage] +class RootRoute extends PageRouteInfo { + const RootRoute({List? children}) + : super( + RootRoute.name, + initialChildren: children, + ); + + static const String name = 'RootRoute'; + + static const PageInfo page = PageInfo(name); +} + +/// generated route for +/// [BackupDetailsPage] +class BackupDetailsRoute extends PageRouteInfo { + const BackupDetailsRoute({List? children}) + : super( + BackupDetailsRoute.name, + initialChildren: children, + ); + + static const String name = 'BackupDetailsRoute'; + + static const PageInfo page = PageInfo(name); +} + +/// generated route for +/// [BackupsListPage] +class BackupsListRoute extends PageRouteInfo { + BackupsListRoute({ + required Service? service, + Key? key, + List? children, + }) : super( + BackupsListRoute.name, + args: BackupsListRouteArgs( + service: service, + key: key, + ), + initialChildren: children, + ); + + static const String name = 'BackupsListRoute'; + + static const PageInfo page = + PageInfo(name); +} + +class BackupsListRouteArgs { + const BackupsListRouteArgs({ + required this.service, + this.key, + }); + + final Service? service; + + final Key? key; + + @override + String toString() { + return 'BackupsListRouteArgs{service: $service, key: $key}'; + } +}