diff --git a/lib/features/base/base_mailbox_controller.dart b/lib/features/base/base_mailbox_controller.dart index b71e66b60..570eaeca9 100644 --- a/lib/features/base/base_mailbox_controller.dart +++ b/lib/features/base/base_mailbox_controller.dart @@ -53,6 +53,10 @@ import 'package:tmail_ui_user/main/routes/route_navigation.dart'; typedef RenameMailboxActionCallback = void Function(PresentationMailbox mailbox, MailboxName newMailboxName); typedef MovingMailboxActionCallback = void Function(PresentationMailbox mailboxSelected, PresentationMailbox? destinationMailbox); +typedef OnMoveFolderContentActionCallback = void Function( + PresentationMailbox currentMailbox, + PresentationMailbox destinationMailbox, +); typedef DeleteMailboxActionCallback = void Function(PresentationMailbox mailbox); typedef AllowSubaddressingActionCallback = void Function(MailboxId, Map?>?, MailboxActions); @@ -637,4 +641,33 @@ abstract class BaseMailboxController extends BaseController triggerScrollWhenExpandFolder(newExpandMode, itemKey, scrollController); } } + + void moveFolderContentAction({ + required BuildContext context, + required AccountId accountId, + required Session session, + required PresentationMailbox mailboxSelected, + required OnMoveFolderContentActionCallback onMoveFolderContentAction, + }) async { + final arguments = DestinationPickerArguments( + accountId, + MailboxActions.moveFolderContent, + session, + mailboxIdSelected: mailboxSelected.id, + ); + + final destinationMailbox = PlatformInfo.isWeb + ? await DialogRouter.pushGeneralDialog( + routeName: AppRoutes.destinationPicker, + arguments: arguments, + ) + : await push(AppRoutes.destinationPicker, arguments: arguments); + if (destinationMailbox is PresentationMailbox) { + log('$runtimeType::moveFolderContentAction: DestinationMailbox is ${destinationMailbox.name?.name}'); + onMoveFolderContentAction( + mailboxSelected, + destinationMailbox, + ); + } + } } \ No newline at end of file diff --git a/lib/features/destination_picker/presentation/destination_picker_controller.dart b/lib/features/destination_picker/presentation/destination_picker_controller.dart index 6d80add26..1f67631d9 100644 --- a/lib/features/destination_picker/presentation/destination_picker_controller.dart +++ b/lib/features/destination_picker/presentation/destination_picker_controller.dart @@ -103,7 +103,9 @@ class DestinationPickerController extends BaseMailboxController { void handleSuccessViewState(Success success) async { super.handleSuccessViewState(success); if (success is GetAllMailboxSuccess) { - if (mailboxAction.value == MailboxActions.move && mailboxIdSelected != null) { + if ((mailboxAction.value == MailboxActions.move || + mailboxAction.value == MailboxActions.moveFolderContent) && + mailboxIdSelected != null) { await buildTree( success.mailboxList.listSubscribedMailboxesAndDefaultMailboxes, mailboxIdSelected: mailboxIdSelected); @@ -249,9 +251,11 @@ class DestinationPickerController extends BaseMailboxController { void searchMailbox(BuildContext context, String value) { searchQuery.value = SearchQuery(value); - final searchableMailboxList = mailboxAction.value == MailboxActions.moveEmail - ? allMailboxes - : allMailboxes.listPersonalMailboxes; + final searchableMailboxList = + mailboxAction.value == MailboxActions.moveEmail || + mailboxAction.value == MailboxActions.moveFolderContent + ? allMailboxes + : allMailboxes.listPersonalMailboxes; final mailboxListWithDisplayName = searchableMailboxList .map((mailbox) => mailbox.withDisplayName(mailbox.getDisplayName(context))) diff --git a/lib/features/destination_picker/presentation/destination_picker_view.dart b/lib/features/destination_picker/presentation/destination_picker_view.dart index 8094faeca..eec4ab2dc 100644 --- a/lib/features/destination_picker/presentation/destination_picker_view.dart +++ b/lib/features/destination_picker/presentation/destination_picker_view.dart @@ -296,8 +296,10 @@ class DestinationPickerView extends GetWidget } }), Obx(() { - if (controller.teamMailboxesIsNotEmpty - && controller.mailboxAction.value == MailboxActions.moveEmail) { + if (controller.teamMailboxesIsNotEmpty && + (controller.mailboxAction.value == MailboxActions.moveEmail || + controller.mailboxAction.value == + MailboxActions.moveFolderContent)) { return Column( mainAxisSize: MainAxisSize.min, children: [ diff --git a/lib/features/destination_picker/presentation/widgets/destination_picker_search_mailbox_item_builder.dart b/lib/features/destination_picker/presentation/widgets/destination_picker_search_mailbox_item_builder.dart index a7c8a9af3..417372053 100644 --- a/lib/features/destination_picker/presentation/widgets/destination_picker_search_mailbox_item_builder.dart +++ b/lib/features/destination_picker/presentation/widgets/destination_picker_search_mailbox_item_builder.dart @@ -158,6 +158,7 @@ class DestinationPickerSearchMailboxItemBuilder extends StatelessWidget { ( mailboxActions == MailboxActions.select || mailboxActions == MailboxActions.create || - mailboxActions == MailboxActions.moveEmail + mailboxActions == MailboxActions.moveEmail || + mailboxActions == MailboxActions.moveFolderContent ); } \ No newline at end of file diff --git a/lib/features/mailbox/domain/usecases/move_folder_content_interactor.dart b/lib/features/mailbox/domain/usecases/move_folder_content_interactor.dart index 860f2f293..1bfb7d7dd 100644 --- a/lib/features/mailbox/domain/usecases/move_folder_content_interactor.dart +++ b/lib/features/mailbox/domain/usecases/move_folder_content_interactor.dart @@ -8,14 +8,13 @@ import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:tmail_ui_user/features/mailbox/domain/model/move_folder_content_request.dart'; import 'package:tmail_ui_user/features/mailbox/domain/repository/mailbox_repository.dart'; import 'package:tmail_ui_user/features/mailbox/domain/state/move_folder_content_state.dart'; -import 'package:tmail_ui_user/features/mailbox/domain/state/move_mailbox_state.dart'; class MoveFolderContentInteractor { final MailboxRepository _mailboxRepository; MoveFolderContentInteractor(this._mailboxRepository); - Stream execute({ + Stream> execute({ required Session session, required AccountId accountId, required MoveFolderContentRequest request, @@ -23,6 +22,7 @@ class MoveFolderContentInteractor { }) async* { try { yield Right(MovingFolderContent()); + onProgressController?.add(Right(MovingFolderContent())); await _mailboxRepository.moveFolderContent( session: session, accountId: accountId, @@ -31,7 +31,7 @@ class MoveFolderContentInteractor { ); yield Right(MoveFolderContentSuccess(request)); } catch (e) { - yield Left(MoveMailboxFailure(e)); + yield Left(MoveFolderContentFailure(e)); } } } diff --git a/lib/features/mailbox/presentation/extensions/handle_move_folder_content_extension.dart b/lib/features/mailbox/presentation/extensions/handle_move_folder_content_extension.dart new file mode 100644 index 000000000..f6bb1fffe --- /dev/null +++ b/lib/features/mailbox/presentation/extensions/handle_move_folder_content_extension.dart @@ -0,0 +1,108 @@ +import 'package:core/presentation/state/success.dart'; +import 'package:dartz/dartz.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:model/extensions/presentation_mailbox_extension.dart'; +import 'package:model/mailbox/presentation_mailbox.dart'; +import 'package:tmail_ui_user/features/email/domain/model/move_action.dart'; +import 'package:tmail_ui_user/features/home/data/exceptions/session_exceptions.dart'; +import 'package:tmail_ui_user/features/mailbox/domain/model/move_folder_content_request.dart'; +import 'package:tmail_ui_user/features/mailbox/domain/state/move_folder_content_state.dart'; +import 'package:tmail_ui_user/features/mailbox/presentation/extensions/presentation_mailbox_extension.dart'; +import 'package:tmail_ui_user/features/mailbox/presentation/mailbox_controller.dart'; + +extension HandleMoveFolderContentExtension on MailboxController { + void performMoveFolderContent({ + required BuildContext context, + required PresentationMailbox mailboxSelected, + }) { + final accountId = mailboxDashBoardController.accountId.value; + final session = mailboxDashBoardController.sessionCurrent; + + if (accountId == null || session == null) { + consumeState( + Stream.value( + Left(MoveFolderContentFailure(NotFoundAccountIdException())), + ), + ); + return; + } + + moveFolderContentAction( + context: context, + accountId: accountId, + session: session, + mailboxSelected: mailboxSelected, + onMoveFolderContentAction: (currentMailbox, destinationMailbox) { + consumeState(mailboxActionReactor.moveFolderContent( + session: session, + accountId: accountId, + moveRequest: MoveFolderContentRequest( + moveAction: MoveAction.moving, + mailboxId: currentMailbox.id, + destinationMailboxId: destinationMailbox.id, + destinationMailboxDisplayName: + destinationMailbox.getDisplayName(context), + markAsRead: destinationMailbox.isSpam, + totalEmails: currentMailbox.countTotalEmails, + ), + onProgressController: + mailboxDashBoardController.progressStateController, + )); + }, + ); + } + + void handleMoveFolderContentSuccess(MoveFolderContentSuccess success) { + mailboxDashBoardController.syncViewStateMailboxActionProgress( + newState: Right(UIState.idle), + ); + final moveFolderRequest = success.request; + + if (moveFolderRequest.moveAction == MoveAction.moving) { + toastManager.showMessageSuccessWithAction( + success: success, + onActionCallback: () { + _undoMoveFolderContentAction( + newMoveRequest: MoveFolderContentRequest( + moveAction: MoveAction.undo, + mailboxId: moveFolderRequest.destinationMailboxId, + destinationMailboxId: moveFolderRequest.mailboxId, + destinationMailboxDisplayName: '', + totalEmails: moveFolderRequest.totalEmails, + ), + ); + }, + ); + } + } + + void _undoMoveFolderContentAction({ + required MoveFolderContentRequest newMoveRequest, + }) { + final accountId = mailboxDashBoardController.accountId.value; + final session = mailboxDashBoardController.sessionCurrent; + + if (accountId == null || session == null) { + consumeState( + Stream.value( + Left(MoveFolderContentFailure(NotFoundAccountIdException())), + ), + ); + return; + } + + consumeState(mailboxActionReactor.moveFolderContent( + session: session, + accountId: accountId, + moveRequest: newMoveRequest, + onProgressController: mailboxDashBoardController.progressStateController, + )); + } + + void handleMoveFolderContentFailure(MoveFolderContentFailure failure) { + mailboxDashBoardController.syncViewStateMailboxActionProgress( + newState: Right(UIState.idle), + ); + toastManager.showMessageFailure(failure); + } +} diff --git a/lib/features/mailbox/presentation/mailbox_bindings.dart b/lib/features/mailbox/presentation/mailbox_bindings.dart index 4af501999..152112a37 100644 --- a/lib/features/mailbox/presentation/mailbox_bindings.dart +++ b/lib/features/mailbox/presentation/mailbox_bindings.dart @@ -20,6 +20,7 @@ import 'package:tmail_ui_user/features/mailbox/domain/usecases/create_new_defaul 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/move_folder_content_interactor.dart'; import 'package:tmail_ui_user/features/mailbox/domain/usecases/move_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/rename_mailbox_interactor.dart'; @@ -60,6 +61,7 @@ class MailboxBindings extends BaseBindings { Get.find(), Get.find(), Get.find(), + Get.find(), Get.find(), Get.find(), Get.find(), @@ -111,6 +113,7 @@ class MailboxBindings extends BaseBindings { Get.lazyPut(() => SubscribeMultipleMailboxInteractor(Get.find())); Get.lazyPut(() => SubaddressingInteractor(Get.find())); Get.lazyPut(() => CreateDefaultMailboxInteractor(Get.find())); + Get.lazyPut(() => MoveFolderContentInteractor(Get.find())); } @override diff --git a/lib/features/mailbox/presentation/mailbox_controller.dart b/lib/features/mailbox/presentation/mailbox_controller.dart index ad2c2d557..e32019be0 100644 --- a/lib/features/mailbox/presentation/mailbox_controller.dart +++ b/lib/features/mailbox/presentation/mailbox_controller.dart @@ -49,6 +49,7 @@ import 'package:tmail_ui_user/features/mailbox/domain/state/create_new_mailbox_s 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/mark_as_mailbox_read_state.dart'; +import 'package:tmail_ui_user/features/mailbox/domain/state/move_folder_content_state.dart'; import 'package:tmail_ui_user/features/mailbox/domain/state/move_mailbox_state.dart'; import 'package:tmail_ui_user/features/mailbox/domain/state/refresh_all_mailboxes_state.dart'; import 'package:tmail_ui_user/features/mailbox/domain/state/refresh_changes_all_mailboxes_state.dart'; @@ -60,6 +61,7 @@ import 'package:tmail_ui_user/features/mailbox/domain/usecases/create_new_defaul 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/move_folder_content_interactor.dart'; import 'package:tmail_ui_user/features/mailbox/domain/usecases/move_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/rename_mailbox_interactor.dart'; @@ -67,6 +69,7 @@ import 'package:tmail_ui_user/features/mailbox/domain/usecases/subaddressing_int import 'package:tmail_ui_user/features/mailbox/domain/usecases/subscribe_mailbox_interactor.dart'; import 'package:tmail_ui_user/features/mailbox/domain/usecases/subscribe_multiple_mailbox_interactor.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/action/mailbox_ui_action.dart'; +import 'package:tmail_ui_user/features/mailbox/presentation/extensions/handle_move_folder_content_extension.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/extensions/presentation_mailbox_extension.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/mixin/mailbox_widget_mixin.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_actions.dart'; @@ -74,6 +77,7 @@ import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_catego import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_node.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/model/mailbox_tree_builder.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/model/open_mailbox_view_event.dart'; +import 'package:tmail_ui_user/features/mailbox/presentation/utils/mailbox_action_reactor.dart'; import 'package:tmail_ui_user/features/mailbox/presentation/utils/mailbox_utils.dart'; import 'package:tmail_ui_user/features/mailbox_creator/domain/usecases/verify_name_interactor.dart'; import 'package:tmail_ui_user/features/mailbox_creator/presentation/model/mailbox_creator_arguments.dart'; @@ -116,8 +120,10 @@ class MailboxController extends BaseMailboxController final SubscribeMultipleMailboxInteractor _subscribeMultipleMailboxInteractor; final SubaddressingInteractor _subaddressingInteractor; final CreateDefaultMailboxInteractor _createDefaultMailboxInteractor; + final MoveFolderContentInteractor _moveFolderContentInteractor; IOSSharingManager? _iosSharingManager; + late MailboxActionReactor mailboxActionReactor; final _activeScrollTop = RxBool(false); final _activeScrollBottom = RxBool(true); @@ -147,6 +153,7 @@ class MailboxController extends BaseMailboxController this._subscribeMultipleMailboxInteractor, this._subaddressingInteractor, this._createDefaultMailboxInteractor, + this._moveFolderContentInteractor, TreeBuilder treeBuilder, VerifyNameInteractor verifyNameInteractor, GetAllMailboxInteractor getAllMailboxInteractor, @@ -162,6 +169,9 @@ class MailboxController extends BaseMailboxController void onInit() { _registerObxStreamListener(); _initWebSocketQueueHandler(); + mailboxActionReactor = MailboxActionReactor( + _moveFolderContentInteractor, + ); super.onInit(); } @@ -186,7 +196,6 @@ class MailboxController extends BaseMailboxController @override void handleSuccessViewState(Success success) { - super.handleSuccessViewState(success); if (success is GetAllMailboxSuccess) { _handleGetAllMailboxSuccess(success); } else if (success is CreateNewMailboxSuccess) { @@ -209,12 +218,15 @@ class MailboxController extends BaseMailboxController handleSubAddressingSuccess(success); } else if (success is CreateDefaultMailboxAllSuccess) { _handleCreateDefaultFolderIfMissingSuccess(success); + } else if (success is MoveFolderContentSuccess) { + handleMoveFolderContentSuccess(success); + } else { + super.handleSuccessViewState(success); } } @override void handleFailureViewState(Failure failure) { - super.handleFailureViewState(failure); if (failure is CreateNewMailboxFailure) { _createNewMailboxFailure(failure); } else if (failure is RenameMailboxFailure) { @@ -223,6 +235,10 @@ class MailboxController extends BaseMailboxController _deleteMailboxFailure(failure); } else if (failure is SubaddressingFailure) { handleSubAddressingFailure(failure); + } else if (failure is MoveFolderContentFailure) { + handleMoveFolderContentFailure(failure); + } else { + super.handleFailureViewState(failure); } } @@ -1178,6 +1194,8 @@ class MailboxController extends BaseMailboxController mailboxDashBoardController.gotoEmailRecovery(); break; case MailboxActions.moveFolderContent: + performMoveFolderContent(context: context, mailboxSelected: mailbox); + mailboxDashBoardController.closeMailboxMenuDrawer(); break; default: break; diff --git a/lib/features/mailbox/presentation/model/mailbox_actions.dart b/lib/features/mailbox/presentation/model/mailbox_actions.dart index 38f56b02c..27e78c677 100644 --- a/lib/features/mailbox/presentation/model/mailbox_actions.dart +++ b/lib/features/mailbox/presentation/model/mailbox_actions.dart @@ -37,6 +37,7 @@ extension MailboxActionsExtension on MailboxActions { return AppLocalizations.of(context).selectParentFolder; case MailboxActions.moveEmail: case MailboxActions.move: + case MailboxActions.moveFolderContent: return AppLocalizations.of(context).moveTo; case MailboxActions.select: case MailboxActions.selectForRuleAction: diff --git a/lib/features/mailbox/presentation/utils/mailbox_action_reactor.dart b/lib/features/mailbox/presentation/utils/mailbox_action_reactor.dart new file mode 100644 index 000000000..4571b433e --- /dev/null +++ b/lib/features/mailbox/presentation/utils/mailbox_action_reactor.dart @@ -0,0 +1,29 @@ +import 'dart:async'; + +import 'package:core/presentation/state/failure.dart'; +import 'package:core/presentation/state/success.dart'; +import 'package:dartz/dartz.dart'; +import 'package:jmap_dart_client/jmap/account_id.dart'; +import 'package:jmap_dart_client/jmap/core/session/session.dart'; +import 'package:tmail_ui_user/features/mailbox/domain/model/move_folder_content_request.dart'; +import 'package:tmail_ui_user/features/mailbox/domain/usecases/move_folder_content_interactor.dart'; + +class MailboxActionReactor { + MailboxActionReactor(this._moveFolderContentInteractor); + + final MoveFolderContentInteractor _moveFolderContentInteractor; + + Stream> moveFolderContent({ + required Session session, + required AccountId accountId, + required MoveFolderContentRequest moveRequest, + StreamController>? onProgressController, + }) { + return _moveFolderContentInteractor.execute( + session: session, + accountId: accountId, + request: moveRequest, + onProgressController: onProgressController, + ); + } +} diff --git a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart index 17a8ea63e..61c1695e7 100644 --- a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart +++ b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart @@ -317,9 +317,9 @@ class MailboxDashBoardController extends ReloadableController Worker? searchInputFocusWorker; Worker? _downloadUIActionWorker; - final StreamController> _progressStateController = + final StreamController> progressStateController = StreamController>.broadcast(); - Stream> get progressState => _progressStateController.stream; + Stream> get progressState => progressStateController.stream; final StreamController _refreshActionEventController = StreamController.broadcast(); @@ -743,7 +743,8 @@ class MailboxDashBoardController extends ReloadableController void _registerStreamListener() { progressState.listen((state) { - viewStateMailboxActionProgress.value = state; + log('$runtimeType::_registerStreamListener: ViewStateMailboxActionProgress = ${state.runtimeType}'); + syncViewStateMailboxActionProgress(newState: state); }); _refreshActionEventController.stream @@ -1760,13 +1761,19 @@ class MailboxDashBoardController extends ReloadableController accountId, trashMailboxId, totalEmailsInTrash, - _progressStateController, + progressStateController, )); } } + void syncViewStateMailboxActionProgress({ + required Either newState, + }) { + viewStateMailboxActionProgress.value = newState; + } + void _emptyTrashFolderSuccess(EmptyTrashFolderSuccess success) { - viewStateMailboxActionProgress.value = Right(UIState.idle); + syncViewStateMailboxActionProgress(newState: Right(UIState.idle)); handleDeleteEmailsInMailbox( emailIds: success.emailIds, @@ -1902,11 +1909,11 @@ class MailboxDashBoardController extends ReloadableController mailboxId, mailboxDisplayName, totalEmailsUnread, - _progressStateController)); + progressStateController)); } void _markAsReadMailboxSuccess(Success success) { - viewStateMailboxActionProgress.value = Right(UIState.idle); + syncViewStateMailboxActionProgress(newState: Right(UIState.idle)); if (success is MarkAsMailboxReadAllSuccess) { if (currentContext != null && currentOverlayContext != null) { @@ -1926,7 +1933,7 @@ class MailboxDashBoardController extends ReloadableController } void _markAsReadMailboxFailure(MarkAsMailboxReadFailure failure) { - viewStateMailboxActionProgress.value = Right(UIState.idle); + syncViewStateMailboxActionProgress(newState: Right(UIState.idle)); if (currentOverlayContext != null && currentContext != null) { appToast.showToastErrorMessage( currentOverlayContext!, @@ -1938,7 +1945,7 @@ class MailboxDashBoardController extends ReloadableController } void _markAsReadMailboxAllFailure(MarkAsMailboxReadAllFailure failure) { - viewStateMailboxActionProgress.value = Right(UIState.idle); + syncViewStateMailboxActionProgress(newState: Right(UIState.idle)); if (currentOverlayContext != null && currentContext != null) { appToast.showToastErrorMessage( currentOverlayContext!, @@ -2694,13 +2701,13 @@ class MailboxDashBoardController extends ReloadableController accountId, spamFolderId, totalEmails, - _progressStateController, + progressStateController, )); } } void _emptySpamFolderSuccess(EmptySpamFolderSuccess success) { - viewStateMailboxActionProgress.value = Right(UIState.idle); + syncViewStateMailboxActionProgress(newState: Right(UIState.idle)); handleDeleteEmailsInMailbox( emailIds: success.emailIds, @@ -3108,13 +3115,13 @@ class MailboxDashBoardController extends ReloadableController } void _handleEmptySpamFolderFailure(EmptySpamFolderFailure failure) { - viewStateMailboxActionProgress.value = Right(UIState.idle); + syncViewStateMailboxActionProgress(newState: Right(UIState.idle)); toastManager.showMessageFailure(failure); } void _handleEmptyTrashFolderFailure(EmptyTrashFolderFailure failure) { - viewStateMailboxActionProgress.value = Right(UIState.idle); + syncViewStateMailboxActionProgress(newState: Right(UIState.idle)); toastManager.showMessageFailure(failure); } @@ -3325,7 +3332,7 @@ class MailboxDashBoardController extends ReloadableController _emailReceiveManager.closeEmailReceiveManagerStream(); _deepLinkDataStreamSubscription?.cancel(); } - _progressStateController.close(); + progressStateController.close(); _refreshActionEventController.close(); _notificationManager.closeStream(); _fcmService.closeStream(); diff --git a/lib/features/mailbox_dashboard/presentation/widgets/mark_mailbox_as_read_loading_banner.dart b/lib/features/mailbox_dashboard/presentation/widgets/mark_mailbox_as_read_loading_banner.dart index f8f166782..90f4a4581 100644 --- a/lib/features/mailbox_dashboard/presentation/widgets/mark_mailbox_as_read_loading_banner.dart +++ b/lib/features/mailbox_dashboard/presentation/widgets/mark_mailbox_as_read_loading_banner.dart @@ -5,6 +5,7 @@ import 'package:flutter/material.dart'; import 'package:tmail_ui_user/features/base/mixin/app_loader_mixin.dart'; import 'package:tmail_ui_user/features/mailbox/domain/state/clear_mailbox_state.dart'; import 'package:tmail_ui_user/features/mailbox/domain/state/mark_as_mailbox_read_state.dart'; +import 'package:tmail_ui_user/features/mailbox/domain/state/move_folder_content_state.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/styles/mark_mailbox_as_read_loading_banner_style.dart'; import 'package:tmail_ui_user/features/thread/domain/state/empty_spam_folder_state.dart'; @@ -29,6 +30,11 @@ class MarkMailboxAsReadLoadingBanner extends StatelessWidget with AppLoaderMixin return _buildProgressBanner(success.countRead, success.totalUnread); } else if (success is EmptyingFolderState) { return _buildProgressBanner(success.countEmailsDeleted, success.totalEmails); + } else if (success is MoveFolderContentProgressState) { + return _buildProgressBanner( + success.countEmailsCompleted, + success.totalEmails, + ); } else { return const SizedBox.shrink(); } diff --git a/lib/features/thread/presentation/thread_view.dart b/lib/features/thread/presentation/thread_view.dart index 35ca8832a..285c2f80b 100644 --- a/lib/features/thread/presentation/thread_view.dart +++ b/lib/features/thread/presentation/thread_view.dart @@ -18,6 +18,7 @@ import 'package:tmail_ui_user/features/email/presentation/model/context_item_ema import 'package:tmail_ui_user/features/email/presentation/model/popup_menu_item_email_action.dart'; import 'package:tmail_ui_user/features/mailbox/domain/state/clear_mailbox_state.dart'; import 'package:tmail_ui_user/features/mailbox/domain/state/mark_as_mailbox_read_state.dart'; +import 'package:tmail_ui_user/features/mailbox/domain/state/move_folder_content_state.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/domain/model/spam_report_state.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/handle_open_context_menu_extension.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/presentation/extensions/open_and_close_composer_extension.dart'; @@ -793,6 +794,7 @@ class _MailboxActionProgressBanner extends StatelessWidget with AppLoaderMixin { if (success is MarkAsMailboxReadLoading || success is EmptySpamFolderLoading || success is EmptyTrashFolderLoading || + success is MovingFolderContent || success is ClearingMailbox) { return Padding( padding: EdgeInsets.only( @@ -815,6 +817,12 @@ class _MailboxActionProgressBanner extends StatelessWidget with AppLoaderMixin { success.countEmailsDeleted, success.totalEmails, ); + } else if (success is MoveFolderContentProgressState) { + return _buildProgressBanner( + context, + success.countEmailsCompleted, + success.totalEmails, + ); } return const SizedBox.shrink(); }, diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index 083dc4899..9dd41e721 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -5057,5 +5057,11 @@ "type": "text", "placeholders_order": [], "placeholders": {} + }, + "moveFolderContentToastMessage": "Failed to move all emails from this folder to another folder.", + "@moveFolderContentToastMessage": { + "type": "text", + "placeholders_order": [], + "placeholders": {} } } \ No newline at end of file diff --git a/lib/main/bindings/core/core_bindings.dart b/lib/main/bindings/core/core_bindings.dart index 639af730e..b0cab9cbe 100644 --- a/lib/main/bindings/core/core_bindings.dart +++ b/lib/main/bindings/core/core_bindings.dart @@ -54,7 +54,7 @@ class CoreBindings extends Bindings { void _bindingToast() { Get.put(AppToast()); - Get.put(ToastManager(Get.find())); + Get.put(ToastManager(Get.find(), Get.find())); } void _bindingDeviceManager() { diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index 951bbad18..89158e39f 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -5352,4 +5352,11 @@ class AppLocalizations { name: 'moveFolderContent', ); } + + String get moveFolderContentToastMessage { + return Intl.message( + 'Failed to move all emails from this folder to another folder.', + name: 'moveFolderContentToastMessage', + ); + } } diff --git a/lib/main/utils/toast_manager.dart b/lib/main/utils/toast_manager.dart index 6fb0ac525..3d97c0996 100644 --- a/lib/main/utils/toast_manager.dart +++ b/lib/main/utils/toast_manager.dart @@ -2,12 +2,16 @@ import 'dart:io'; import 'package:core/domain/exceptions/file_exception.dart'; import 'package:core/domain/exceptions/web_session_exception.dart'; +import 'package:core/presentation/extensions/color_extension.dart'; +import 'package:core/presentation/resources/image_paths.dart'; import 'package:core/presentation/state/failure.dart'; import 'package:core/presentation/state/success.dart'; import 'package:core/presentation/utils/app_toast.dart'; import 'package:core/utils/app_logger.dart'; +import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:flutter_appauth_web/authorization_exception.dart'; +import 'package:flutter_svg/svg.dart'; import 'package:jmap_dart_client/jmap/core/error/method/error_method_response.dart'; import 'package:jmap_dart_client/jmap/core/error/method/exception/error_method_response_exception.dart'; import 'package:jmap_dart_client/jmap/core/error/set_error.dart'; @@ -27,6 +31,7 @@ import 'package:tmail_ui_user/features/login/data/network/oidc_error.dart'; import 'package:tmail_ui_user/features/login/domain/exceptions/authentication_exception.dart'; import 'package:tmail_ui_user/features/login/domain/exceptions/oauth_authorization_error.dart'; import 'package:tmail_ui_user/features/mailbox/domain/state/clear_mailbox_state.dart'; +import 'package:tmail_ui_user/features/mailbox/domain/state/move_folder_content_state.dart'; import 'package:tmail_ui_user/features/manage_account/domain/state/add_recipient_in_forwarding_state.dart'; import 'package:tmail_ui_user/features/manage_account/domain/state/create_new_rule_filter_state.dart'; import 'package:tmail_ui_user/features/manage_account/domain/state/delete_recipient_in_forwarding_state.dart'; @@ -45,8 +50,9 @@ import 'package:tmail_ui_user/main/routes/route_navigation.dart'; class ToastManager { final AppToast appToast; + final ImagePaths imagePaths; - ToastManager(this.appToast); + ToastManager(this.appToast, this.imagePaths); String getDefaultMessageByException( AppLocalizations appLocalizations, @@ -187,6 +193,9 @@ class ToastManager { } else if (failure is CalendarEventReplyFailure) { message = message ?? appLocalizations.eventReplyWasSentUnsuccessfully; + } else if (failure is MoveFolderContentFailure) { + message = message ?? + appLocalizations.moveFolderContentToastMessage; } log('ToastManager::showMessageFailure: Message: $message'); if (message?.trim().isNotEmpty == true) { @@ -256,4 +265,38 @@ class ToastManager { appToast.showToastSuccessMessage(overlayContext, message!); } } + + void showMessageSuccessWithAction({ + required Success success, + required VoidCallback onActionCallback, + }) { + final context = currentContext; + final overlayContext = currentOverlayContext; + if (context == null || overlayContext == null) { + logError('$runtimeType::showMessageSuccessWithAction: Context or OverlayContext is null'); + return; + } + + String? message; + final appLocalizations = AppLocalizations.of(context); + if (success is MoveFolderContentSuccess) { + message = appLocalizations.movedToFolder( + success.request.destinationMailboxDisplayName, + ); + } + log('$runtimeType::showMessageSuccessWithAction: Message: $message'); + if (message?.trim().isNotEmpty == true) { + appToast.showToastMessage( + overlayContext, + message!, + actionName: appLocalizations.undo, + onActionClick: onActionCallback, + leadingSVGIcon: imagePaths.icFolderMailbox, + leadingSVGIconColor: Colors.white, + backgroundColor: AppColor.toastSuccessBackgroundColor, + textColor: Colors.white, + actionIcon: SvgPicture.asset(imagePaths.icUndo), + ); + } + } } diff --git a/test/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller_test.dart b/test/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller_test.dart index bcd2152ad..0d5cebfed 100644 --- a/test/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller_test.dart +++ b/test/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller_test.dart @@ -49,6 +49,7 @@ import 'package:tmail_ui_user/features/mailbox/domain/usecases/create_new_mailbo 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/mark_as_mailbox_read_interactor.dart'; +import 'package:tmail_ui_user/features/mailbox/domain/usecases/move_folder_content_interactor.dart'; import 'package:tmail_ui_user/features/mailbox/domain/usecases/move_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/rename_mailbox_interactor.dart'; @@ -165,6 +166,7 @@ const fallbackGenerators = { MockSpec(), MockSpec(), MockSpec(), + MockSpec(), MockSpec(), MockSpec(), MockSpec(), @@ -280,6 +282,7 @@ void main() { final subscribeMultipleMailboxInteractor = MockSubscribeMultipleMailboxInteractor(); final subaddressingInteractor = MockSubaddressingInteractor(); final createDefaultMailboxInteractor = MockCreateDefaultMailboxInteractor(); + final moveFolderContentInteractor = MockMoveFolderContentInteractor(); final treeBuilder = MockTreeBuilder(); final verifyNameInteractor = MockVerifyNameInteractor(); final getAllMailboxInteractor = MockGetAllMailboxInteractor(); @@ -411,6 +414,7 @@ void main() { subscribeMultipleMailboxInteractor, subaddressingInteractor, createDefaultMailboxInteractor, + moveFolderContentInteractor, treeBuilder, verifyNameInteractor, getAllMailboxInteractor, @@ -647,6 +651,7 @@ void main() { subscribeMultipleMailboxInteractor, subaddressingInteractor, createDefaultMailboxInteractor, + moveFolderContentInteractor, treeBuilder, verifyNameInteractor, getAllMailboxInteractor, diff --git a/test/features/mailbox_dashboard/presentation/view/mailbox_dashboard_view_widget_test.dart b/test/features/mailbox_dashboard/presentation/view/mailbox_dashboard_view_widget_test.dart index 997c2a9a1..4177c2492 100644 --- a/test/features/mailbox_dashboard/presentation/view/mailbox_dashboard_view_widget_test.dart +++ b/test/features/mailbox_dashboard/presentation/view/mailbox_dashboard_view_widget_test.dart @@ -46,6 +46,7 @@ import 'package:tmail_ui_user/features/mailbox/domain/usecases/create_new_mailbo 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/mark_as_mailbox_read_interactor.dart'; +import 'package:tmail_ui_user/features/mailbox/domain/usecases/move_folder_content_interactor.dart'; import 'package:tmail_ui_user/features/mailbox/domain/usecases/move_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/rename_mailbox_interactor.dart'; @@ -166,6 +167,7 @@ const fallbackGenerators = { MockSpec(), MockSpec(), MockSpec(), + MockSpec(), MockSpec(), MockSpec(), MockSpec(), @@ -266,6 +268,7 @@ void main() { final subscribeMultipleMailboxInteractor = MockSubscribeMultipleMailboxInteractor(); final subaddressingInteractor = MockSubaddressingInteractor(); final createDefaultMailboxInteractor = MockCreateDefaultMailboxInteractor(); + final moveFolderContentInteractor = MockMoveFolderContentInteractor(); final treeBuilder = MockTreeBuilder(); final verifyNameInteractor = MockVerifyNameInteractor(); final getAllMailboxInteractor = MockGetAllMailboxInteractor(); @@ -400,6 +403,7 @@ void main() { subscribeMultipleMailboxInteractor, subaddressingInteractor, createDefaultMailboxInteractor, + moveFolderContentInteractor, treeBuilder, verifyNameInteractor, getAllMailboxInteractor,