From 83a2d19e37e643c405fa1f8b758df52b5be35170 Mon Sep 17 00:00:00 2001 From: inexcode Date: Wed, 16 Feb 2022 10:01:05 +0300 Subject: [PATCH] Introduce new brand screen, use it for backups --- assets/translations/en.json | 1 + assets/translations/ru.json | 1 + lib/config/brand_theme.dart | 9 +- .../components/brand_cards/brand_cards.dart | 26 ++ .../brand_hero_screen/brand_hero_screen.dart | 61 +++ .../pages/backup_details/backup_details.dart | 387 +++++++++--------- lib/ui/pages/backup_details/header.dart | 80 ---- lib/ui/pages/providers/providers.dart | 15 +- 8 files changed, 292 insertions(+), 288 deletions(-) create mode 100644 lib/ui/components/brand_hero_screen/brand_hero_screen.dart delete mode 100644 lib/ui/pages/backup_details/header.dart diff --git a/assets/translations/en.json b/assets/translations/en.json index 8675c86e..47d778fd 100644 --- a/assets/translations/en.json +++ b/assets/translations/en.json @@ -111,6 +111,7 @@ "restoring": "Restoring from backup", "error_pending": "Server returned error, check it below", "restore_alert": "You are about to restore from backup created on {}. All current data will be lost. Are you sure?", + "refresh": "Refresh status", "refetchBackups": "Refetch backup list", "refetchingList": "In a few minutes list will be updated" } diff --git a/assets/translations/ru.json b/assets/translations/ru.json index a9b23e07..bd9c5ef9 100644 --- a/assets/translations/ru.json +++ b/assets/translations/ru.json @@ -111,6 +111,7 @@ "restoring": "Восстановление из копии", "error_pending": "Сервер вернул ошибку: проверьте её ниже.", "restore_alert": "Вы собираетесь восстановить из копии созданной {}. Все текущие данные будут потеряны. Вы уверены?", + "refresh": "Обновить статус", "refetchBackups": "Обновить список копий", "refetchingList": "Через несколько минут список будет обновлён" diff --git a/lib/config/brand_theme.dart b/lib/config/brand_theme.dart index 32b1b453..f363413c 100644 --- a/lib/config/brand_theme.dart +++ b/lib/config/brand_theme.dart @@ -38,10 +38,14 @@ final ligtTheme = ThemeData( color: BrandColors.red1, ), ), + listTileTheme: ListTileThemeData( + minLeadingWidth: 24.0, + ), textTheme: TextTheme( headline1: headline1Style, headline2: headline2Style, - caption: headline4Style, + headline3: headline3Style, + headline4: headline4Style, bodyText1: body1Style, subtitle1: TextStyle(fontSize: 15, height: 1.6), // text input style ), @@ -56,7 +60,8 @@ var darkTheme = ligtTheme.copyWith( textTheme: TextTheme( headline1: headline1Style.copyWith(color: BrandColors.white), headline2: headline2Style.copyWith(color: BrandColors.white), - caption: headline4Style.copyWith(color: BrandColors.white), + headline3: headline3Style.copyWith(color: BrandColors.white), + headline4: headline4Style.copyWith(color: BrandColors.white), bodyText1: body1Style.copyWith(color: BrandColors.white), subtitle1: TextStyle(fontSize: 15, height: 1.6), // text input style ), diff --git a/lib/ui/components/brand_cards/brand_cards.dart b/lib/ui/components/brand_cards/brand_cards.dart index 5d0e7204..398e7e89 100644 --- a/lib/ui/components/brand_cards/brand_cards.dart +++ b/lib/ui/components/brand_cards/brand_cards.dart @@ -20,6 +20,9 @@ class BrandCards { shadow: bigShadow, borderRadius: BorderRadius.circular(10), ); + static Widget outlined({required Widget child}) => _OutlinedCard( + child: child, + ); } class _BrandCard extends StatelessWidget { @@ -52,6 +55,29 @@ class _BrandCard extends StatelessWidget { } } +class _OutlinedCard extends StatelessWidget { + const _OutlinedCard({ + Key? key, + required this.child, + }) : super(key: key); + + final Widget child; + @override + Widget build(BuildContext context) { + return Card( + elevation: 0.0, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(4), + side: BorderSide( + color: Colors.grey.withOpacity(0.2), + width: 1, + ), + ), + child: child, + ); + } +} + final bigShadow = [ BoxShadow( offset: Offset(0, 4), diff --git a/lib/ui/components/brand_hero_screen/brand_hero_screen.dart b/lib/ui/components/brand_hero_screen/brand_hero_screen.dart new file mode 100644 index 00000000..934c952f --- /dev/null +++ b/lib/ui/components/brand_hero_screen/brand_hero_screen.dart @@ -0,0 +1,61 @@ +import 'package:flutter/material.dart'; +import 'package:selfprivacy/ui/components/brand_header/brand_header.dart'; + +class BrandHeroScreen extends StatelessWidget { + const BrandHeroScreen({ + Key? key, + this.headerTitle = '', + this.hasBackButton = true, + this.hasFlashButton = true, + required this.children, + this.heroIcon, + this.heroTitle, + this.heroSubtitle, + }) : super(key: key); + + final List children; + final String headerTitle; + final bool hasBackButton; + final bool hasFlashButton; + final IconData? heroIcon; + final String? heroTitle; + final String? heroSubtitle; + + @override + Widget build(BuildContext context) { + return SafeArea( + child: Scaffold( + appBar: PreferredSize( + preferredSize: Size.fromHeight(52.0), + child: BrandHeader( + title: headerTitle, + hasBackButton: hasBackButton, + hasFlashButton: hasFlashButton, + ), + ), + body: ListView( + padding: EdgeInsets.all(16.0), + children: [ + if (heroIcon != null) + Icon( + heroIcon, + size: 48.0, + ), + SizedBox(height: 16.0), + if (heroTitle != null) + Text(heroTitle!, + style: Theme.of(context).textTheme.headline2, + textAlign: TextAlign.center), + SizedBox(height: 8.0), + if (heroSubtitle != null) + Text(heroSubtitle!, + style: Theme.of(context).textTheme.bodyMedium, + textAlign: TextAlign.center), + SizedBox(height: 16.0), + ...children, + ], + ), + ), + ); + } +} diff --git a/lib/ui/pages/backup_details/backup_details.dart b/lib/ui/pages/backup_details/backup_details.dart index 752be742..3040d1aa 100644 --- a/lib/ui/pages/backup_details/backup_details.dart +++ b/lib/ui/pages/backup_details/backup_details.dart @@ -1,6 +1,6 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:selfprivacy/config/brand_colors.dart'; -import 'package:selfprivacy/config/brand_theme.dart'; import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart'; import 'package:selfprivacy/logic/cubit/backups/backups_cubit.dart'; @@ -9,13 +9,11 @@ import 'package:selfprivacy/logic/models/state_types.dart'; import 'package:selfprivacy/ui/components/action_button/action_button.dart'; import 'package:selfprivacy/ui/components/brand_alert/brand_alert.dart'; import 'package:selfprivacy/ui/components/brand_button/brand_button.dart'; -import 'package:selfprivacy/ui/components/brand_divider/brand_divider.dart'; +import 'package:selfprivacy/ui/components/brand_hero_screen/brand_hero_screen.dart'; import 'package:selfprivacy/ui/components/brand_icons/brand_icons.dart'; import 'package:selfprivacy/ui/components/brand_text/brand_text.dart'; -import 'package:selfprivacy/ui/components/icon_status_mask/icon_status_mask.dart'; -import 'package:easy_localization/easy_localization.dart'; -part 'header.dart'; +import '../../components/brand_cards/brand_cards.dart'; var navigatorKey = GlobalKey(); @@ -44,203 +42,200 @@ class _BackupDetailsState extends State var backups = context.watch().state.backups; var refreshing = context.watch().state.refreshing; - return Scaffold( - appBar: PreferredSize( - child: Column( - children: [ - Container( - height: 51, - alignment: Alignment.center, - padding: EdgeInsets.symmetric(horizontal: 15), - child: BrandText.h4('basis.details'.tr()), - ), - BrandDivider(), - ], - ), - preferredSize: Size.fromHeight(52), - ), - body: SingleChildScrollView( - physics: ClampingScrollPhysics(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: paddingH15V0, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - _Header( - providerState: providerState, - refreshing: refreshing, + return BrandHeroScreen( + heroIcon: BrandIcons.save, + heroTitle: 'providers.backup.card_title'.tr(), + heroSubtitle: 'providers.backup.bottom_sheet.1'.tr(), + children: [ + if (isReady && !isBackupInitialized) + BrandButton.rised( + onPressed: preventActions + ? null + : () async { + await context.read().createBucket(); + }, + text: 'providers.backup.initialize'.tr(), + ), + if (backupStatus == BackupStatusEnum.initializing) + BrandText.body1('providers.backup.waitingForRebuild'.tr()), + if (backupStatus != BackupStatusEnum.initializing && + backupStatus != BackupStatusEnum.noKey) + BrandCards.outlined( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (backupStatus == BackupStatusEnum.initialized) + ListTile( + onTap: preventActions + ? null + : () async { + await context.read().createBackup(); + }, + leading: Icon( + Icons.add_circle_outline_rounded, + ), + title: Text( + 'providers.backup.create_new'.tr(), + style: Theme.of(context).textTheme.headline6, + ), ), - SizedBox(height: 10), - BrandText.h2('providers.backup.card_title'.tr()), - SizedBox(height: 10), - BrandText.body1('providers.backup.bottom_sheet.1'.tr()), - SizedBox(height: 20), - if (isReady && !isBackupInitialized) - BrandButton.rised( - onPressed: preventActions - ? null - : () async { - await context.read().createBucket(); - }, - text: 'providers.backup.initialize'.tr(), + if (backupStatus == BackupStatusEnum.backingUp) + ListTile( + title: Text( + 'providers.backup.creating'.tr( + args: [(backupProgress * 100).round().toString()]), + style: Theme.of(context).textTheme.headline6, ), - if (backupStatus == BackupStatusEnum.initializing) - BrandText.body1('providers.backup.waitingForRebuild'.tr()), - if (backupStatus != BackupStatusEnum.initializing && - backupStatus != BackupStatusEnum.noKey) - Card( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(4), - side: BorderSide( - color: Colors.grey.withOpacity(0.2), - width: 1, - ), - ), - elevation: 0, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - if (backupStatus == BackupStatusEnum.initialized) - ListTile( - onTap: preventActions - ? null - : () async { - await context - .read() - .createBackup(); - }, - leading: Icon( - Icons.add_circle_outline_rounded, - color: BrandColors.textColor1, - ), - title: BrandText.h5( - 'providers.backup.create_new'.tr()), - ), - if (backupStatus == BackupStatusEnum.backingUp) - ListTile( - title: BrandText.h5('providers.backup.creating' - .tr(args: [ - (backupProgress * 100).round().toString() - ])), - subtitle: LinearProgressIndicator( - value: backupProgress, - backgroundColor: Colors.grey.withOpacity(0.2), - ), - ), - if (backupStatus == BackupStatusEnum.restoring) - ListTile( - title: BrandText.h5('providers.backup.restoring' - .tr(args: [ - (backupProgress * 100).round().toString() - ])), - subtitle: LinearProgressIndicator( - backgroundColor: Colors.grey.withOpacity(0.2), - ), - ), - if (backupStatus == BackupStatusEnum.error) - ListTile( - leading: Icon( - Icons.error_outline, - color: BrandColors.red1, - ), - title: BrandText.h5( - 'providers.backup.error_pending'.tr()), - ), - ], - ), + subtitle: LinearProgressIndicator( + value: backupProgress, + backgroundColor: Colors.grey.withOpacity(0.2), ), - 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) - Card( - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(4), - side: BorderSide( - color: Colors.grey.withOpacity(0.2), - width: 1, - ), - ), - elevation: 0, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ListTile( - leading: Icon( - Icons.refresh, - color: BrandColors.textColor1, - ), - title: - BrandText.h5('providers.backup.restore'.tr()), - ), - Divider( - height: 1.0, - ), - if (backups.isEmpty) - ListTile( - leading: Icon( - Icons.error_outline, - ), - title: Text('providers.backup.no_backups'.tr()), - ), - if (backups.isNotEmpty) - Column( - children: backups.map((backup) { - return ListTile( - onTap: preventActions - ? null - : () { - var nav = getIt(); - nav.showPopUpDialog(BrandAlert( - title: 'providers.backup.restoring' - .tr(), - contentText: - 'providers.backup.restore_alert' - .tr(args: [ - backup.time.toString() - ]), - actions: [ - ActionButton( - text: 'basis.cancel'.tr(), - ), - ActionButton( - onPressed: () => { - context - .read() - .restoreBackup(backup.id) - }, - text: 'modals.yes'.tr(), - ) - ], - )); - }, - title: Text( - MaterialLocalizations.of(context) - .formatShortDate(backup.time) + - ' ' + - TimeOfDay.fromDateTime(backup.time) - .format(context), - ), - ); - }).toList(), - ), - ], - ), + ), + if (backupStatus == BackupStatusEnum.restoring) + ListTile( + title: Text( + 'providers.backup.restoring'.tr( + args: [(backupProgress * 100).round().toString()]), + style: Theme.of(context).textTheme.headline6, ), - if (backupStatus == BackupStatusEnum.error) - BrandText.body1(backupError.toString()), - ], - ), + subtitle: LinearProgressIndicator( + backgroundColor: Colors.grey.withOpacity(0.2), + ), + ), + if (backupStatus == BackupStatusEnum.error) + ListTile( + leading: Icon( + Icons.error_outline, + color: BrandColors.red1, + ), + title: Text( + 'providers.backup.error_pending'.tr(), + style: Theme.of(context).textTheme.headline6, + ), + ), + ], ), - SizedBox(height: 10), - ], + ), + 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) + BrandCards.outlined( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ListTile( + leading: Icon( + Icons.refresh, + ), + title: Text( + 'providers.backup.restore'.tr(), + style: Theme.of(context).textTheme.headline6, + ), + ), + Divider( + height: 1.0, + ), + if (backups.isEmpty) + ListTile( + leading: Icon( + Icons.error_outline, + ), + title: Text('providers.backup.no_backups'.tr()), + ), + if (backups.isNotEmpty) + Column( + children: backups.map((backup) { + return ListTile( + onTap: preventActions + ? null + : () { + var nav = getIt(); + nav.showPopUpDialog(BrandAlert( + title: 'providers.backup.restoring'.tr(), + contentText: 'providers.backup.restore_alert' + .tr(args: [backup.time.toString()]), + actions: [ + ActionButton( + text: 'basis.cancel'.tr(), + ), + ActionButton( + onPressed: () => { + context + .read() + .restoreBackup(backup.id) + }, + text: 'modals.yes'.tr(), + ) + ], + )); + }, + title: Text( + MaterialLocalizations.of(context) + .formatShortDate(backup.time) + + ' ' + + TimeOfDay.fromDateTime(backup.time) + .format(context), + ), + ); + }).toList(), + ), + ], + ), + ), + SizedBox(height: 16), + BrandCards.outlined( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ListTile( + title: Text( + 'providers.backup.refresh'.tr(), + ), + onTap: refreshing + ? null + : () => {context.read().updateBackups()}, + enabled: !refreshing, + ), + if (providerState != StateType.uninitialized) + Column( + children: [ + Divider( + height: 1.0, + ), + ListTile( + title: Text( + 'providers.backup.refetchBackups'.tr(), + ), + onTap: preventActions + ? null + : () => { + context + .read() + .forceUpdateBackups() + }, + ), + Divider( + height: 1.0, + ), + ListTile( + title: Text( + 'providers.backup.reuploadKey'.tr(), + ), + onTap: preventActions + ? null + : () => {context.read().reuploadKey()}, + ), + ], + ), + ], + ), ), - ), + if (backupStatus == BackupStatusEnum.error) + BrandText.body1(backupError.toString()), + ], ); } } diff --git a/lib/ui/pages/backup_details/header.dart b/lib/ui/pages/backup_details/header.dart deleted file mode 100644 index 0dd93b53..00000000 --- a/lib/ui/pages/backup_details/header.dart +++ /dev/null @@ -1,80 +0,0 @@ -part of 'backup_details.dart'; - -class _Header extends StatelessWidget { - const _Header( - {Key? key, required this.providerState, required this.refreshing}) - : super(key: key); - - final StateType providerState; - final bool refreshing; - - @override - Widget build(BuildContext context) { - return Row( - children: [ - IconStatusMask( - status: providerState, - child: Icon( - BrandIcons.save, - size: 40, - color: Colors.white, - ), - ), - Spacer(), - Padding( - padding: EdgeInsets.symmetric( - vertical: 4, - horizontal: 2, - ), - child: IconButton( - onPressed: refreshing - ? null - : () => {context.read().updateBackups()}, - icon: const Icon(Icons.refresh_rounded), - ), - ), - Padding( - padding: EdgeInsets.symmetric( - vertical: 4, - horizontal: 2, - ), - child: PopupMenuButton<_PopupMenuItemType>( - enabled: providerState != StateType.uninitialized, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(10.0), - ), - onSelected: (_PopupMenuItemType result) { - switch (result) { - case _PopupMenuItemType.reuploadKey: - context.read().reuploadKey(); - break; - case _PopupMenuItemType.refetchBackups: - context.read().forceUpdateBackups(); - break; - } - }, - icon: Icon(Icons.more_vert), - itemBuilder: (BuildContext context) => [ - PopupMenuItem<_PopupMenuItemType>( - value: _PopupMenuItemType.reuploadKey, - child: Container( - padding: EdgeInsets.only(left: 5), - child: Text('providers.backup.reuploadKey'.tr()), - ), - ), - PopupMenuItem<_PopupMenuItemType>( - value: _PopupMenuItemType.refetchBackups, - child: Container( - padding: EdgeInsets.only(left: 5), - child: Text('providers.backup.refetchBackups'.tr()), - ), - ), - ], - ), - ), - ], - ); - } -} - -enum _PopupMenuItemType { reuploadKey, refetchBackups } diff --git a/lib/ui/pages/providers/providers.dart b/lib/ui/pages/providers/providers.dart index 3d04290a..c89569c4 100644 --- a/lib/ui/pages/providers/providers.dart +++ b/lib/ui/pages/providers/providers.dart @@ -1,3 +1,4 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:selfprivacy/config/brand_theme.dart'; import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart'; @@ -15,7 +16,7 @@ import 'package:selfprivacy/ui/components/not_ready_card/not_ready_card.dart'; import 'package:selfprivacy/ui/helpers/modals.dart'; import 'package:selfprivacy/ui/pages/backup_details/backup_details.dart'; import 'package:selfprivacy/ui/pages/server_details/server_details.dart'; -import 'package:easy_localization/easy_localization.dart'; +import 'package:selfprivacy/utils/route_transitions/basic.dart'; import 'package:selfprivacy/utils/ui_helpers.dart'; var navigatorKey = GlobalKey(); @@ -120,15 +121,9 @@ class _Card extends StatelessWidget { title = 'providers.backup.card_title'.tr(); stableText = 'providers.backup.status'.tr(); - onTap = () => showBrandBottomSheet( - context: context, - builder: (BuildContext context) { - return BrandBottomSheet( - isExpended: true, - child: BackupDetails(), - ); - }, - ); + onTap = () => Navigator.of(context).push(materialRoute( + BackupDetails(), + )); break; } return GestureDetector(