import 'package:auto_route/auto_route.dart'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:selfprivacy/logic/cubit/server_installation/server_installation_cubit.dart'; import 'package:selfprivacy/logic/cubit/backups/backups_cubit.dart'; import 'package:selfprivacy/logic/models/json/backup.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/layouts/brand_hero_screen.dart'; import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart'; import 'package:selfprivacy/ui/helpers/modals.dart'; GlobalKey navigatorKey = GlobalKey(); @RoutePage() class BackupDetailsPage extends StatefulWidget { const BackupDetailsPage({super.key}); @override State createState() => _BackupDetailsPageState(); } class _BackupDetailsPageState extends State with SingleTickerProviderStateMixin { @override Widget build(final BuildContext context) { final bool isReady = context.watch().state is ServerInstallationFinished; final bool isBackupInitialized = context.watch().state.isInitialized; final BackupStatusEnum backupStatus = context.watch().state.status; final StateType providerState = isReady && isBackupInitialized ? (backupStatus == BackupStatusEnum.error ? StateType.warning : StateType.stable) : StateType.uninitialized; final bool preventActions = context.watch().state.preventActions; final double backupProgress = context.watch().state.progress; final String backupError = context.watch().state.error; final List backups = context.watch().state.backups; final bool refreshing = context.watch().state.refreshing; return BrandHeroScreen( heroIcon: BrandIcons.save, heroTitle: 'backup.card_title'.tr(), heroSubtitle: 'backup.description'.tr(), children: [ if (isReady && !isBackupInitialized) BrandButton.rised( onPressed: preventActions ? null : () async { await context.read().createBucket(); }, text: 'backup.initialize'.tr(), ), if (backupStatus == BackupStatusEnum.initializing) Text( 'backup.waiting_for_rebuild'.tr(), style: Theme.of(context).textTheme.bodyMedium, ), if (backupStatus != BackupStatusEnum.initializing && backupStatus != BackupStatusEnum.noKey) OutlinedCard( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ if (backupStatus == BackupStatusEnum.initialized) ListTile( onTap: preventActions ? null : () async { await context.read().createBackup(); }, leading: const Icon( Icons.add_circle_outline_rounded, ), title: Text( 'backup.create_new'.tr(), style: Theme.of(context).textTheme.titleLarge, ), ), if (backupStatus == BackupStatusEnum.backingUp) ListTile( title: Text( 'backup.creating'.tr( args: [(backupProgress * 100).round().toString()], ), style: Theme.of(context).textTheme.titleLarge, ), subtitle: LinearProgressIndicator( value: backupProgress, backgroundColor: Colors.grey.withOpacity(0.2), ), ), if (backupStatus == BackupStatusEnum.restoring) ListTile( title: Text( 'backup.restoring'.tr( args: [(backupProgress * 100).round().toString()], ), style: Theme.of(context).textTheme.titleLarge, ), subtitle: LinearProgressIndicator( backgroundColor: Colors.grey.withOpacity(0.2), ), ), if (backupStatus == BackupStatusEnum.error) ListTile( leading: Icon( Icons.error_outline, color: Theme.of(context).colorScheme.error, ), title: Text( 'backup.error_pending'.tr(), style: Theme.of(context).textTheme.titleLarge, ), ), ], ), ), const SizedBox(height: 16), // Card with a list of existing backups // Each list item has a date // When clicked, starts the restore action if (backupStatus != BackupStatusEnum.initializing && backupStatus != BackupStatusEnum.noKey) OutlinedCard( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ ListTile( leading: const Icon( Icons.refresh, ), title: Text( 'backup.restore'.tr(), style: Theme.of(context).textTheme.titleLarge, ), ), 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) }, ); }, title: Text( '${MaterialLocalizations.of(context).formatShortDate(backup.time)} ${TimeOfDay.fromDateTime(backup.time).format(context)}', ), ), ) .toList(), ), ], ), ), const SizedBox(height: 16), OutlinedCard( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ ListTile( title: Text( 'backup.refresh'.tr(), ), onTap: refreshing ? null : () => {context.read().updateBackups()}, enabled: !refreshing, ), 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()}, ), ], ), ], ), ), if (backupStatus == BackupStatusEnum.error) Text( backupError.toString(), style: Theme.of(context).textTheme.bodyMedium, ), ], ); } }