From 33e9d23043d6a1b55e88659c78198e6a4c8efd03 Mon Sep 17 00:00:00 2001 From: Inex Code Date: Thu, 14 Nov 2024 18:04:35 +0300 Subject: [PATCH] refactor(ui): Refactor the SnapshotModal --- .../bloc/server_jobs/server_jobs_state.dart | 9 ++ .../snapshot_creation_reason_tile.dart | 25 ++++ .../snapshot_creation_time_tile.dart | 25 ++++ .../snapshot_id_tile.dart} | 4 +- .../snapshot_service_tile.dart | 38 +++++ .../molecules/cards/radio_selection_card.dart | 60 ++++++++ .../pages/backups/create_backups_modal.dart | 27 +--- lib/ui/pages/backups/snapshot_modal.dart | 140 +++--------------- 8 files changed, 182 insertions(+), 146 deletions(-) create mode 100644 lib/ui/atoms/list_tiles/backup_snapshot_tiles/snapshot_creation_reason_tile.dart create mode 100644 lib/ui/atoms/list_tiles/backup_snapshot_tiles/snapshot_creation_time_tile.dart rename lib/ui/{pages/backups/snapshot_id_list_tile.dart => atoms/list_tiles/backup_snapshot_tiles/snapshot_id_tile.dart} (90%) create mode 100644 lib/ui/atoms/list_tiles/backup_snapshot_tiles/snapshot_service_tile.dart create mode 100644 lib/ui/molecules/cards/radio_selection_card.dart diff --git a/lib/logic/bloc/server_jobs/server_jobs_state.dart b/lib/logic/bloc/server_jobs/server_jobs_state.dart index 42370d51..d04c2738 100644 --- a/lib/logic/bloc/server_jobs/server_jobs_state.dart +++ b/lib/logic/bloc/server_jobs/server_jobs_state.dart @@ -30,6 +30,15 @@ sealed class ServerJobsState extends Equatable { ) .toList(); + List get busyServices => backupJobList + .where( + (final ServerJob job) => + job.status == JobStatusEnum.running || + job.status == JobStatusEnum.created, + ) + .map((final ServerJob job) => job.typeId.split('.')[1]) + .toList(); + bool get hasRemovableJobs => serverJobList.any( (final job) => job.status == JobStatusEnum.finished || diff --git a/lib/ui/atoms/list_tiles/backup_snapshot_tiles/snapshot_creation_reason_tile.dart b/lib/ui/atoms/list_tiles/backup_snapshot_tiles/snapshot_creation_reason_tile.dart new file mode 100644 index 00000000..29aa28a4 --- /dev/null +++ b/lib/ui/atoms/list_tiles/backup_snapshot_tiles/snapshot_creation_reason_tile.dart @@ -0,0 +1,25 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; + +class SnapshotCreationReasonTile extends StatelessWidget { + const SnapshotCreationReasonTile({ + required this.reason, + super.key, + }); + + final String reason; + + @override + Widget build(final BuildContext context) => ListTile( + leading: Icon( + Icons.info_outline, + color: Theme.of(context).colorScheme.onSurface, + ), + title: Text( + 'backup.snapshot_reason_title'.tr(), + ), + subtitle: Text( + reason, + ), + ); +} diff --git a/lib/ui/atoms/list_tiles/backup_snapshot_tiles/snapshot_creation_time_tile.dart b/lib/ui/atoms/list_tiles/backup_snapshot_tiles/snapshot_creation_time_tile.dart new file mode 100644 index 00000000..6ce9d76c --- /dev/null +++ b/lib/ui/atoms/list_tiles/backup_snapshot_tiles/snapshot_creation_time_tile.dart @@ -0,0 +1,25 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; + +class SnapshotCreationTimeTile extends StatelessWidget { + const SnapshotCreationTimeTile({ + required this.time, + super.key, + }); + + final DateTime time; + + @override + Widget build(final BuildContext context) => ListTile( + leading: Icon( + Icons.access_time_outlined, + color: Theme.of(context).colorScheme.onSurface, + ), + title: Text( + 'backup.snapshot_creation_time_title'.tr(), + ), + subtitle: Text( + '${MaterialLocalizations.of(context).formatShortDate(time.toLocal())} ${TimeOfDay.fromDateTime(time.toLocal()).format(context)}', + ), + ); +} diff --git a/lib/ui/pages/backups/snapshot_id_list_tile.dart b/lib/ui/atoms/list_tiles/backup_snapshot_tiles/snapshot_id_tile.dart similarity index 90% rename from lib/ui/pages/backups/snapshot_id_list_tile.dart rename to lib/ui/atoms/list_tiles/backup_snapshot_tiles/snapshot_id_tile.dart index 22d35d34..123498a9 100644 --- a/lib/ui/pages/backups/snapshot_id_list_tile.dart +++ b/lib/ui/atoms/list_tiles/backup_snapshot_tiles/snapshot_id_tile.dart @@ -3,8 +3,8 @@ import 'package:flutter/material.dart'; import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/utils/platform_adapter.dart'; -class SnapshotIdListTile extends StatelessWidget { - const SnapshotIdListTile({ +class SnapshotIdTile extends StatelessWidget { + const SnapshotIdTile({ required this.snapshotId, super.key, }); diff --git a/lib/ui/atoms/list_tiles/backup_snapshot_tiles/snapshot_service_tile.dart b/lib/ui/atoms/list_tiles/backup_snapshot_tiles/snapshot_service_tile.dart new file mode 100644 index 00000000..b41330c5 --- /dev/null +++ b/lib/ui/atoms/list_tiles/backup_snapshot_tiles/snapshot_service_tile.dart @@ -0,0 +1,38 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:selfprivacy/logic/models/service.dart'; + +class SnapshotServiceTile extends StatelessWidget { + const SnapshotServiceTile({ + required this.service, + required this.fallbackServiceName, + super.key, + }); + + final Service? service; + final String fallbackServiceName; + + @override + Widget build(final BuildContext context) => ListTile( + leading: service != null + ? SvgPicture.string( + service!.svgIcon, + height: 24, + width: 24, + colorFilter: ColorFilter.mode( + Theme.of(context).colorScheme.onSurface, + BlendMode.srcIn, + ), + ) + : const Icon( + Icons.question_mark_outlined, + ), + title: Text( + 'backup.snapshot_service_title'.tr(), + ), + subtitle: Text( + service?.displayName ?? fallbackServiceName, + ), + ); +} diff --git a/lib/ui/molecules/cards/radio_selection_card.dart b/lib/ui/molecules/cards/radio_selection_card.dart new file mode 100644 index 00000000..f94b29c7 --- /dev/null +++ b/lib/ui/molecules/cards/radio_selection_card.dart @@ -0,0 +1,60 @@ +import 'package:flutter/material.dart'; +import 'package:selfprivacy/ui/atoms/cards/outlined_card.dart'; + +class RadioSelectionCard extends StatelessWidget { + const RadioSelectionCard({ + required this.isSelected, + required this.title, + required this.subtitle, + required this.onTap, + super.key, + }); + + final bool isSelected; + final String title; + final String subtitle; + final void Function() onTap; + + @override + Widget build(final BuildContext context) => OutlinedCard( + borderColor: isSelected ? Theme.of(context).colorScheme.primary : null, + borderWidth: isSelected ? 3 : 1, + child: InkResponse( + highlightShape: BoxShape.rectangle, + onTap: onTap, + child: Padding( + padding: const EdgeInsets.all(16.0), + child: Row( + children: [ + if (isSelected) + Icon( + Icons.radio_button_on_outlined, + color: Theme.of(context).colorScheme.primary, + ) + else + Icon( + Icons.radio_button_off_outlined, + color: Theme.of(context).colorScheme.outline, + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: Theme.of(context).textTheme.titleMedium, + ), + Text( + subtitle, + style: Theme.of(context).textTheme.bodyMedium, + ), + ], + ), + ), + ], + ), + ), + ), + ); +} diff --git a/lib/ui/pages/backups/create_backups_modal.dart b/lib/ui/pages/backups/create_backups_modal.dart index d9fa3982..c17d04e4 100644 --- a/lib/ui/pages/backups/create_backups_modal.dart +++ b/lib/ui/pages/backups/create_backups_modal.dart @@ -4,7 +4,6 @@ import 'package:flutter_svg/flutter_svg.dart'; import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart'; import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart'; import 'package:selfprivacy/logic/bloc/volumes/volumes_bloc.dart'; -import 'package:selfprivacy/logic/models/json/server_job.dart'; import 'package:selfprivacy/logic/models/service.dart'; class CreateBackupsModal extends StatefulWidget { @@ -29,17 +28,8 @@ class _CreateBackupsModalState extends State { @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(); + final List busyServices = + context.read().state.busyServices; selectedServices.addAll( widget.services .where((final Service service) => !busyServices.contains(service.id)), @@ -48,17 +38,8 @@ class _CreateBackupsModalState extends State { @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(); + final List busyServices = + context.watch().state.busyServices; return ListView( controller: widget.scrollController, diff --git a/lib/ui/pages/backups/snapshot_modal.dart b/lib/ui/pages/backups/snapshot_modal.dart index d97f825c..611c7d1e 100644 --- a/lib/ui/pages/backups/snapshot_modal.dart +++ b/lib/ui/pages/backups/snapshot_modal.dart @@ -1,17 +1,18 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:flutter_svg/flutter_svg.dart'; import 'package:selfprivacy/config/get_it_config.dart'; import 'package:selfprivacy/logic/bloc/backups/backups_bloc.dart'; import 'package:selfprivacy/logic/bloc/server_jobs/server_jobs_bloc.dart'; import 'package:selfprivacy/logic/bloc/services/services_bloc.dart'; import 'package:selfprivacy/logic/models/backup.dart'; -import 'package:selfprivacy/logic/models/json/server_job.dart'; import 'package:selfprivacy/logic/models/service.dart'; import 'package:selfprivacy/ui/atoms/buttons/brand_button.dart'; -import 'package:selfprivacy/ui/atoms/cards/outlined_card.dart'; +import 'package:selfprivacy/ui/atoms/list_tiles/backup_snapshot_tiles/snapshot_creation_reason_tile.dart'; +import 'package:selfprivacy/ui/atoms/list_tiles/backup_snapshot_tiles/snapshot_creation_time_tile.dart'; +import 'package:selfprivacy/ui/atoms/list_tiles/backup_snapshot_tiles/snapshot_id_tile.dart'; +import 'package:selfprivacy/ui/atoms/list_tiles/backup_snapshot_tiles/snapshot_service_tile.dart'; +import 'package:selfprivacy/ui/molecules/cards/radio_selection_card.dart'; import 'package:selfprivacy/ui/molecules/info_box/info_box.dart'; -import 'package:selfprivacy/ui/pages/backups/snapshot_id_list_tile.dart'; class SnapshotModal extends StatefulWidget { const SnapshotModal({ @@ -33,17 +34,8 @@ class _SnapshotModalState extends State { @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(); + final List busyServices = + context.watch().state.busyServices; final bool isServiceBusy = busyServices.contains(widget.snapshot.serviceId); @@ -65,62 +57,25 @@ class _SnapshotModalState extends State { textAlign: TextAlign.center, ), const SizedBox(height: 16), - ListTile( - leading: service != null - ? SvgPicture.string( - service.svgIcon, - height: 24, - width: 24, - colorFilter: ColorFilter.mode( - Theme.of(context).colorScheme.onSurface, - BlendMode.srcIn, - ), - ) - : const Icon( - Icons.question_mark_outlined, - ), - title: Text( - 'backup.snapshot_service_title'.tr(), - ), - subtitle: Text( - service?.displayName ?? widget.snapshot.fallbackServiceName, - ), + SnapshotServiceTile( + service: service, + fallbackServiceName: widget.snapshot.fallbackServiceName, ), - ListTile( - leading: Icon( - Icons.access_time_outlined, - color: Theme.of(context).colorScheme.onSurface, - ), - title: Text( - 'backup.snapshot_creation_time_title'.tr(), - ), - subtitle: Text( - '${MaterialLocalizations.of(context).formatShortDate(widget.snapshot.time.toLocal())} ${TimeOfDay.fromDateTime(widget.snapshot.time.toLocal()).format(context)}', - ), - ), - SnapshotIdListTile(snapshotId: widget.snapshot.id), - ListTile( - leading: Icon( - Icons.info_outline, - color: Theme.of(context).colorScheme.onSurface, - ), - title: Text( - 'backup.snapshot_reason_title'.tr(), - ), - subtitle: Text( - widget.snapshot.reason.displayName.tr(), - ), + SnapshotCreationTimeTile(time: widget.snapshot.time), + SnapshotIdTile(snapshotId: widget.snapshot.id), + SnapshotCreationReasonTile( + reason: widget.snapshot.reason.displayName.tr(), ), if (service != null) Column( children: [ - const SizedBox(height: 8), + const SizedBox(height: 16), Text( 'backup.snapshot_modal_select_strategy'.tr(), style: Theme.of(context).textTheme.titleMedium, ), - const SizedBox(height: 8), - _BackupStrategySelectionCard( + const SizedBox(height: 16), + RadioSelectionCard( isSelected: selectedStrategy == BackupRestoreStrategy.downloadVerifyOverwrite, onTap: () { @@ -136,7 +91,7 @@ class _SnapshotModalState extends State { .tr(), ), const SizedBox(height: 8), - _BackupStrategySelectionCard( + RadioSelectionCard( isSelected: selectedStrategy == BackupRestoreStrategy.inplace, onTap: () { setState(() { @@ -147,7 +102,7 @@ class _SnapshotModalState extends State { subtitle: 'backup.snapshot_modal_inplace_option_description'.tr(), ), - const SizedBox(height: 8), + const SizedBox(height: 16), // Restore backup button BrandButton.filled( onPressed: isServiceBusy @@ -180,60 +135,3 @@ class _SnapshotModalState extends State { ); } } - -class _BackupStrategySelectionCard extends StatelessWidget { - const _BackupStrategySelectionCard({ - required this.isSelected, - required this.title, - required this.subtitle, - required this.onTap, - }); - - final bool isSelected; - final String title; - final String subtitle; - final void Function() onTap; - - @override - Widget build(final BuildContext context) => OutlinedCard( - borderColor: isSelected ? Theme.of(context).colorScheme.primary : null, - borderWidth: isSelected ? 3 : 1, - child: InkResponse( - highlightShape: BoxShape.rectangle, - onTap: onTap, - child: Padding( - padding: const EdgeInsets.all(16.0), - child: Row( - children: [ - if (isSelected) - Icon( - Icons.radio_button_on_outlined, - color: Theme.of(context).colorScheme.primary, - ) - else - Icon( - Icons.radio_button_off_outlined, - color: Theme.of(context).colorScheme.outline, - ), - const SizedBox(width: 16), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - title, - style: Theme.of(context).textTheme.bodyLarge, - ), - Text( - subtitle, - style: Theme.of(context).textTheme.bodyMedium, - ), - ], - ), - ), - ], - ), - ), - ), - ); -}