diff --git a/lib/features/email/domain/model/mark_read_action.dart b/lib/features/email/domain/model/mark_read_action.dart new file mode 100644 index 0000000000..ab4b06b7cd --- /dev/null +++ b/lib/features/email/domain/model/mark_read_action.dart @@ -0,0 +1,5 @@ +enum MarkReadAction { + tap, + swipeOnThread, + undo +} \ No newline at end of file diff --git a/lib/features/email/domain/state/mark_as_email_read_state.dart b/lib/features/email/domain/state/mark_as_email_read_state.dart index d7cd511868..28e48586cb 100644 --- a/lib/features/email/domain/state/mark_as_email_read_state.dart +++ b/lib/features/email/domain/state/mark_as_email_read_state.dart @@ -4,14 +4,17 @@ import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:model/email/read_actions.dart'; import 'package:model/model.dart'; import 'package:tmail_ui_user/features/base/state/ui_action_state.dart'; +import 'package:tmail_ui_user/features/email/domain/model/mark_read_action.dart'; class MarkAsEmailReadSuccess extends UIActionState { final Email updatedEmail; final ReadActions readActions; + final MarkReadAction markReadAction; MarkAsEmailReadSuccess( this.updatedEmail, this.readActions, + this.markReadAction, { jmap.State? currentEmailState, jmap.State? currentMailboxState, @@ -19,7 +22,7 @@ class MarkAsEmailReadSuccess extends UIActionState { ) : super(currentEmailState, currentMailboxState); @override - List get props => [updatedEmail, readActions, ...super.props]; + List get props => [updatedEmail, readActions, markReadAction, ...super.props]; } class MarkAsEmailReadFailure extends FeatureFailure { diff --git a/lib/features/email/domain/usecases/mark_as_email_read_interactor.dart b/lib/features/email/domain/usecases/mark_as_email_read_interactor.dart index 4305181fb8..435f6064c5 100644 --- a/lib/features/email/domain/usecases/mark_as_email_read_interactor.dart +++ b/lib/features/email/domain/usecases/mark_as_email_read_interactor.dart @@ -4,6 +4,7 @@ import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:model/model.dart'; +import 'package:tmail_ui_user/features/email/domain/model/mark_read_action.dart'; import 'package:tmail_ui_user/features/email/domain/repository/email_repository.dart'; import 'package:tmail_ui_user/features/email/domain/state/mark_as_email_read_state.dart'; import 'package:tmail_ui_user/features/mailbox/domain/repository/mailbox_repository.dart'; @@ -14,7 +15,7 @@ class MarkAsEmailReadInteractor { MarkAsEmailReadInteractor(this._emailRepository, this._mailboxRepository); - Stream> execute(Session session, AccountId accountId, Email email, ReadActions readAction) async* { + Stream> execute(Session session, AccountId accountId, Email email, ReadActions readAction, MarkReadAction markReadAction) async* { try { final listState = await Future.wait([ _mailboxRepository.getMailboxState( session,accountId), @@ -30,6 +31,7 @@ class MarkAsEmailReadInteractor { yield Right(MarkAsEmailReadSuccess( updatedEmail, readAction, + markReadAction, currentEmailState: currentEmailState, currentMailboxState: currentMailboxState)); } else { diff --git a/lib/features/email/presentation/controller/single_email_controller.dart b/lib/features/email/presentation/controller/single_email_controller.dart index 348caf2954..2fdaec026f 100644 --- a/lib/features/email/presentation/controller/single_email_controller.dart +++ b/lib/features/email/presentation/controller/single_email_controller.dart @@ -30,6 +30,7 @@ import 'package:tmail_ui_user/features/destination_picker/presentation/model/des import 'package:tmail_ui_user/features/email/domain/extensions/list_attachments_extension.dart'; import 'package:tmail_ui_user/features/email/domain/model/detailed_email.dart'; import 'package:tmail_ui_user/features/email/domain/model/event_action.dart'; +import 'package:tmail_ui_user/features/email/domain/model/mark_read_action.dart'; import 'package:tmail_ui_user/features/email/domain/state/parse_calendar_event_state.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/parse_calendar_event_interactor.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/store_opened_email_interactor.dart'; @@ -237,7 +238,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin { } if (!selectedEmail.hasRead) { - markAsEmailRead(selectedEmail, ReadActions.markAsRead); + markAsEmailRead(selectedEmail, ReadActions.markAsRead, MarkReadAction.tap); } if (_identitySelected == null) { @@ -508,11 +509,11 @@ class SingleEmailController extends BaseController with AppLoaderMixin { } } - void markAsEmailRead(PresentationEmail presentationEmail, ReadActions readActions) async { + void markAsEmailRead(PresentationEmail presentationEmail, ReadActions readActions, MarkReadAction markReadAction) async { final accountId = mailboxDashBoardController.accountId.value; final session = mailboxDashBoardController.sessionCurrent; if (accountId != null && session != null) { - consumeState(_markAsEmailReadInteractor.execute(session, accountId, presentationEmail.toEmail(), readActions)); + consumeState(_markAsEmailReadInteractor.execute(session, accountId, presentationEmail.toEmail(), readActions, markReadAction)); } } @@ -925,7 +926,7 @@ class SingleEmailController extends BaseController with AppLoaderMixin { switch(actionType) { case EmailActionType.markAsUnread: popBack(); - markAsEmailRead(presentationEmail, ReadActions.markAsUnread); + markAsEmailRead(presentationEmail, ReadActions.markAsUnread, MarkReadAction.tap); break; case EmailActionType.markAsStarred: markAsStarEmail(presentationEmail, MarkStarAction.markStar); 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 e044de9ab4..c5321e638c 100644 --- a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart +++ b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart @@ -30,6 +30,8 @@ import 'package:tmail_ui_user/features/composer/domain/state/save_email_as_draft import 'package:tmail_ui_user/features/composer/domain/state/send_email_state.dart'; import 'package:tmail_ui_user/features/composer/domain/state/update_email_drafts_state.dart'; import 'package:tmail_ui_user/features/composer/domain/usecases/send_email_interactor.dart'; +import 'package:tmail_ui_user/features/email/domain/model/mark_read_action.dart'; +import 'package:tmail_ui_user/features/email/domain/state/mark_as_email_read_state.dart'; import 'package:tmail_ui_user/features/email/domain/state/store_sending_email_state.dart'; import 'package:tmail_ui_user/features/composer/presentation/composer_bindings.dart'; import 'package:tmail_ui_user/features/composer/presentation/extensions/email_action_type_extension.dart'; @@ -339,6 +341,8 @@ class MailboxDashBoardController extends ReloadableController { _handleUpdateSendingEmailSuccess(success); } else if (success is EmptySpamFolderSuccess) { _emptySpamFolderSuccess(success); + } else if (success is MarkAsEmailReadSuccess) { + _markAsReadEmailSuccess(success); } } @@ -663,13 +667,14 @@ class MailboxDashBoardController extends ReloadableController { } } - void markAsEmailRead(PresentationEmail presentationEmail, ReadActions readActions) async { + void markAsEmailRead(PresentationEmail presentationEmail, ReadActions readActions, MarkReadAction markReadAction) async { if (accountId.value != null && sessionCurrent != null) { consumeState(_markAsEmailReadInteractor.execute( sessionCurrent!, accountId.value!, presentationEmail.toEmail(), - readActions)); + readActions, + markReadAction)); } } @@ -721,6 +726,39 @@ class MailboxDashBoardController extends ReloadableController { } } + void _markAsReadEmailSuccess(Success success) { + ReadActions? readActions; + MarkReadAction? markReadAction; + PresentationEmail? presentationEmail; + + if (success is MarkAsEmailReadSuccess) { + readActions = success.readActions; + markReadAction = success.markReadAction; + presentationEmail = success.updatedEmail.toPresentationEmail(); + } + + if (readActions != null && currentContext != null && currentOverlayContext != null && markReadAction == MarkReadAction.swipeOnThread) { + final message = readActions == ReadActions.markAsUnread + ? AppLocalizations.of(currentContext!).markedSingleMessageToast(AppLocalizations.of(currentContext!).unread.toLowerCase()) + : AppLocalizations.of(currentContext!).markedSingleMessageToast(AppLocalizations.of(currentContext!).read.toLowerCase()); + + final undoAction = readActions == ReadActions.markAsUnread ? ReadActions.markAsRead : ReadActions.markAsUnread; + + _appToast.showToastMessage( + currentOverlayContext!, + message, + actionName: AppLocalizations.of(currentContext!).undo, + onActionClick: () { + markAsEmailRead(presentationEmail!, undoAction, MarkReadAction.undo); + }, + leadingSVGIcon: _imagePaths.icToastSuccessMessage, + backgroundColor: AppColor.toastSuccessBackgroundColor, + textColor: Colors.white, + actionIcon: SvgPicture.asset(_imagePaths.icUndo), + ); + } + } + void markAsStarSelectedMultipleEmail(List listPresentationEmail, MarkStarAction markStarAction) { final listEmail = listPresentationEmail .map((presentationEmail) => presentationEmail.toEmail()) diff --git a/lib/features/search/email/presentation/search_email_controller.dart b/lib/features/search/email/presentation/search_email_controller.dart index b7b7296064..e438c98340 100644 --- a/lib/features/search/email/presentation/search_email_controller.dart +++ b/lib/features/search/email/presentation/search_email_controller.dart @@ -31,6 +31,7 @@ import 'package:tmail_ui_user/features/base/base_controller.dart'; import 'package:tmail_ui_user/features/base/mixin/date_range_picker_mixin.dart'; import 'package:tmail_ui_user/features/contact/presentation/model/contact_arguments.dart'; import 'package:tmail_ui_user/features/destination_picker/presentation/model/destination_picker_arguments.dart'; +import 'package:tmail_ui_user/features/email/domain/model/mark_read_action.dart'; import 'package:tmail_ui_user/features/email/domain/state/delete_email_permanently_state.dart'; import 'package:tmail_ui_user/features/email/domain/state/delete_multiple_emails_permanently_state.dart'; import 'package:tmail_ui_user/features/email/domain/state/mark_as_email_read_state.dart'; @@ -673,10 +674,10 @@ class SearchEmailController extends BaseController selectEmail(context, selectedEmail); break; case EmailActionType.markAsRead: - markAsEmailRead(selectedEmail, ReadActions.markAsRead); + markAsEmailRead(selectedEmail, ReadActions.markAsRead, MarkReadAction.tap); break; case EmailActionType.markAsUnread: - markAsEmailRead(selectedEmail, ReadActions.markAsUnread); + markAsEmailRead(selectedEmail, ReadActions.markAsUnread, MarkReadAction.tap); break; case EmailActionType.markAsStarred: markAsStarEmail(selectedEmail, MarkStarAction.markStar); diff --git a/lib/features/thread/presentation/mixin/email_action_controller.dart b/lib/features/thread/presentation/mixin/email_action_controller.dart index faa25cfa4c..ce81b3c068 100644 --- a/lib/features/thread/presentation/mixin/email_action_controller.dart +++ b/lib/features/thread/presentation/mixin/email_action_controller.dart @@ -19,6 +19,7 @@ import 'package:model/extensions/presentation_mailbox_extension.dart'; import 'package:model/mailbox/presentation_mailbox.dart'; import 'package:pointer_interceptor/pointer_interceptor.dart'; import 'package:tmail_ui_user/features/destination_picker/presentation/model/destination_picker_arguments.dart'; +import 'package:tmail_ui_user/features/email/domain/model/mark_read_action.dart'; import 'package:tmail_ui_user/features/email/domain/model/move_action.dart'; import 'package:tmail_ui_user/features/email/domain/model/move_to_mailbox_request.dart'; import 'package:tmail_ui_user/features/email/presentation/model/composer_arguments.dart'; @@ -233,8 +234,8 @@ mixin EmailActionController { mailboxDashBoardController.deleteEmailPermanently(email); } - void markAsEmailRead(PresentationEmail presentationEmail, ReadActions readActions) async { - mailboxDashBoardController.markAsEmailRead(presentationEmail, readActions); + void markAsEmailRead(PresentationEmail presentationEmail, ReadActions readActions, MarkReadAction markReadAction) async { + mailboxDashBoardController.markAsEmailRead(presentationEmail, readActions, markReadAction); } void markAsStarEmail(PresentationEmail presentationEmail, MarkStarAction action) { diff --git a/lib/features/thread/presentation/thread_controller.dart b/lib/features/thread/presentation/thread_controller.dart index 9e9c6b9615..ace5fecd70 100644 --- a/lib/features/thread/presentation/thread_controller.dart +++ b/lib/features/thread/presentation/thread_controller.dart @@ -24,6 +24,7 @@ import 'package:tmail_ui_user/features/base/base_controller.dart'; import 'package:tmail_ui_user/features/composer/domain/state/save_email_as_drafts_state.dart'; import 'package:tmail_ui_user/features/composer/domain/state/send_email_state.dart'; import 'package:tmail_ui_user/features/composer/domain/state/update_email_drafts_state.dart'; +import 'package:tmail_ui_user/features/email/domain/model/mark_read_action.dart'; import 'package:tmail_ui_user/features/email/domain/state/delete_email_permanently_state.dart'; import 'package:tmail_ui_user/features/email/domain/state/delete_multiple_emails_permanently_state.dart'; import 'package:tmail_ui_user/features/email/domain/state/mark_as_email_read_state.dart'; @@ -882,10 +883,10 @@ class ThreadController extends BaseController with EmailActionController { selectEmail(context, selectedEmail); break; case EmailActionType.markAsRead: - markAsEmailRead(selectedEmail, ReadActions.markAsRead); + markAsEmailRead(selectedEmail, ReadActions.markAsRead, MarkReadAction.tap); break; case EmailActionType.markAsUnread: - markAsEmailRead(selectedEmail, ReadActions.markAsUnread); + markAsEmailRead(selectedEmail, ReadActions.markAsUnread, MarkReadAction.tap); break; case EmailActionType.markAsStarred: markAsStarEmail(selectedEmail, MarkStarAction.markStar); @@ -1064,4 +1065,24 @@ class ThreadController extends BaseController with EmailActionController { ); } } + + Future swipeEmailAction(BuildContext context, PresentationEmail email, DismissDirection direction) async { + if (direction == DismissDirection.startToEnd) { + ReadActions readActions = !email.hasRead ? ReadActions.markAsRead : ReadActions.markAsUnread; + markAsEmailRead(email, readActions, MarkReadAction.swipeOnThread); + } + return false; + } + + DismissDirection getSwipeDirection (bool isWebDesktop, SelectMode selectMode) { + if (isWebDesktop) { + return DismissDirection.none; + } + + if (selectMode == SelectMode.ACTIVE) { + return DismissDirection.none; + } + + return DismissDirection.horizontal; + } } \ No newline at end of file diff --git a/lib/features/thread/presentation/thread_view.dart b/lib/features/thread/presentation/thread_view.dart index d2bd5d89b3..bbf46ff070 100644 --- a/lib/features/thread/presentation/thread_view.dart +++ b/lib/features/thread/presentation/thread_view.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_portal/flutter_portal.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'package:get/get.dart'; +import 'package:jmap_dart_client/jmap/mail/email/email.dart'; import 'package:model/model.dart'; import 'package:tmail_ui_user/features/base/mixin/app_loader_mixin.dart'; import 'package:tmail_ui_user/features/base/widget/compose_floating_button.dart'; @@ -388,18 +389,60 @@ class ThreadView extends GetWidget final isShowingEmailContent = controller.mailboxDashBoardController.selectedEmail.value?.id == presentationEmail.id; final selectModeAll = controller.mailboxDashBoardController.currentSelectMode.value; - return (EmailTileBuilder( - context, - presentationEmail, - selectModeAll, - controller.searchQuery, - isShowingEmailContent, - mailboxContain: presentationEmail.mailboxContain, - isSearchEmailRunning: controller.searchController.isSearchEmailRunning - ) - ..addOnPressEmailActionClick((action, email) => _handleEmailActionClicked(context, email, action)) - ..addOnMoreActionClick((email, position) => _handleEmailContextMenuAction(context, email, position)) - ).build(); + return Dismissible( + key: ValueKey(presentationEmail.id), + direction: controller.getSwipeDirection(_responsiveUtils.isWebDesktop(context), selectModeAll), + background: Container( + color: AppColor.colorItemRecipientSelected, + child: Padding( + padding: const EdgeInsetsDirectional.only(start: 16), + child: Align( + alignment: AlignmentDirectional.centerStart, + child: Row( + children: [ + CircleAvatar( + backgroundColor: AppColor.colorSpamReportBannerBackground, + radius: 24, + child: !presentationEmail.hasRead + ? SvgPicture.asset( + _imagePaths.icMarkAsRead, + fit: BoxFit.fill, + ) + : SvgPicture.asset( + _imagePaths.icUnreadEmail, + fit: BoxFit.fill, + colorFilter: AppColor.primaryColor.asFilter(), + ), + ), + const SizedBox(width: 11), + Text( + !presentationEmail.hasRead + ? AppLocalizations.of(context).mark_as_read + : AppLocalizations.of(context).mark_as_unread, + style: const TextStyle( + fontSize: 15, + color: AppColor.primaryColor, + ), + ), + ], + ), + ), + ), + ), + confirmDismiss: (direction) => controller.swipeEmailAction(context, presentationEmail, direction), + child: (EmailTileBuilder( + context, + presentationEmail, + selectModeAll, + controller.searchQuery, + isShowingEmailContent, + mailboxContain: presentationEmail.mailboxContain, + isSearchEmailRunning: controller.searchController.isSearchEmailRunning + ) + ..addOnPressEmailActionClick((action, email) => _handleEmailActionClicked(context, email, action)) + ..addOnMoreActionClick((email, position) => _handleEmailContextMenuAction(context, email, position)) + ).build(), + ); } void _handleEmailActionClicked( diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index 2456e56c70..37e54217be 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2023-08-23T18:45:06.308548", + "@@last_modified": "2023-08-28T17:04:30.893394", "initializing_data": "Initializing data...", "@initializing_data": { "type": "text", @@ -3179,5 +3179,15 @@ "type": "text", "placeholders_order": [], "placeholders": {} + }, + "markedSingleMessageToast": "Message has been marked as {action}", + "@markedSingleMessageToast": { + "type": "text", + "placeholders_order": [ + "action" + ], + "placeholders": { + "action": {} + } } } \ No newline at end of file diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index dfbcc99856..f066530b7e 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -3280,4 +3280,12 @@ class AppLocalizations { name: 'enterSomeSuggestions', ); } + + String markedSingleMessageToast(String action) { + return Intl.message( + 'Message has been marked as $action', + name: 'markedSingleMessageToast', + args: [action] + ); + } } \ No newline at end of file