Introduce new brand screen, use it for backups

pull/85/head
Inex Code 2022-02-16 10:01:05 +03:00
parent 8de33ea19b
commit 83a2d19e37
8 changed files with 292 additions and 288 deletions

View File

@ -111,6 +111,7 @@
"restoring": "Restoring from backup", "restoring": "Restoring from backup",
"error_pending": "Server returned error, check it below", "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?", "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", "refetchBackups": "Refetch backup list",
"refetchingList": "In a few minutes list will be updated" "refetchingList": "In a few minutes list will be updated"
} }

View File

@ -111,6 +111,7 @@
"restoring": "Восстановление из копии", "restoring": "Восстановление из копии",
"error_pending": "Сервер вернул ошибку: проверьте её ниже.", "error_pending": "Сервер вернул ошибку: проверьте её ниже.",
"restore_alert": "Вы собираетесь восстановить из копии созданной {}. Все текущие данные будут потеряны. Вы уверены?", "restore_alert": "Вы собираетесь восстановить из копии созданной {}. Все текущие данные будут потеряны. Вы уверены?",
"refresh": "Обновить статус",
"refetchBackups": "Обновить список копий", "refetchBackups": "Обновить список копий",
"refetchingList": "Через несколько минут список будет обновлён" "refetchingList": "Через несколько минут список будет обновлён"

View File

@ -38,10 +38,14 @@ final ligtTheme = ThemeData(
color: BrandColors.red1, color: BrandColors.red1,
), ),
), ),
listTileTheme: ListTileThemeData(
minLeadingWidth: 24.0,
),
textTheme: TextTheme( textTheme: TextTheme(
headline1: headline1Style, headline1: headline1Style,
headline2: headline2Style, headline2: headline2Style,
caption: headline4Style, headline3: headline3Style,
headline4: headline4Style,
bodyText1: body1Style, bodyText1: body1Style,
subtitle1: TextStyle(fontSize: 15, height: 1.6), // text input style subtitle1: TextStyle(fontSize: 15, height: 1.6), // text input style
), ),
@ -56,7 +60,8 @@ var darkTheme = ligtTheme.copyWith(
textTheme: TextTheme( textTheme: TextTheme(
headline1: headline1Style.copyWith(color: BrandColors.white), headline1: headline1Style.copyWith(color: BrandColors.white),
headline2: headline2Style.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), bodyText1: body1Style.copyWith(color: BrandColors.white),
subtitle1: TextStyle(fontSize: 15, height: 1.6), // text input style subtitle1: TextStyle(fontSize: 15, height: 1.6), // text input style
), ),

View File

@ -20,6 +20,9 @@ class BrandCards {
shadow: bigShadow, shadow: bigShadow,
borderRadius: BorderRadius.circular(10), borderRadius: BorderRadius.circular(10),
); );
static Widget outlined({required Widget child}) => _OutlinedCard(
child: child,
);
} }
class _BrandCard extends StatelessWidget { 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 = [ final bigShadow = [
BoxShadow( BoxShadow(
offset: Offset(0, 4), offset: Offset(0, 4),

View File

@ -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<Widget> 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: <Widget>[
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,
],
),
),
);
}
}

View File

@ -1,6 +1,6 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:selfprivacy/config/brand_colors.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/config/get_it_config.dart';
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart'; import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.dart';
import 'package:selfprivacy/logic/cubit/backups/backups_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/action_button/action_button.dart';
import 'package:selfprivacy/ui/components/brand_alert/brand_alert.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_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_icons/brand_icons.dart';
import 'package:selfprivacy/ui/components/brand_text/brand_text.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<NavigatorState>(); var navigatorKey = GlobalKey<NavigatorState>();
@ -44,40 +42,11 @@ class _BackupDetailsState extends State<BackupDetails>
var backups = context.watch<BackupsCubit>().state.backups; var backups = context.watch<BackupsCubit>().state.backups;
var refreshing = context.watch<BackupsCubit>().state.refreshing; var refreshing = context.watch<BackupsCubit>().state.refreshing;
return Scaffold( return BrandHeroScreen(
appBar: PreferredSize( heroIcon: BrandIcons.save,
child: Column( heroTitle: 'providers.backup.card_title'.tr(),
heroSubtitle: 'providers.backup.bottom_sheet.1'.tr(),
children: [ 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,
),
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) if (isReady && !isBackupInitialized)
BrandButton.rised( BrandButton.rised(
onPressed: preventActions onPressed: preventActions
@ -91,15 +60,7 @@ class _BackupDetailsState extends State<BackupDetails>
BrandText.body1('providers.backup.waitingForRebuild'.tr()), BrandText.body1('providers.backup.waitingForRebuild'.tr()),
if (backupStatus != BackupStatusEnum.initializing && if (backupStatus != BackupStatusEnum.initializing &&
backupStatus != BackupStatusEnum.noKey) backupStatus != BackupStatusEnum.noKey)
Card( BrandCards.outlined(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4),
side: BorderSide(
color: Colors.grey.withOpacity(0.2),
width: 1,
),
),
elevation: 0,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@ -108,23 +69,23 @@ class _BackupDetailsState extends State<BackupDetails>
onTap: preventActions onTap: preventActions
? null ? null
: () async { : () async {
await context await context.read<BackupsCubit>().createBackup();
.read<BackupsCubit>()
.createBackup();
}, },
leading: Icon( leading: Icon(
Icons.add_circle_outline_rounded, Icons.add_circle_outline_rounded,
color: BrandColors.textColor1,
), ),
title: BrandText.h5( title: Text(
'providers.backup.create_new'.tr()), 'providers.backup.create_new'.tr(),
style: Theme.of(context).textTheme.headline6,
),
), ),
if (backupStatus == BackupStatusEnum.backingUp) if (backupStatus == BackupStatusEnum.backingUp)
ListTile( ListTile(
title: BrandText.h5('providers.backup.creating' title: Text(
.tr(args: [ 'providers.backup.creating'.tr(
(backupProgress * 100).round().toString() args: [(backupProgress * 100).round().toString()]),
])), style: Theme.of(context).textTheme.headline6,
),
subtitle: LinearProgressIndicator( subtitle: LinearProgressIndicator(
value: backupProgress, value: backupProgress,
backgroundColor: Colors.grey.withOpacity(0.2), backgroundColor: Colors.grey.withOpacity(0.2),
@ -132,10 +93,11 @@ class _BackupDetailsState extends State<BackupDetails>
), ),
if (backupStatus == BackupStatusEnum.restoring) if (backupStatus == BackupStatusEnum.restoring)
ListTile( ListTile(
title: BrandText.h5('providers.backup.restoring' title: Text(
.tr(args: [ 'providers.backup.restoring'.tr(
(backupProgress * 100).round().toString() args: [(backupProgress * 100).round().toString()]),
])), style: Theme.of(context).textTheme.headline6,
),
subtitle: LinearProgressIndicator( subtitle: LinearProgressIndicator(
backgroundColor: Colors.grey.withOpacity(0.2), backgroundColor: Colors.grey.withOpacity(0.2),
), ),
@ -146,8 +108,10 @@ class _BackupDetailsState extends State<BackupDetails>
Icons.error_outline, Icons.error_outline,
color: BrandColors.red1, color: BrandColors.red1,
), ),
title: BrandText.h5( title: Text(
'providers.backup.error_pending'.tr()), 'providers.backup.error_pending'.tr(),
style: Theme.of(context).textTheme.headline6,
),
), ),
], ],
), ),
@ -158,25 +122,18 @@ class _BackupDetailsState extends State<BackupDetails>
// When clicked, starts the restore action // When clicked, starts the restore action
if (backupStatus != BackupStatusEnum.initializing && if (backupStatus != BackupStatusEnum.initializing &&
backupStatus != BackupStatusEnum.noKey) backupStatus != BackupStatusEnum.noKey)
Card( BrandCards.outlined(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4),
side: BorderSide(
color: Colors.grey.withOpacity(0.2),
width: 1,
),
),
elevation: 0,
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
ListTile( ListTile(
leading: Icon( leading: Icon(
Icons.refresh, Icons.refresh,
color: BrandColors.textColor1,
), ),
title: title: Text(
BrandText.h5('providers.backup.restore'.tr()), 'providers.backup.restore'.tr(),
style: Theme.of(context).textTheme.headline6,
),
), ),
Divider( Divider(
height: 1.0, height: 1.0,
@ -197,13 +154,9 @@ class _BackupDetailsState extends State<BackupDetails>
: () { : () {
var nav = getIt<NavigationService>(); var nav = getIt<NavigationService>();
nav.showPopUpDialog(BrandAlert( nav.showPopUpDialog(BrandAlert(
title: 'providers.backup.restoring' title: 'providers.backup.restoring'.tr(),
.tr(), contentText: 'providers.backup.restore_alert'
contentText: .tr(args: [backup.time.toString()]),
'providers.backup.restore_alert'
.tr(args: [
backup.time.toString()
]),
actions: [ actions: [
ActionButton( ActionButton(
text: 'basis.cancel'.tr(), text: 'basis.cancel'.tr(),
@ -232,15 +185,57 @@ class _BackupDetailsState extends State<BackupDetails>
], ],
), ),
), ),
SizedBox(height: 16),
BrandCards.outlined(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
ListTile(
title: Text(
'providers.backup.refresh'.tr(),
),
onTap: refreshing
? null
: () => {context.read<BackupsCubit>().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<BackupsCubit>()
.forceUpdateBackups()
},
),
Divider(
height: 1.0,
),
ListTile(
title: Text(
'providers.backup.reuploadKey'.tr(),
),
onTap: preventActions
? null
: () => {context.read<BackupsCubit>().reuploadKey()},
),
],
),
],
),
),
if (backupStatus == BackupStatusEnum.error) if (backupStatus == BackupStatusEnum.error)
BrandText.body1(backupError.toString()), BrandText.body1(backupError.toString()),
], ],
),
),
SizedBox(height: 10),
],
),
),
); );
} }
} }

View File

@ -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<BackupsCubit>().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<BackupsCubit>().reuploadKey();
break;
case _PopupMenuItemType.refetchBackups:
context.read<BackupsCubit>().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 }

View File

@ -1,3 +1,4 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:selfprivacy/config/brand_theme.dart'; import 'package:selfprivacy/config/brand_theme.dart';
import 'package:selfprivacy/logic/cubit/app_config/app_config_cubit.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/helpers/modals.dart';
import 'package:selfprivacy/ui/pages/backup_details/backup_details.dart'; import 'package:selfprivacy/ui/pages/backup_details/backup_details.dart';
import 'package:selfprivacy/ui/pages/server_details/server_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'; import 'package:selfprivacy/utils/ui_helpers.dart';
var navigatorKey = GlobalKey<NavigatorState>(); var navigatorKey = GlobalKey<NavigatorState>();
@ -120,15 +121,9 @@ class _Card extends StatelessWidget {
title = 'providers.backup.card_title'.tr(); title = 'providers.backup.card_title'.tr();
stableText = 'providers.backup.status'.tr(); stableText = 'providers.backup.status'.tr();
onTap = () => showBrandBottomSheet( onTap = () => Navigator.of(context).push(materialRoute(
context: context, BackupDetails(),
builder: (BuildContext context) { ));
return BrandBottomSheet(
isExpended: true,
child: BackupDetails(),
);
},
);
break; break;
} }
return GestureDetector( return GestureDetector(