From 79a7b451424780e901c8117d4e5cfd1d733dca53 Mon Sep 17 00:00:00 2001 From: dab246 Date: Thu, 24 Feb 2022 18:55:15 +0700 Subject: [PATCH] TF-234 Implement delete a mailbox --- assets/images/ic_delete_toast.svg | 4 + assets/images/ic_remove_dialog.svg | 4 + core/lib/core.dart | 2 + .../extensions/color_extension.dart | 5 + .../presentation/resources/image_paths.dart | 2 + ...firmation_dialog_action_sheet_builder.dart | 71 +++++++++ .../cupertino_action_sheet_builder.dart | 2 +- .../dialog/confirmation_dialog_builder.dart | 148 ++++++++++++++++++ .../presentation/destination_picker_view.dart | 4 +- .../data/datasource/mailbox_datasource.dart | 2 + .../mailbox_cache_datasource_impl.dart | 5 + .../mailbox_datasource_impl.dart | 9 ++ .../mailbox/data/network/mailbox_api.dart | 25 +++ .../repository/mailbox_repository_impl.dart | 5 + .../domain/repository/mailbox_repository.dart | 2 + .../state/delete_multiple_mailbox_state.dart | 18 +++ .../delete_multiple_mailbox_interactor.dart | 25 +++ .../presentation/mailbox_bindings.dart | 6 +- .../presentation/mailbox_controller.dart | 88 ++++++++++- .../mailbox/presentation/mailbox_view.dart | 33 ++-- .../presentation/model/mailbox_node.dart | 14 +- .../presentation/model/mailbox_tree.dart | 18 +++ .../presentation/thread_controller.dart | 2 +- lib/main/localizations/app_localizations.dart | 26 +++ 24 files changed, 495 insertions(+), 25 deletions(-) create mode 100644 assets/images/ic_delete_toast.svg create mode 100644 assets/images/ic_remove_dialog.svg create mode 100644 core/lib/presentation/views/bottom_popup/confirmation_dialog_action_sheet_builder.dart create mode 100644 core/lib/presentation/views/dialog/confirmation_dialog_builder.dart create mode 100644 lib/features/mailbox/domain/state/delete_multiple_mailbox_state.dart create mode 100644 lib/features/mailbox/domain/usecases/delete_multiple_mailbox_interactor.dart diff --git a/assets/images/ic_delete_toast.svg b/assets/images/ic_delete_toast.svg new file mode 100644 index 000000000..2c82dd270 --- /dev/null +++ b/assets/images/ic_delete_toast.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/ic_remove_dialog.svg b/assets/images/ic_remove_dialog.svg new file mode 100644 index 000000000..dcd64a0fb --- /dev/null +++ b/assets/images/ic_remove_dialog.svg @@ -0,0 +1,4 @@ + + + + diff --git a/core/lib/core.dart b/core/lib/core.dart index e36944332..087464b18 100644 --- a/core/lib/core.dart +++ b/core/lib/core.dart @@ -41,11 +41,13 @@ export 'presentation/views/context_menu/context_menu_header_builder.dart'; export 'presentation/views/context_menu/simple_context_menu_action_builder.dart'; export 'presentation/views/dialog/loading_dialog_builder.dart'; export 'presentation/views/dialog/downloading_file_dialog_builder.dart'; +export 'presentation/views/dialog/confirmation_dialog_builder.dart'; export 'presentation/views/background/background_widget_builder.dart'; export 'presentation/views/html_viewer/html_content_viewer_widget.dart'; export 'presentation/views/floating_button/scrolling_floating_button_animated.dart'; export 'presentation/views/bottom_popup/cupertino_action_sheet_action_builder.dart'; export 'presentation/views/bottom_popup/cupertino_action_sheet_builder.dart'; +export 'presentation/views/bottom_popup/confirmation_dialog_action_sheet_builder.dart'; // Resources export 'presentation/resources/assets_paths.dart'; diff --git a/core/lib/presentation/extensions/color_extension.dart b/core/lib/presentation/extensions/color_extension.dart index a33f226d2..36ced66b9 100644 --- a/core/lib/presentation/extensions/color_extension.dart +++ b/core/lib/presentation/extensions/color_extension.dart @@ -80,6 +80,11 @@ extension AppColor on Color { static const colorInputBackgroundErrorVerifyName = Color(0xFFFAEBEB); static const colorInputBackgroundCreateMailbox = Color(0xFFF2F3F5); static const colorHintInputCreateMailbox= Color(0xFFA9B4C2); + static const colorMessageConfirmDialog= Color(0xFF6D7885); + static const colorActionDeleteConfirmDialog= Color(0xFFE64646); + static const colorActionCancelDialog= Color(0xFF007AFF); + static const colorMessageDialog= Color(0xFF222222); + static const colorConfirmActionDialog= Color(0xFFF2F2F2); static const mapGradientColor = [ [Color(0xFF21D4FD), Color(0xFFB721FF)], diff --git a/core/lib/presentation/resources/image_paths.dart b/core/lib/presentation/resources/image_paths.dart index 66f6e5e66..d859b7d2f 100644 --- a/core/lib/presentation/resources/image_paths.dart +++ b/core/lib/presentation/resources/image_paths.dart @@ -82,6 +82,8 @@ class ImagePaths { String get icSendToast => _getImagePath('ic_send_toast.svg'); String get icClearTextSearch => _getImagePath('ic_clear_text_search.svg'); String get icRenameMailbox => _getImagePath('ic_rename_mailbox.svg'); + String get icDeleteToast => _getImagePath('ic_delete_toast.svg'); + String get icRemoveDialog => _getImagePath('ic_remove_dialog.svg'); String _getImagePath(String imageName) { return AssetsPaths.images + imageName; diff --git a/core/lib/presentation/views/bottom_popup/confirmation_dialog_action_sheet_builder.dart b/core/lib/presentation/views/bottom_popup/confirmation_dialog_action_sheet_builder.dart new file mode 100644 index 000000000..6359c62b6 --- /dev/null +++ b/core/lib/presentation/views/bottom_popup/confirmation_dialog_action_sheet_builder.dart @@ -0,0 +1,71 @@ + +import 'package:core/core.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; + +typedef OnConfirmActionClick = void Function(); +typedef OnCancelActionClick = void Function(); + +class ConfirmationDialogActionSheetBuilder { + + final BuildContext _context; + + String? _messageText; + String? _confirmText; + String? _cancelText; + OnConfirmActionClick?_onConfirmActionClick; + OnCancelActionClick? _onCancelActionClick; + + ConfirmationDialogActionSheetBuilder(this._context); + + void onConfirmAction(String confirmText, OnConfirmActionClick onConfirmActionClick) { + _onConfirmActionClick = onConfirmActionClick; + _confirmText = confirmText; + } + + void onCancelAction(String cancelText, OnCancelActionClick onCancelActionClick) { + _onCancelActionClick = onCancelActionClick; + _cancelText = cancelText; + } + + void messageText(String message) { + _messageText = message; + } + + void show() { + showCupertinoModalPopup( + context: _context, + barrierColor: AppColor.colorDefaultCupertinoActionSheet, + builder: (context) => CupertinoActionSheet( + actions: [ + Container( + padding: EdgeInsets.symmetric(vertical: 8, horizontal: 10), + color: Colors.white, + child: CupertinoActionSheetAction( + child: Text( + _messageText ?? '', + textAlign: TextAlign.center, + style: TextStyle(fontSize: 14, color: AppColor.colorMessageConfirmDialog)), + onPressed: () => {}, + ) + ), + Container( + color: Colors.white, + child: CupertinoActionSheetAction( + child: Text( + _confirmText ?? '', + style: TextStyle(fontWeight: FontWeight.w500, fontSize: 20, color: AppColor.colorActionDeleteConfirmDialog)), + onPressed: () => _onConfirmActionClick?.call(), + ) + ), + ], + cancelButton: CupertinoActionSheetAction( + child: Text( + _cancelText ?? '', + style: TextStyle(fontWeight: FontWeight.w500, fontSize: 20, color: AppColor.colorActionCancelDialog)), + onPressed: () => _onCancelActionClick?.call(), + ), + ) + ); + } +} \ No newline at end of file diff --git a/core/lib/presentation/views/bottom_popup/cupertino_action_sheet_builder.dart b/core/lib/presentation/views/bottom_popup/cupertino_action_sheet_builder.dart index 83d3c829a..9f3d830b1 100644 --- a/core/lib/presentation/views/bottom_popup/cupertino_action_sheet_builder.dart +++ b/core/lib/presentation/views/bottom_popup/cupertino_action_sheet_builder.dart @@ -29,7 +29,7 @@ class CupertinoActionSheetBuilder { _actionTiles.addAll(tiles); } - void build() { + void show() { showCupertinoModalPopup( context: _context, barrierColor: AppColor.colorDefaultCupertinoActionSheet, diff --git a/core/lib/presentation/views/dialog/confirmation_dialog_builder.dart b/core/lib/presentation/views/dialog/confirmation_dialog_builder.dart new file mode 100644 index 000000000..57705110d --- /dev/null +++ b/core/lib/presentation/views/dialog/confirmation_dialog_builder.dart @@ -0,0 +1,148 @@ + +import 'package:core/core.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; + +typedef OnConfirmButtonAction = void Function(); +typedef OnCancelButtonAction = void Function(); +typedef OnCloseButtonAction = void Function(); + +class ConfirmDialogBuilder { + final ImagePaths _imagePath; + + Key? _key; + String _title = ''; + String _content = ''; + String _confirmText = ''; + String _cancelText = ''; + Widget? _iconWidget; + + OnConfirmButtonAction? _onConfirmButtonAction; + OnCancelButtonAction? _onCancelButtonAction; + OnCloseButtonAction? _onCloseButtonAction; + + ConfirmDialogBuilder(this._imagePath); + + void key(Key key) { + _key = key; + } + + void title(String title) { + _title = title; + } + + void content(String content) { + _content = content; + } + + void addIcon(Widget? icon) { + _iconWidget = icon; + } + + void onConfirmButtonAction(String confirmText, OnConfirmButtonAction? onConfirmButtonAction) { + _confirmText = confirmText; + _onConfirmButtonAction = onConfirmButtonAction; + } + + void onCancelButtonAction(String cancelText, OnCancelButtonAction? onCancelButtonAction) { + _cancelText = cancelText; + _onCancelButtonAction = onCancelButtonAction; + } + + void onCloseButtonAction(OnCloseButtonAction? onCloseButtonAction) { + _onCloseButtonAction = onCloseButtonAction; + } + + Widget build() { + return Dialog( + key: _key, + shape: RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(16))), + insetPadding: EdgeInsets.symmetric(horizontal: 24.0, vertical: 16.0), + child: Container( + margin: EdgeInsets.zero, + padding: EdgeInsets.zero, + width: 400, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.all(Radius.circular(16))), + child: Wrap(children: [ + Align( + alignment: Alignment.centerRight, + child: IconButton( + padding: EdgeInsets.only(top: 16, right: 16), + onPressed: () => _onCloseButtonAction?.call(), + icon: SvgPicture.asset(_imagePath.icCloseMailbox, width: 30, height: 30, fit: BoxFit.fill))), + if (_iconWidget != null) + Container( + margin: EdgeInsets.only(top: 24), + alignment: Alignment.center, + child: _iconWidget, + ), + Padding( + padding: EdgeInsets.only(top: 12), + child: Center( + child: Text( + _title, + textAlign: TextAlign.center, + style: TextStyle(fontSize: 20.0, color: AppColor.colorActionDeleteConfirmDialog, fontWeight: FontWeight.w500) + ) + ) + ), + Padding( + padding: EdgeInsets.symmetric(horizontal: 16, vertical: 24), + child: Center( + child: Text(_content, + textAlign: TextAlign.center, + style: TextStyle(fontSize: 17.0, color: AppColor.colorMessageDialog) + ), + ), + ), + Padding( + padding: EdgeInsets.only(bottom: 16, left: 16, right: 16), + child: Row( + children: [ + Expanded( + child: _buildButton(name: _cancelText, action: _onCancelButtonAction) + ), + SizedBox(width: 16), + Expanded( + child: _buildButton( + name: _confirmText, + bgColor: AppColor.colorConfirmActionDialog, + nameColor: AppColor.colorActionDeleteConfirmDialog, + action: _onConfirmButtonAction) + ) + ] + )) + ]) + ), + ); + } + + Widget _buildButton({ + String? name, Color? nameColor, Color? bgColor, Function? action + }) { + return SizedBox( + width: double.infinity, + height: 48, + child: ElevatedButton( + onPressed: () => action?.call(), + style: ButtonStyle( + foregroundColor: MaterialStateProperty.resolveWith( + (Set states) => bgColor ?? AppColor.colorTextButton), + backgroundColor: MaterialStateProperty.resolveWith( + (Set states) => bgColor ?? AppColor.colorTextButton), + shape: MaterialStateProperty.all(RoundedRectangleBorder( + borderRadius: BorderRadius.circular(8), + side: BorderSide(width: 0, color: bgColor ?? AppColor.colorTextButton), + )), + padding: MaterialStateProperty.resolveWith( + (Set states) => EdgeInsets.symmetric(horizontal: 16)), + elevation: MaterialStateProperty.resolveWith((Set states) => 0)), + child: Text(name ?? '', + textAlign: TextAlign.center, + style: TextStyle(fontSize: 17, fontWeight: FontWeight.w500, color: nameColor ?? Colors.white)), + ) + ); + } +} diff --git a/lib/features/destination_picker/presentation/destination_picker_view.dart b/lib/features/destination_picker/presentation/destination_picker_view.dart index cee096a26..85e6c4d7c 100644 --- a/lib/features/destination_picker/presentation/destination_picker_view.dart +++ b/lib/features/destination_picker/presentation/destination_picker_view.dart @@ -276,7 +276,7 @@ class DestinationPickerView extends GetWidget { _imagePaths, mailboxNode, mailboxDisplayed: MailboxDisplayed.destinationPicker) - ..addOnSelectMailboxFolderClick((mailboxNode) => controller.selectMailboxAction(mailboxNode.item.toPresentationMailboxWithMailboxPath( + ..addOnOpenMailboxFolderClick((mailboxNode) => controller.selectMailboxAction(mailboxNode.item.toPresentationMailboxWithMailboxPath( mailboxNode.getPathMailboxNode( controller.folderMailboxTree, controller.defaultMailboxList, @@ -292,7 +292,7 @@ class DestinationPickerView extends GetWidget { _imagePaths, mailboxNode, mailboxDisplayed: MailboxDisplayed.destinationPicker) - ..addOnSelectMailboxFolderClick((mailboxNode) => controller.selectMailboxAction(mailboxNode.item.toPresentationMailboxWithMailboxPath( + ..addOnOpenMailboxFolderClick((mailboxNode) => controller.selectMailboxAction(mailboxNode.item.toPresentationMailboxWithMailboxPath( mailboxNode.getPathMailboxNode( controller.folderMailboxTree, controller.defaultMailboxList, diff --git a/lib/features/mailbox/data/datasource/mailbox_datasource.dart b/lib/features/mailbox/data/datasource/mailbox_datasource.dart index 6322d2e5d..d11cb3799 100644 --- a/lib/features/mailbox/data/datasource/mailbox_datasource.dart +++ b/lib/features/mailbox/data/datasource/mailbox_datasource.dart @@ -16,4 +16,6 @@ abstract class MailboxDataSource { Future update({List? updated, List? created, List? destroyed}); Future createNewMailbox(AccountId accountId, CreateNewMailboxRequest newMailboxRequest); + + Future deleteMultipleMailbox(AccountId accountId, List mailboxIds); } \ No newline at end of file diff --git a/lib/features/mailbox/data/datasource_impl/mailbox_cache_datasource_impl.dart b/lib/features/mailbox/data/datasource_impl/mailbox_cache_datasource_impl.dart index 3bbf8e724..fd9fbfbdd 100644 --- a/lib/features/mailbox/data/datasource_impl/mailbox_cache_datasource_impl.dart +++ b/lib/features/mailbox/data/datasource_impl/mailbox_cache_datasource_impl.dart @@ -49,4 +49,9 @@ class MailboxCacheDataSourceImpl extends MailboxDataSource { Future createNewMailbox(AccountId accountId, CreateNewMailboxRequest newMailboxRequest) { throw UnimplementedError(); } + + @override + Future deleteMultipleMailbox(AccountId accountId, List mailboxIds) { + throw UnimplementedError(); + } } \ No newline at end of file diff --git a/lib/features/mailbox/data/datasource_impl/mailbox_datasource_impl.dart b/lib/features/mailbox/data/datasource_impl/mailbox_datasource_impl.dart index ef76df489..156d37911 100644 --- a/lib/features/mailbox/data/datasource_impl/mailbox_datasource_impl.dart +++ b/lib/features/mailbox/data/datasource_impl/mailbox_datasource_impl.dart @@ -50,4 +50,13 @@ class MailboxDataSourceImpl extends MailboxDataSource { throw error; }); } + + @override + Future deleteMultipleMailbox(AccountId accountId, List mailboxIds) { + return Future.sync(() async { + return await mailboxAPI.deleteMultipleMailbox(accountId, mailboxIds); + }).catchError((error) { + throw error; + }); + } } \ No newline at end of file diff --git a/lib/features/mailbox/data/network/mailbox_api.dart b/lib/features/mailbox/data/network/mailbox_api.dart index 56439886a..780767df3 100644 --- a/lib/features/mailbox/data/network/mailbox_api.dart +++ b/lib/features/mailbox/data/network/mailbox_api.dart @@ -128,4 +128,29 @@ class MailboxAPI { throw error; }); } + + Future deleteMultipleMailbox(AccountId accountId, List mailboxIds) async { + final setMailboxMethod = SetMailboxMethod(accountId) + ..addDestroy(mailboxIds.map((mailboxId) => mailboxId.id).toSet()) + ..addOnDestroyRemoveEmails(true); + + final requestBuilder = JmapRequestBuilder(httpClient, ProcessingInvocation()); + + final setMailboxInvocation = requestBuilder.invocation(setMailboxMethod); + + final response = await (requestBuilder + ..usings(setMailboxMethod.requiredCapabilities)) + .build() + .execute(); + + final setMailboxResponse = response.parse( + setMailboxInvocation.methodCallId, + SetMailboxResponse.deserialize); + + return Future.sync(() async { + return setMailboxResponse?.destroyed?.isNotEmpty == true; + }).catchError((error) { + throw error; + }); + } } \ No newline at end of file diff --git a/lib/features/mailbox/data/repository/mailbox_repository_impl.dart b/lib/features/mailbox/data/repository/mailbox_repository_impl.dart index c14ea59d9..1d222f5c5 100644 --- a/lib/features/mailbox/data/repository/mailbox_repository_impl.dart +++ b/lib/features/mailbox/data/repository/mailbox_repository_impl.dart @@ -140,4 +140,9 @@ class MailboxRepositoryImpl extends MailboxRepository { Future createNewMailbox(AccountId accountId, CreateNewMailboxRequest newMailboxRequest) { return mapDataSource[DataSourceType.network]!.createNewMailbox(accountId, newMailboxRequest); } + + @override + Future deleteMultipleMailbox(AccountId accountId, List mailboxIds) { + return mapDataSource[DataSourceType.network]!.deleteMultipleMailbox(accountId, mailboxIds); + } } \ No newline at end of file diff --git a/lib/features/mailbox/domain/repository/mailbox_repository.dart b/lib/features/mailbox/domain/repository/mailbox_repository.dart index a5b072432..2e3f1885a 100644 --- a/lib/features/mailbox/domain/repository/mailbox_repository.dart +++ b/lib/features/mailbox/domain/repository/mailbox_repository.dart @@ -11,4 +11,6 @@ abstract class MailboxRepository { Stream refresh(AccountId accountId, State currentState); Future createNewMailbox(AccountId accountId, CreateNewMailboxRequest newMailboxRequest); + + Future deleteMultipleMailbox(AccountId accountId, List mailboxIds); } \ No newline at end of file diff --git a/lib/features/mailbox/domain/state/delete_multiple_mailbox_state.dart b/lib/features/mailbox/domain/state/delete_multiple_mailbox_state.dart new file mode 100644 index 000000000..70a20dd36 --- /dev/null +++ b/lib/features/mailbox/domain/state/delete_multiple_mailbox_state.dart @@ -0,0 +1,18 @@ +import 'package:core/core.dart'; + +class DeleteMultipleMailboxSuccess extends UIState { + + DeleteMultipleMailboxSuccess(); + + @override + List get props => []; +} + +class DeleteMultipleMailboxFailure extends FeatureFailure { + final exception; + + DeleteMultipleMailboxFailure(this.exception); + + @override + List get props => [exception]; +} \ No newline at end of file diff --git a/lib/features/mailbox/domain/usecases/delete_multiple_mailbox_interactor.dart b/lib/features/mailbox/domain/usecases/delete_multiple_mailbox_interactor.dart new file mode 100644 index 000000000..4f8ab74ab --- /dev/null +++ b/lib/features/mailbox/domain/usecases/delete_multiple_mailbox_interactor.dart @@ -0,0 +1,25 @@ +import 'package:core/core.dart'; +import 'package:dartz/dartz.dart'; +import 'package:jmap_dart_client/jmap/account_id.dart'; +import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; +import 'package:tmail_ui_user/features/mailbox/domain/repository/mailbox_repository.dart'; +import 'package:tmail_ui_user/features/mailbox/domain/state/delete_multiple_mailbox_state.dart'; + +class DeleteMultipleMailboxInteractor { + final MailboxRepository mailboxRepository; + + DeleteMultipleMailboxInteractor(this.mailboxRepository); + + Stream> execute(AccountId accountId, List mailboxIds) async* { + try { + final result = await mailboxRepository.deleteMultipleMailbox(accountId, mailboxIds); + if (result) { + yield Right(DeleteMultipleMailboxSuccess()); + } else { + yield Left(DeleteMultipleMailboxFailure(null)); + } + } catch (e) { + yield Left(DeleteMultipleMailboxFailure(e)); + } + } +} \ No newline at end of file diff --git a/lib/features/mailbox/presentation/mailbox_bindings.dart b/lib/features/mailbox/presentation/mailbox_bindings.dart index 1be0fa2d6..cc51ea6cf 100644 --- a/lib/features/mailbox/presentation/mailbox_bindings.dart +++ b/lib/features/mailbox/presentation/mailbox_bindings.dart @@ -17,6 +17,7 @@ import 'package:tmail_ui_user/features/mailbox/data/network/mailbox_api.dart'; import 'package:tmail_ui_user/features/mailbox/data/repository/mailbox_repository_impl.dart'; import 'package:tmail_ui_user/features/mailbox/domain/repository/mailbox_repository.dart'; import 'package:tmail_ui_user/features/mailbox/domain/usecases/create_new_mailbox_interactor.dart'; +import 'package:tmail_ui_user/features/mailbox/domain/usecases/delete_multiple_mailbox_interactor.dart'; import 'package:tmail_ui_user/features/mailbox/domain/usecases/get_all_mailbox_interactor.dart'; import 'package:tmail_ui_user/features/mailbox/domain/usecases/refresh_all_mailbox_interactor.dart'; import 'package:tmail_ui_user/features/mailbox/domain/usecases/search_mailbox_interactor.dart'; @@ -45,6 +46,7 @@ class MailboxBindings extends BaseBindings { Get.find(), Get.find(), Get.find(), + Get.find(), Get.find(), Get.find(), Get.find(), @@ -58,7 +60,6 @@ class MailboxBindings extends BaseBindings { void bindingsDataSource() { Get.lazyPut(() => Get.find()); Get.lazyPut(() => Get.find()); - } @override @@ -66,7 +67,6 @@ class MailboxBindings extends BaseBindings { Get.lazyPut(() => MailboxDataSourceImpl(Get.find())); Get.lazyPut(() => MailboxCacheDataSourceImpl(Get.find())); Get.lazyPut(() => StateDataSourceImpl(Get.find())); - } @override @@ -76,13 +76,13 @@ class MailboxBindings extends BaseBindings { Get.lazyPut(() => RefreshAllMailboxInteractor(Get.find())); Get.lazyPut(() => CreateNewMailboxInteractor(Get.find())); Get.lazyPut(() => SearchMailboxInteractor()); + Get.lazyPut(() => DeleteMultipleMailboxInteractor(Get.find())); } @override void bindingsRepository() { Get.lazyPut(() => Get.find()); Get.lazyPut(() => Get.find()); - } @override diff --git a/lib/features/mailbox/presentation/mailbox_controller.dart b/lib/features/mailbox/presentation/mailbox_controller.dart index 314c15a17..b1e580e96 100644 --- a/lib/features/mailbox/presentation/mailbox_controller.dart +++ b/lib/features/mailbox/presentation/mailbox_controller.dart @@ -1,7 +1,7 @@ import 'package:core/core.dart'; import 'package:dartz/dartz.dart'; -import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/id.dart'; @@ -16,9 +16,11 @@ import 'package:tmail_ui_user/features/email/domain/state/mark_as_email_read_sta import 'package:tmail_ui_user/features/login/domain/usecases/delete_credential_interactor.dart'; import 'package:tmail_ui_user/features/mailbox/domain/model/create_new_mailbox_request.dart'; import 'package:tmail_ui_user/features/mailbox/domain/state/create_new_mailbox_state.dart'; +import 'package:tmail_ui_user/features/mailbox/domain/state/delete_multiple_mailbox_state.dart'; import 'package:tmail_ui_user/features/mailbox/domain/state/get_all_mailboxes_state.dart'; import 'package:tmail_ui_user/features/mailbox/domain/state/search_mailbox_state.dart'; import 'package:tmail_ui_user/features/mailbox/domain/usecases/create_new_mailbox_interactor.dart'; +import 'package:tmail_ui_user/features/mailbox/domain/usecases/delete_multiple_mailbox_interactor.dart'; import 'package:tmail_ui_user/features/mailbox/domain/usecases/get_all_mailbox_interactor.dart'; import 'package:tmail_ui_user/features/mailbox/domain/usecases/refresh_all_mailbox_interactor.dart'; import 'package:tmail_ui_user/features/mailbox/domain/usecases/search_mailbox_interactor.dart'; @@ -50,6 +52,7 @@ class MailboxController extends BaseController { final RefreshAllMailboxInteractor _refreshAllMailboxInteractor; final CreateNewMailboxInteractor _createNewMailboxInteractor; final SearchMailboxInteractor _searchMailboxInteractor; + final DeleteMultipleMailboxInteractor _deleteMultipleMailboxInteractor; final TreeBuilder _treeBuilder; final Uuid _uuid; final AppToast _appToast; @@ -77,6 +80,7 @@ class MailboxController extends BaseController { this._refreshAllMailboxInteractor, this._createNewMailboxInteractor, this._searchMailboxInteractor, + this._deleteMultipleMailboxInteractor, this._treeBuilder, this._uuid, this._appToast, @@ -144,6 +148,8 @@ class MailboxController extends BaseController { _createNewMailboxFailure(failure); } else if (failure is SearchMailboxFailure) { _searchMailboxFailure(failure); + } else if (failure is DeleteMultipleMailboxFailure) { + _deleteMailboxFailure(failure); } }, (success) { @@ -151,6 +157,12 @@ class MailboxController extends BaseController { _createNewMailboxSuccess(success); } else if (success is SearchMailboxSuccess) { _searchMailboxSuccess(success); + } else if (success is DeleteMultipleMailboxSuccess) { + _deleteMailboxSuccess(success); + } else if (success is GetAllMailboxSuccess) { + if (isSearchActive()) { + _searchMailboxAction(allMailboxes, searchQuery.value); + } } } ); @@ -310,9 +322,14 @@ class MailboxController extends BaseController { bool isSearchActive() => searchState.value.searchStatus == SearchStatus.ACTIVE; - void enableSearch() => searchState.value = searchState.value.enableSearchState(); + void enableSearch() { + _cancelSelectMailbox(); + searchState.value = searchState.value.enableSearchState(); + } void disableSearch(BuildContext context) { + _cancelSelectMailbox(); + listMailboxSearched.clear(); searchState.value = searchState.value.disableSearchState(); searchQuery.value = SearchQuery.initial(); @@ -446,13 +463,80 @@ class MailboxController extends BaseController { List selectionMailbox) { switch(actions) { case MailboxActions.delete: + _openConfirmationDialogDeleteMailboxAction(context, selectionMailbox.first); break; default: break; } } + void _openConfirmationDialogDeleteMailboxAction(BuildContext context, PresentationMailbox presentationMailbox) { + if (responsiveUtils.isMobile(context) || responsiveUtils.isMobileDevice(context)) { + (ConfirmationDialogActionSheetBuilder(context) + ..messageText(AppLocalizations.of(context).message_confirmation_dialog_delete_mailbox(presentationMailbox.name?.name ?? '')) + ..onCancelAction(AppLocalizations.of(context).cancel, () => popBack()) + ..onConfirmAction(AppLocalizations.of(context).delete, () => _deleteMailboxAction(presentationMailbox))) + .show(); + } else { + showDialog( + context: context, + barrierColor: AppColor.colorDefaultCupertinoActionSheet, + builder: (BuildContext context) => (ConfirmDialogBuilder(_imagePaths) + ..key(Key('confirm_dialog_delete_mailbox')) + ..title(AppLocalizations.of(context).delete_mailboxes) + ..content(AppLocalizations.of(context).message_confirmation_dialog_delete_mailbox(presentationMailbox.name?.name ?? '')) + ..addIcon(SvgPicture.asset(_imagePaths.icRemoveDialog, fit: BoxFit.fill)) + ..onCloseButtonAction(() => popBack()) + ..onConfirmButtonAction(AppLocalizations.of(context).delete, () => _deleteMailboxAction(presentationMailbox)) + ..onCancelButtonAction(AppLocalizations.of(context).cancel, () => popBack())) + .build()); + } + } + + void _deleteMailboxAction(PresentationMailbox presentationMailbox) { + final newFolderMailboxTree = MailboxTree(MailboxNode( + MailboxNode.rootItem(), + childrenItems: folderMailboxNodeList)); + final mailboxNode = newFolderMailboxTree.findNode(presentationMailbox.id); + + final accountId = mailboxDashBoardController.accountId.value; + + if (mailboxNode != null && accountId != null) { + final allMailboxId = newFolderMailboxTree + .getAllNodes(mailboxNode) + .map((node) => node.item.id) + .toList(); + final allMailboxIdReversed = allMailboxId.reversed.toList(); + consumeState(_deleteMultipleMailboxInteractor.execute(accountId, allMailboxIdReversed)); + } else { + _deleteMailboxFailure(DeleteMultipleMailboxFailure(null)); + } + + _cancelSelectMailbox(); + popBack(); + } + + void _deleteMailboxSuccess(DeleteMultipleMailboxSuccess success) { + if (Get.overlayContext != null && Get.context != null) { + _appToast.showToastWithIcon( + Get.overlayContext!, + message: AppLocalizations.of(Get.context!).delete_mailboxes_successfully, + icon: _imagePaths.icSelectedV2); + } + refreshMailboxChanges(); + } + + void _deleteMailboxFailure(DeleteMultipleMailboxFailure failure) { + if (Get.overlayContext != null && Get.context != null) { + _appToast.showToastWithIcon( + Get.overlayContext!, + message: AppLocalizations.of(Get.context!).delete_mailboxes_failure, + icon: _imagePaths.icDeleteToast); + } + } + void closeMailboxScreen(BuildContext context) { + _cancelSelectMailbox(); mailboxDashBoardController.closeDrawer(); } diff --git a/lib/features/mailbox/presentation/mailbox_view.dart b/lib/features/mailbox/presentation/mailbox_view.dart index 7a82ef893..585847737 100644 --- a/lib/features/mailbox/presentation/mailbox_view.dart +++ b/lib/features/mailbox/presentation/mailbox_view.dart @@ -70,14 +70,28 @@ class MailboxView extends GetWidget { bottom: 10), child: Row(mainAxisAlignment: MainAxisAlignment.center, children: [ !responsiveUtils.isDesktop(context) ? _buildCloseScreenButton(context) : SizedBox(width: 50), - Obx(() => controller.currentSelectMode.value == SelectMode.INACTIVE - ? SizedBox(width: 40) - : SizedBox(width: 49)), + Obx(() { + if (controller.isSearchActive()) { + return controller.listMailboxSearched.isNotEmpty + ? SizedBox(width: controller.isSelectionEnabled() ? 49 : 40) + : SizedBox.shrink(); + } else { + return SizedBox(width: controller.isSelectionEnabled() ? 49 : 40); + } + }), Expanded(child: Text( AppLocalizations.of(context).folders, textAlign: TextAlign.center, style: TextStyle(fontSize: 20, color: AppColor.colorNameEmail, fontWeight: FontWeight.w700))), - Obx(() => _buildEditMailboxButton(context, controller.currentSelectMode.value)), + Obx(() { + if (controller.isSearchActive()) { + return controller.listMailboxSearched.isNotEmpty + ? _buildEditMailboxButton(context, controller.isSelectionEnabled()) + : SizedBox(width: 25); + } else { + return _buildEditMailboxButton(context, controller.isSelectionEnabled()); + } + }), _buildAddNewFolderButton(context), ])), Divider(color: AppColor.colorDividerMailbox, height: 0.5, thickness: 0.2), @@ -103,15 +117,15 @@ class MailboxView extends GetWidget { icon: SvgPicture.asset(imagePaths.icAddNewFolder, width: 30, height: 30, fit: BoxFit.fill))); } - Widget _buildEditMailboxButton(BuildContext context, SelectMode selectMode) { + Widget _buildEditMailboxButton(BuildContext context, bool isSelectionEnabled) { return Material( borderRadius: BorderRadius.circular(20), color: Colors.transparent, child: TextButton( child: Text( - selectMode == SelectMode.INACTIVE ? AppLocalizations.of(context).edit : AppLocalizations.of(context).cancel, + !isSelectionEnabled ? AppLocalizations.of(context).edit : AppLocalizations.of(context).cancel, style: TextStyle(fontSize: 17, color: AppColor.colorTextButton)), - onPressed: () => selectMode == SelectMode.INACTIVE + onPressed: () => !isSelectionEnabled ? controller.enableSelectionMailbox() : controller.disableSelectionMailbox() ) @@ -283,7 +297,6 @@ class MailboxView extends GetWidget { ..addDecoration(BoxDecoration(borderRadius: BorderRadius.circular(12), color: AppColor.colorBgSearchBar)) ..addIconClearText(SvgPicture.asset(imagePaths.icClearTextSearch, width: 20, height: 20, fit: BoxFit.fill)) ..setHintText(AppLocalizations.of(context).hint_search_mailboxes) - ..addOnCancelSearchPressed(() => controller.disableSearch(context)) ..addOnClearTextSearchAction(() => controller.clearSearchText()) ..addOnTextChangeSearchAction((query) => controller.searchMailbox(query)) ..addOnSearchTextAction((query) => controller.searchMailbox(query))) @@ -324,8 +337,10 @@ class MailboxView extends GetWidget { imagePaths, listMailbox[index], isSearchActive: controller.isSearchActive(), + allSelectMode: controller.currentSelectMode.value, isLastElement: index == listMailbox.length - 1) - ..addOnOpenMailboxAction((mailbox) => controller.openMailbox(context, mailbox))) + ..addOnOpenMailboxAction((mailbox) => controller.openMailbox(context, mailbox)) + ..addOnSelectMailboxActionClick((mailbox) => controller.selectMailbox(context, mailbox))) .build())); } diff --git a/lib/features/mailbox/presentation/model/mailbox_node.dart b/lib/features/mailbox/presentation/model/mailbox_node.dart index e10717724..2cc30e541 100644 --- a/lib/features/mailbox/presentation/model/mailbox_node.dart +++ b/lib/features/mailbox/presentation/model/mailbox_node.dart @@ -135,12 +135,12 @@ extension MailboxNodeExtension on MailboxNode { ); } - MailboxNode toSelectedMailboxNode({required SelectMode selectMode, ExpandMode? newExpandMode}) { - return MailboxNode( - item, - childrenItems: childrenItems, - expandMode: newExpandMode ?? expandMode, - selectMode: selectMode, - ); + MailboxNode toSelectedMailboxNode({required SelectMode selectMode, ExpandMode? newExpandMode}) { + return MailboxNode( + item, + childrenItems: childrenItems, + expandMode: newExpandMode ?? expandMode, + selectMode: selectMode, + ); } } diff --git a/lib/features/mailbox/presentation/model/mailbox_tree.dart b/lib/features/mailbox/presentation/model/mailbox_tree.dart index ce89f2e87..116cc719d 100644 --- a/lib/features/mailbox/presentation/model/mailbox_tree.dart +++ b/lib/features/mailbox/presentation/model/mailbox_tree.dart @@ -27,6 +27,24 @@ class MailboxTree with EquatableMixin { return result; } + List getAllNodes(MailboxNode node){ + List listOfNodes = []; + _addAllNodes(node, listOfNodes); + return listOfNodes; + } + + void _addAllNodes(MailboxNode? node, List listOfNodes) { + if (node != null) { + listOfNodes.add(node); + List? childrenItems = node.childrenItems; + if (childrenItems != null) { + childrenItems.forEach((child) { + _addAllNodes(child, listOfNodes); + }); + } + } + } + @override List get props => [root]; } \ No newline at end of file diff --git a/lib/features/thread/presentation/thread_controller.dart b/lib/features/thread/presentation/thread_controller.dart index b03cc24db..c4996e903 100644 --- a/lib/features/thread/presentation/thread_controller.dart +++ b/lib/features/thread/presentation/thread_controller.dart @@ -448,7 +448,7 @@ class ThreadController extends BaseController { (CupertinoActionSheetBuilder(context) ..addTiles(actionTiles) ..addCancelButton(cancelButton)) - .build(); + .show(); } void openFilterMessagesForTablet(BuildContext context, RelativeRect? position, List popupMenuItems) async { diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index 5eab4c591..c7b10a96a 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -676,4 +676,30 @@ class AppLocalizations { 'Rename', name: 'rename'); } + + String get delete_mailboxes_successfully { + return Intl.message( + 'Delete mailboxes successfully', + name: 'delete_mailboxes_successfully'); + } + + String get delete_mailboxes_failure { + return Intl.message( + 'Delete mailboxes failure', + name: 'delete_mailboxes_failure'); + } + + String get delete_mailboxes { + return Intl.message( + 'Delete mailboxes', + name: 'delete_mailboxes'); + } + + String message_confirmation_dialog_delete_mailbox(String nameMailbox) { + return Intl.message( + '"$nameMailbox" mailbox and all of the sub-folders and messages it contains will be deleted and won\'t be able to recover. Do you want to continue to delete?', + name: 'message_confirmation_dialog_delete_mailbox', + args: [nameMailbox] + ); + } } \ No newline at end of file