From 7367d7e1d23e436e43e46b84c7f2a79c1f6f997b Mon Sep 17 00:00:00 2001 From: dab246 Date: Mon, 9 Jan 2023 17:01:34 +0700 Subject: [PATCH] TF-1317 Show push notification on background or terminated app --- ...ate_authentication_account_interactor.dart | 10 +- .../mailbox_dashboard_controller.dart | 77 ++++++++--- .../listener/email_change_listener.dart | 61 +++++++-- .../local_notification_config.dart | 3 + .../local_notification_manager.dart | 6 +- .../presentation/utils/fcm_utils.dart | 14 +- .../presentation/thread_controller.dart | 127 +++++++++++------- .../thread/presentation/thread_view.dart | 11 +- lib/l10n/intl_messages.arb | 24 +++- lib/main/localizations/app_localizations.dart | 20 +++ model/lib/model.dart | 4 +- .../notification/notification_payload.dart | 31 +++++ 12 files changed, 292 insertions(+), 96 deletions(-) create mode 100644 model/lib/notification/notification_payload.dart diff --git a/lib/features/login/domain/usecases/update_authentication_account_interactor.dart b/lib/features/login/domain/usecases/update_authentication_account_interactor.dart index 18c663268..dc5a55d70 100644 --- a/lib/features/login/domain/usecases/update_authentication_account_interactor.dart +++ b/lib/features/login/domain/usecases/update_authentication_account_interactor.dart @@ -15,10 +15,12 @@ class UpdateAuthenticationAccountInteractor { try{ yield Right(UpdateAuthenticationAccountLoading()); final currentAccount = await _accountRepository.getCurrentAccount(); - await _accountRepository.setCurrentAccount(currentAccount.fromAccountId( - accountId: accountId, - apiUrl: apiUrl - )); + await _accountRepository.setCurrentAccount( + currentAccount.fromAccountId( + accountId: accountId, + apiUrl: apiUrl + ) + ); yield Right(UpdateAuthenticationAccountSuccess()); } catch(e) { yield Left(UpdateAuthenticationAccountFailure(e)); 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 bd6c082a6..f694d05b9 100644 --- a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart +++ b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart @@ -1,4 +1,5 @@ import 'dart:async'; +import 'dart:convert'; import 'package:core/core.dart'; import 'package:dartz/dartz.dart'; @@ -10,7 +11,6 @@ import 'package:get/get.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/capability/capability_identifier.dart'; import 'package:jmap_dart_client/jmap/core/capability/mail_capability.dart'; -import 'package:jmap_dart_client/jmap/core/id.dart'; import 'package:jmap_dart_client/jmap/core/session/session.dart'; import 'package:jmap_dart_client/jmap/core/unsigned_int.dart'; import 'package:jmap_dart_client/jmap/mail/email/email.dart'; @@ -101,6 +101,7 @@ import 'package:tmail_ui_user/main/routes/route_navigation.dart'; import 'package:tmail_ui_user/main/routes/route_utils.dart'; import 'package:tmail_ui_user/main/routes/router_arguments.dart'; import 'package:tmail_ui_user/main/utils/email_receive_manager.dart'; +import 'package:jmap_dart_client/jmap/core/state.dart' as jmap; class MailboxDashBoardController extends ReloadableController { @@ -387,16 +388,7 @@ class MailboxDashBoardController extends ReloadableController { _notificationManager.activatedNotificationClickedOnTerminate = true; final notificationResponse = await _notificationManager.getCurrentNotificationResponse(); log('MailboxDashBoardController::_handleClickLocalNotificationOnTerminated():payload: ${notificationResponse?.payload}'); - final emailIdFromPayload = notificationResponse?.payload; - final currentAccountId = accountId.value; - if (emailIdFromPayload?.isNotEmpty == true && currentAccountId != null) { - _getPresentationEmailFromEmailIdAction( - EmailId(Id(emailIdFromPayload!)), - currentAccountId - ); - } else { - dispatchRoute(DashboardRoutes.thread); - } + _handleMessageFromNotification(notificationResponse?.payload, onForeground: false); } void _getUserProfile() async { @@ -1428,21 +1420,62 @@ class MailboxDashBoardController extends ReloadableController { void _handleClickLocalNotificationOnForeground(NotificationResponse? response) { _notificationManager.activatedNotificationClickedOnTerminate = true; log('MailboxDashBoardController::_handleClickLocalNotificationOnForeground():payload: ${response?.payload}'); - final emailIdFromPayload = response?.payload; - final currentAccountId = accountId.value; - if (emailIdFromPayload?.isNotEmpty == true && currentAccountId != null) { - popAllRouteIfHave(); - dispatchRoute(DashboardRoutes.waiting); + _handleMessageFromNotification(response?.payload); + } - _getPresentationEmailFromEmailIdAction( - EmailId(Id(emailIdFromPayload!)), - currentAccountId - ); + void _handleMessageFromNotification(String? payload, {bool onForeground = true}) { + log('MailboxDashBoardController::_handleMessageFromNotification():payload: $payload'); + if (payload == null) { + dispatchRoute(DashboardRoutes.thread); + return; + } + + final payloadDecoded = jsonDecode(payload); + if (payloadDecoded is Map) { + final notificationPayload = NotificationPayload.fromJson(payloadDecoded); + log('MailboxDashBoardController::_handleMessageFromNotification():notificationPayload: $notificationPayload'); + + if (notificationPayload.emailId != null) { + _handleNotificationMessageFromEmailId(notificationPayload.emailId!, onForeground: onForeground); + } else if (notificationPayload.newState != null) { + _handleNotificationMessageFromNewState(notificationPayload.newState!, onForeground: onForeground); + } else { + dispatchRoute(DashboardRoutes.thread); + } } else { dispatchRoute(DashboardRoutes.thread); } } + void _handleNotificationMessageFromNewState(jmap.State newState, {bool onForeground = true}) { + if (onForeground) { + _openInboxMailbox(); + } + } + + void _handleNotificationMessageFromEmailId(EmailId emailId, {bool onForeground = true}) { + final currentAccountId = accountId.value; + if (currentAccountId != null) { + if (onForeground) { + _showWaitingView(); + } + _getPresentationEmailFromEmailIdAction(emailId, currentAccountId); + } else { + dispatchRoute(DashboardRoutes.thread); + } + } + + void _showWaitingView() { + popAllRouteIfHave(); + dispatchRoute( DashboardRoutes.waiting); + } + + void _openInboxMailbox() { + popAllRouteIfHave(); + dispatchMailboxUIAction(SelectMailboxDefaultAction()); + dispatchRoute(DashboardRoutes.thread); + } + void _getPresentationEmailFromEmailIdAction(EmailId emailId, AccountId accountId) { log('MailboxDashBoardController:_getPresentationEmailFromEmailIdAction:emailId: $emailId'); consumeState(_getEmailByIdInteractor.execute( @@ -1482,6 +1515,10 @@ class MailboxDashBoardController extends ReloadableController { } } + void updateEmailList(List newEmailList) { + emailsInCurrentMailbox.value = newEmailList; + } + @override void onClose() { _emailReceiveManager.closeEmailReceiveManagerStream(); diff --git a/lib/features/push_notification/presentation/listener/email_change_listener.dart b/lib/features/push_notification/presentation/listener/email_change_listener.dart index 6e2557a6f..0ba5def29 100644 --- a/lib/features/push_notification/presentation/listener/email_change_listener.dart +++ b/lib/features/push_notification/presentation/listener/email_change_listener.dart @@ -1,4 +1,6 @@ +import 'dart:io'; + import 'package:core/presentation/state/failure.dart'; import 'package:core/presentation/state/success.dart'; import 'package:core/utils/app_logger.dart'; @@ -6,6 +8,7 @@ import 'package:core/utils/build_utils.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/state.dart' as jmap; import 'package:model/email/presentation_email.dart'; +import 'package:model/notification/notification_payload.dart'; import 'package:tmail_ui_user/features/base/action/ui_action.dart'; import 'package:tmail_ui_user/features/email/domain/state/get_stored_state_email_state.dart'; import 'package:tmail_ui_user/features/email/domain/usecases/get_stored_email_state_interactor.dart'; @@ -20,8 +23,11 @@ import 'package:tmail_ui_user/features/push_notification/domain/usecases/store_e import 'package:tmail_ui_user/features/push_notification/domain/usecases/store_email_state_to_refresh_interactor.dart'; import 'package:tmail_ui_user/features/push_notification/presentation/action/fcm_action.dart'; import 'package:tmail_ui_user/features/push_notification/presentation/listener/change_listener.dart'; +import 'package:tmail_ui_user/features/push_notification/presentation/notification/local_notification_config.dart'; import 'package:tmail_ui_user/features/push_notification/presentation/notification/local_notification_manager.dart'; +import 'package:tmail_ui_user/features/push_notification/presentation/utils/fcm_utils.dart'; import 'package:tmail_ui_user/features/thread/domain/constants/thread_constants.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; import 'package:tmail_ui_user/main/routes/route_navigation.dart'; class EmailChangeListener extends ChangeListener { @@ -78,9 +84,23 @@ class EmailChangeListener extends ChangeListener { _newState = newState; _accountId = accountId; log('EmailChangeListener::_pushNotificationAction():newState: $newState'); - _getStoredEmailDeliveryState(); + + if (BuildUtils.isWeb) { + _storeEmailDeliveryStateAction(_newState!); + } else { + if (Platform.isAndroid) { + _getStoredEmailDeliveryState(); + } else if (Platform.isIOS) { + _storeEmailDeliveryStateAction(_newState!); + _showLocalNotificationForIOS(_newState!, _accountId!); + } else { + logError('EmailChangeListener::_pushNotificationAction(): NOT SUPPORTED PLATFORM'); + } + } } + + void _getStoredEmailDeliveryState() { if (_getStoredEmailDeliveryStateInteractor != null) { consumeState(_getStoredEmailDeliveryStateInteractor!.execute()); @@ -112,13 +132,30 @@ class EmailChangeListener extends ChangeListener { } } - void _showLocalNotification(PresentationEmail presentationEmail, jmap.State newState) { + void _showLocalNotification(PresentationEmail presentationEmail) { + final notificationPayload = NotificationPayload(emailId: presentationEmail.id); + log('EmailChangeListener::_showLocalNotification():notificationPayload: $notificationPayload'); LocalNotificationManager.instance.showPushNotification( id: presentationEmail.id.id.value, title: presentationEmail.subject ?? '', message: presentationEmail.preview, emailAddress: presentationEmail.from?.first, - payload: presentationEmail.id.id.value, + payload: notificationPayload.encodeToString, + ); + } + + void _showLocalNotificationForIOS(jmap.State newState, AccountId accountId) { + final notificationPayload = NotificationPayload(newState: newState); + log('EmailChangeListener::_showLocalNotificationForIOS():notificationPayload: $notificationPayload'); + LocalNotificationManager.instance.showPushNotification( + id: '${FcmUtils.instance.platformOS}-${accountId.id.value}', + title: currentContext != null + ? AppLocalizations.of(currentContext!).appTitlePushNotification + : LocalNotificationConfig.notificationTitle, + message: currentContext != null + ? AppLocalizations.of(currentContext!).youHaveNewMessages + : LocalNotificationConfig.notificationMessage, + payload: notificationPayload.encodeToString, ); } @@ -144,19 +181,21 @@ class EmailChangeListener extends ChangeListener { if (_newState != null) { _storeEmailDeliveryStateAction(_newState!); - if (!BuildUtils.isWeb) { - for (var presentationEmail in success.emailList) { - _showLocalNotification(presentationEmail, _newState!); - } + if (!BuildUtils.isWeb && Platform.isAndroid) { + _handleLocalPushNotification(success.emailList); } } - - if (!BuildUtils.isWeb) { - LocalNotificationManager.instance.groupPushNotification(); - } } } + void _handleLocalPushNotification(List emailList) { + for (var presentationEmail in emailList) { + _showLocalNotification(presentationEmail); + } + + LocalNotificationManager.instance.groupPushNotification(); + } + void _handleStoreEmailStateToRefreshAction(jmap.State newState) { log('EmailChangeListener::_handleStoreEmailStateToRefreshAction():newState: $newState'); if (_storeEmailStateToRefreshInteractor != null) { diff --git a/lib/features/push_notification/presentation/notification/local_notification_config.dart b/lib/features/push_notification/presentation/notification/local_notification_config.dart index e50957030..975979bf1 100644 --- a/lib/features/push_notification/presentation/notification/local_notification_config.dart +++ b/lib/features/push_notification/presentation/notification/local_notification_config.dart @@ -7,6 +7,8 @@ class LocalNotificationConfig { static const _channelId = 'team_mail_notification_channel_id'; static const _channelName = 'Team Mail notifications'; static const _channelDescription = 'Team Mail notifications'; + static const notificationTitle = 'Team Mail'; + static const notificationMessage = 'You have new messages'; static const iosInitializationSettings = DarwinInitializationSettings(); @@ -53,6 +55,7 @@ class LocalNotificationConfig { presentSound: true, presentAlert: true, presentBadge: true, + threadIdentifier: _channelId ), ); } diff --git a/lib/features/push_notification/presentation/notification/local_notification_manager.dart b/lib/features/push_notification/presentation/notification/local_notification_manager.dart index 794079997..3f187d066 100644 --- a/lib/features/push_notification/presentation/notification/local_notification_manager.dart +++ b/lib/features/push_notification/presentation/notification/local_notification_manager.dart @@ -8,6 +8,8 @@ import 'package:flutter_local_notifications/flutter_local_notifications.dart'; import 'package:jmap_dart_client/jmap/mail/email/email_address.dart'; import 'package:model/extensions/email_address_extension.dart'; import 'package:tmail_ui_user/features/push_notification/presentation/notification/local_notification_config.dart'; +import 'package:tmail_ui_user/main/localizations/app_localizations.dart'; +import 'package:tmail_ui_user/main/routes/route_navigation.dart'; class LocalNotificationManager { @@ -159,7 +161,9 @@ class LocalNotificationManager { if (activeNotifications != null && activeNotifications.isNotEmpty) { final inboxStyleInformation = InboxStyleInformation( [''], - summaryText: '${activeNotifications.length - 1} new emails'.addBlockTag('b'), + summaryText: currentContext != null + ? AppLocalizations.of(currentContext!).totalNewMessagePushNotification(activeNotifications.length - 1).addBlockTag('b') + : '${activeNotifications.length - 1} new emails'.addBlockTag('b'), htmlFormatSummaryText: true, ); diff --git a/lib/features/push_notification/presentation/utils/fcm_utils.dart b/lib/features/push_notification/presentation/utils/fcm_utils.dart index 603c474b2..c63ec4096 100644 --- a/lib/features/push_notification/presentation/utils/fcm_utils.dart +++ b/lib/features/push_notification/presentation/utils/fcm_utils.dart @@ -82,20 +82,20 @@ class FcmUtils { String get platformOS { var platformName = ''; if (BuildUtils.isWeb) { - platformName = "Web"; + platformName = 'Web'; } else { if (Platform.isAndroid) { - platformName = "Android"; + platformName = 'Android'; } else if (Platform.isIOS) { - platformName = "IOS"; + platformName = 'IOS'; } else if (Platform.isFuchsia) { - platformName = "Fuchsia"; + platformName = 'Fuchsia'; } else if (Platform.isLinux) { - platformName = "Linux"; + platformName = 'Linux'; } else if (Platform.isMacOS) { - platformName = "MacOS"; + platformName = 'MacOS'; } else if (Platform.isWindows) { - platformName = "Windows"; + platformName = 'Windows'; } } log('FcmUtils::platformOS():$platformName'); diff --git a/lib/features/thread/presentation/thread_controller.dart b/lib/features/thread/presentation/thread_controller.dart index 61f578bc7..f91b7a1ec 100644 --- a/lib/features/thread/presentation/thread_controller.dart +++ b/lib/features/thread/presentation/thread_controller.dart @@ -117,8 +117,6 @@ class ThreadController extends BaseController with EmailActionController { SearchQuery? get searchQuery => searchController.searchEmailFilter.value.text; - RxList get emailList => mailboxDashBoardController.emailsInCurrentMailbox; - ThreadController( this._getEmailsInMailboxInteractor, this._refreshChangesEmailsInMailboxInteractor, @@ -154,7 +152,7 @@ class ThreadController extends BaseController with EmailActionController { newState.fold( (failure) { if (failure is SearchEmailFailure) { - emailList.clear(); + mailboxDashBoardController.emailsInCurrentMailbox.clear(); } else if (failure is SearchMoreEmailFailure || failure is LoadMoreEmailsFailure) { _isLoadingMore = false; } else if (failure is GetEmailByIdFailure) { @@ -356,7 +354,7 @@ class ThreadController extends BaseController with EmailActionController { void _resetToOriginalValue() { dispatchState(Right(LoadingState())); - emailList.clear(); + mailboxDashBoardController.emailsInCurrentMailbox.clear(); canLoadMore = true; _isLoadingMore = false; cancelSelectEmail(); @@ -365,13 +363,13 @@ class ThreadController extends BaseController with EmailActionController { void _getAllEmailSuccess(GetAllEmailSuccess success) { _currentEmailState = success.currentEmailState; log('ThreadController::_getAllEmailSuccess():_currentEmailState: $_currentEmailState'); - emailList.value = success.emailList.syncPresentationEmail( + final newListEmail = success.emailList.syncPresentationEmail( mapMailboxById: mailboxDashBoardController.mapMailboxById, selectedMailbox: currentMailbox, searchQuery: searchController.searchQuery, isSearchEmailRunning: searchController.isSearchEmailRunning ); - + mailboxDashBoardController.updateEmailList(newListEmail); if (listEmailController.hasClients) { listEmailController.animateTo(0, duration: const Duration(milliseconds: 500), curve: Curves.fastOutSlowIn); } @@ -380,17 +378,18 @@ class ThreadController extends BaseController with EmailActionController { void _refreshChangesAllEmailSuccess(RefreshChangesAllEmailSuccess success) { _currentEmailState = success.currentEmailState; - final emailsBeforeChanges = emailList; + final emailsBeforeChanges = mailboxDashBoardController.emailsInCurrentMailbox; final emailsAfterChanges = success.emailList; final newListEmail = emailsAfterChanges.combine(emailsBeforeChanges); - emailList.value = newListEmail.syncPresentationEmail( + final emailListSynced = newListEmail.syncPresentationEmail( mapMailboxById: mailboxDashBoardController.mapMailboxById, selectedMailbox: currentMailbox, searchQuery: searchController.searchQuery, isSearchEmailRunning: searchController.isSearchEmailRunning ); + mailboxDashBoardController.updateEmailList(emailListSynced); - if (emailList.isEmpty) { + if (mailboxDashBoardController.emailsInCurrentMailbox.isEmpty) { refreshAllEmail(); } } @@ -410,29 +409,33 @@ class ThreadController extends BaseController with EmailActionController { } EmailFilterCondition _getFilterCondition({bool isLoadMore = false}) { + final lastEmail = mailboxDashBoardController.emailsInCurrentMailbox.isNotEmpty + ? mailboxDashBoardController.emailsInCurrentMailbox.last + : null; + final mailboxIdSelected = mailboxDashBoardController.selectedMailbox.value?.id; switch(mailboxDashBoardController.filterMessageOption.value) { case FilterMessageOption.all: return EmailFilterCondition( - inMailbox: mailboxDashBoardController.selectedMailbox.value?.id, - before: isLoadMore ? emailList.last.receivedAt : null + inMailbox: mailboxIdSelected, + before: isLoadMore ? lastEmail?.receivedAt : null ); case FilterMessageOption.unread: return EmailFilterCondition( - inMailbox: mailboxDashBoardController.selectedMailbox.value?.id, - notKeyword: KeyWordIdentifier.emailSeen.value, - before: isLoadMore ? emailList.last.receivedAt : null + inMailbox: mailboxIdSelected, + notKeyword: KeyWordIdentifier.emailSeen.value, + before: isLoadMore ? lastEmail?.receivedAt : null ); case FilterMessageOption.attachments: return EmailFilterCondition( - inMailbox: mailboxDashBoardController.selectedMailbox.value?.id, - hasAttachment: true, - before: isLoadMore ? emailList.last.receivedAt : null + inMailbox: mailboxIdSelected, + hasAttachment: true, + before: isLoadMore ? lastEmail?.receivedAt : null ); case FilterMessageOption.starred: return EmailFilterCondition( - inMailbox: mailboxDashBoardController.selectedMailbox.value?.id, - hasKeyword: KeyWordIdentifier.emailFlagged.value, - before: isLoadMore ? emailList.last.receivedAt : null + inMailbox: mailboxIdSelected, + hasKeyword: KeyWordIdentifier.emailFlagged.value, + before: isLoadMore ? lastEmail?.receivedAt : null ); } } @@ -443,20 +446,26 @@ class ThreadController extends BaseController with EmailActionController { cancelSelectEmail(); if (searchController.isSearchEmailRunning) { - final limit = emailList.isNotEmpty ? UnsignedInt(emailList.length) : ThreadConstants.defaultLimit; searchController.searchEmailFilter.value = _searchEmailFilter.clearBeforeDate(); - _searchEmail(limit: limit); + _searchEmail(limit: limitEmailFetched); } else { _getAllEmail(); } } + UnsignedInt get limitEmailFetched { + final emailsInCurrentMailbox = mailboxDashBoardController.emailsInCurrentMailbox; + final limit = emailsInCurrentMailbox.isNotEmpty + ? UnsignedInt(emailsInCurrentMailbox.length) + : ThreadConstants.defaultLimit; + return limit; + } + void _refreshEmailChanges({jmap.State? currentEmailState}) { log('ThreadController::_refreshEmailChanges(): currentEmailState: $currentEmailState'); if (searchController.isSearchEmailRunning) { - final limit = emailList.isNotEmpty ? UnsignedInt(emailList.length) : ThreadConstants.defaultLimit; searchController.searchEmailFilter.value = _searchEmailFilter.clearBeforeDate(); - _searchEmail(limit: limit); + _searchEmail(limit: limitEmailFetched); } else { final newEmailState = currentEmailState ?? _currentEmailState; log('ThreadController::_refreshEmailChanges(): newEmailState: $newEmailState'); @@ -487,7 +496,7 @@ class ThreadController extends BaseController with EmailActionController { filterOption: mailboxDashBoardController.filterMessageOption.value, filter: _getFilterCondition(isLoadMore: true), properties: ThreadConstants.propertiesDefault, - lastEmailId: emailList.last.id) + lastEmailId: mailboxDashBoardController.emailsInCurrentMailbox.last.id) )); } } @@ -497,7 +506,9 @@ class ThreadController extends BaseController with EmailActionController { } bool _notDuplicatedInCurrentList(PresentationEmail email) { - return emailList.isEmpty || !emailList.map((element) => element.id).contains(email.id); + final emailsInCurrentMailbox = mailboxDashBoardController.emailsInCurrentMailbox; + return emailsInCurrentMailbox.isEmpty || + !emailsInCurrentMailbox.map((element) => element.id).contains(email.id); } void _loadMoreEmailsSuccess(LoadMoreEmailsSuccess success) { @@ -513,7 +524,7 @@ class ThreadController extends BaseController with EmailActionController { isSearchEmailRunning: searchController.isSearchEmailRunning ); - emailList.addAll(appendableList); + mailboxDashBoardController.emailsInCurrentMailbox.addAll(appendableList); } else { canLoadMore = false; } @@ -527,8 +538,11 @@ class ThreadController extends BaseController with EmailActionController { } Tuple2 _getSelectionEmailsRange(PresentationEmail presentationEmailSelected) { - final emailSelectedIndex = emailList.indexWhere((e) => e.id == presentationEmailSelected.id); - final latestEmailSelectedOrUnselectedIndex = emailList.indexWhere((e) => e.id == latestEmailSelectedOrUnselected.value?.id); + final emailsInCurrentMailbox = mailboxDashBoardController.emailsInCurrentMailbox; + final emailSelectedIndex = emailsInCurrentMailbox + .indexWhere((e) => e.id == presentationEmailSelected.id); + final latestEmailSelectedOrUnselectedIndex = emailsInCurrentMailbox + .indexWhere((e) => e.id == latestEmailSelectedOrUnselected.value?.id); if (emailSelectedIndex > latestEmailSelectedOrUnselectedIndex) { return Tuple2(latestEmailSelectedOrUnselectedIndex, emailSelectedIndex); } else { @@ -537,16 +551,22 @@ class ThreadController extends BaseController with EmailActionController { } bool _checkAllowMakeRangeEmailsSelected(Tuple2 selectionEmailsRange) { + final emailsInCurrentMailbox = mailboxDashBoardController.emailsInCurrentMailbox; return latestEmailSelectedOrUnselected.value?.selectMode == SelectMode.ACTIVE && - !emailList.sublist(selectionEmailsRange.value1, selectionEmailsRange.value2).every((e) => e.selectMode == SelectMode.ACTIVE) || + !emailsInCurrentMailbox.sublist(selectionEmailsRange.value1, selectionEmailsRange.value2).every((e) => e.selectMode == SelectMode.ACTIVE) || latestEmailSelectedOrUnselected.value?.selectMode == SelectMode.INACTIVE && - emailList.sublist(selectionEmailsRange.value1, selectionEmailsRange.value2).every((e) => e.selectMode == SelectMode.INACTIVE); + emailsInCurrentMailbox.sublist(selectionEmailsRange.value1, selectionEmailsRange.value2).every((e) => e.selectMode == SelectMode.INACTIVE); } void _applySelectModeToRangeEmails(Tuple2 selectionEmailsRange, SelectMode selectMode) { - emailList.value = emailList.asMap().map((index, email) { - return MapEntry(index, index >= selectionEmailsRange.value1 && index <= selectionEmailsRange.value2 ? email.toSelectedEmail(selectMode: selectMode) : email); - }).values.toList(); + final newEmailList = mailboxDashBoardController.emailsInCurrentMailbox + .asMap() + .map((index, email) { + return MapEntry(index, index >= selectionEmailsRange.value1 && index <= selectionEmailsRange.value2 ? email.toSelectedEmail(selectMode: selectMode) : email); + }) + .values + .toList(); + mailboxDashBoardController.updateEmailList(newEmailList); } void _rangeSelectionEmailsAction(PresentationEmail presentationEmailSelected) { @@ -560,14 +580,19 @@ class ThreadController extends BaseController with EmailActionController { } void selectEmail(BuildContext context, PresentationEmail presentationEmailSelected) { + final emailsInCurrentMailbox = mailboxDashBoardController.emailsInCurrentMailbox; + if (_rangeSelectionMode && latestEmailSelectedOrUnselected.value != null && latestEmailSelectedOrUnselected.value?.id != presentationEmailSelected.id) { _rangeSelectionEmailsAction(presentationEmailSelected); } else { - emailList.value = emailList + final newEmailList = emailsInCurrentMailbox .map((email) => email.id == presentationEmailSelected.id ? email.toggleSelect() : email) .toList(); + mailboxDashBoardController.updateEmailList(newEmailList); } - latestEmailSelectedOrUnselected.value = emailList.firstWhereOrNull((e) => e.id == presentationEmailSelected.id); + + latestEmailSelectedOrUnselected.value = emailsInCurrentMailbox + .firstWhereOrNull((e) => e.id == presentationEmailSelected.id); focusNodeKeyBoard.requestFocus(); if (_isUnSelectedAll()) { mailboxDashBoardController.currentSelectMode.value = SelectMode.INACTIVE; @@ -585,19 +610,27 @@ class ThreadController extends BaseController with EmailActionController { } void setSelectAllEmailAction() { - emailList.value = emailList.map((email) => email.toSelectedEmail(selectMode: SelectMode.ACTIVE)).toList(); + final newEmailList = mailboxDashBoardController.emailsInCurrentMailbox + .map((email) => email.toSelectedEmail(selectMode: SelectMode.ACTIVE)) + .toList(); + mailboxDashBoardController.updateEmailList(newEmailList); mailboxDashBoardController.currentSelectMode.value = SelectMode.ACTIVE; mailboxDashBoardController.listEmailSelected.value = listEmailSelected; } - List get listEmailSelected => emailList.listEmailSelected; + List get listEmailSelected => + mailboxDashBoardController.emailsInCurrentMailbox.listEmailSelected; bool _isUnSelectedAll() { - return emailList.every((email) => email.selectMode == SelectMode.INACTIVE); + return mailboxDashBoardController.emailsInCurrentMailbox + .every((email) => email.selectMode == SelectMode.INACTIVE); } void cancelSelectEmail() { - emailList.value = emailList.map((email) => email.toSelectedEmail(selectMode: SelectMode.INACTIVE)).toList(); + final newEmailList = mailboxDashBoardController.emailsInCurrentMailbox + .map((email) => email.toSelectedEmail(selectMode: SelectMode.INACTIVE)) + .toList(); + mailboxDashBoardController.updateEmailList(newEmailList); mailboxDashBoardController.currentSelectMode.value = SelectMode.INACTIVE; mailboxDashBoardController.listEmailSelected.clear(); } @@ -673,28 +706,30 @@ class ThreadController extends BaseController with EmailActionController { .map((email) => email.toSearchPresentationEmail(mailboxDashBoardController.mapMailboxById)) .toList(); - final emailsSearchBeforeChanges = emailList; + final emailsSearchBeforeChanges = mailboxDashBoardController.emailsInCurrentMailbox; final emailsSearchAfterChanges = resultEmailSearchList; final newListEmailSearch = emailsSearchAfterChanges.combine(emailsSearchBeforeChanges); - emailList.value = newListEmailSearch.syncPresentationEmail( + final newEmailListSynced = newListEmailSearch.syncPresentationEmail( mapMailboxById: mailboxDashBoardController.mapMailboxById, selectedMailbox: currentMailbox, searchQuery: searchController.searchQuery, isSearchEmailRunning: searchController.isSearchEmailRunning ); + mailboxDashBoardController.updateEmailList(newEmailListSynced); searchController.autoFocus.value = true; } void searchMoreEmails() { if (canSearchMore && _accountId != null) { - searchController.updateFilterEmail(before: emailList.last.receivedAt); + final lastEmail = mailboxDashBoardController.emailsInCurrentMailbox.last; + searchController.updateFilterEmail(before: lastEmail.receivedAt); consumeState(_searchMoreEmailInteractor.execute( _accountId!, limit: ThreadConstants.defaultLimit, sort: _sortOrder, filter: searchController.searchEmailFilter.value.mappingToEmailFilterCondition(), properties: ThreadConstants.propertiesDefault, - lastEmailId: emailList.last.id + lastEmailId: lastEmail.id )); } } @@ -703,7 +738,7 @@ class ThreadController extends BaseController with EmailActionController { if (success.emailList.isNotEmpty) { final resultEmailSearchList = success.emailList .map((email) => email.toSearchPresentationEmail(mailboxDashBoardController.mapMailboxById)) - .where((email) => !emailList.contains(email)) + .where((email) => !mailboxDashBoardController.emailsInCurrentMailbox.contains(email)) .toList() .syncPresentationEmail( mapMailboxById: mailboxDashBoardController.mapMailboxById, @@ -711,7 +746,7 @@ class ThreadController extends BaseController with EmailActionController { searchQuery: searchController.searchQuery, isSearchEmailRunning: searchController.isSearchEmailRunning ); - emailList.addAll(resultEmailSearchList); + mailboxDashBoardController.emailsInCurrentMailbox.addAll(resultEmailSearchList); } else { canSearchMore = false; } diff --git a/lib/features/thread/presentation/thread_view.dart b/lib/features/thread/presentation/thread_view.dart index fa084421f..e010891dd 100644 --- a/lib/features/thread/presentation/thread_view.dart +++ b/lib/features/thread/presentation/thread_view.dart @@ -122,7 +122,7 @@ class ThreadView extends GetWidget return Obx(() { if ((!BuildUtils.isWeb || (BuildUtils.isWeb && controller.isSelectionEnabled() && controller.isSearchActive() && !_responsiveUtils.isDesktop(context))) - && controller.emailList.listEmailSelected.isNotEmpty) { + && controller.mailboxDashBoardController.emailsInCurrentMailbox.listEmailSelected.isNotEmpty) { return Column(children: [ const Divider(color: AppColor.lineItemListColor, height: 1, thickness: 0.2), Padding( @@ -131,7 +131,7 @@ class ThreadView extends GetWidget context, _imagePaths, _responsiveUtils, - controller.emailList.listEmailSelected, + controller.mailboxDashBoardController.emailsInCurrentMailbox.listEmailSelected, controller.mailboxDashBoardController.selectedMailbox.value) ..addOnPressEmailSelectionActionClick((actionType, selectionEmail) => controller.pressEmailSelectionAction(context, actionType, selectionEmail))) @@ -147,7 +147,7 @@ class ThreadView extends GetWidget return Obx(() { return AppBarThreadWidgetBuilder( controller.currentMailbox, - controller.emailList.listEmailSelected, + controller.mailboxDashBoardController.emailsInCurrentMailbox.listEmailSelected, controller.mailboxDashBoardController.currentSelectMode.value, controller.mailboxDashBoardController.filterMessageOption.value, onOpenMailboxMenuActionClick: controller.openMailboxLeftMenu, @@ -287,9 +287,10 @@ class ThreadView extends GetWidget padding: EdgeInsets.zero, color: Colors.white, child: Obx(() { + log('ThreadView::_buildListEmail():'); return Visibility( visible: controller.openingEmail.isFalse, - child: _buildResultListEmail(context, controller.emailList)); + child: _buildResultListEmail(context, controller.mailboxDashBoardController.emailsInCurrentMailbox)); }) ); } @@ -439,7 +440,7 @@ class ThreadView extends GetWidget bool supportEmptyTrash(BuildContext context) { return controller.isMailboxTrash - && controller.emailList.isNotEmpty + && controller.mailboxDashBoardController.emailsInCurrentMailbox.isNotEmpty && !controller.isSearchActive() && !_responsiveUtils.isWebDesktop(context); } diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index c220ad6f6..0b940a8ce 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2022-12-28T14:09:51.971658", + "@@last_modified": "2023-01-09T16:48:24.908944", "initializing_data": "Initializing data...", "@initializing_data": { "type": "text", @@ -2551,5 +2551,27 @@ "type": "text", "placeholders_order": [], "placeholders": {} + }, + "youHaveNewMessages": "You have new messages", + "@youHaveNewMessages": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "appTitlePushNotification": "Team Mail", + "@appTitlePushNotification": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "totalNewMessagePushNotification": "{count} new emails", + "@totalNewMessagePushNotification": { + "type": "text", + "placeholders_order": [ + "count" + ], + "placeholders": { + "count": {} + } } } \ No newline at end of file diff --git a/lib/main/localizations/app_localizations.dart b/lib/main/localizations/app_localizations.dart index 235984764..823d6f52f 100644 --- a/lib/main/localizations/app_localizations.dart +++ b/lib/main/localizations/app_localizations.dart @@ -2620,4 +2620,24 @@ class AppLocalizations { name: 'regards', ); } + + String get youHaveNewMessages { + return Intl.message( + 'You have new messages', + name: 'youHaveNewMessages', + ); + } + + String get appTitlePushNotification { + return Intl.message( + 'Team Mail', + name: 'appTitlePushNotification'); + } + + String totalNewMessagePushNotification(int count) { + return Intl.message( + '$count new emails', + name: 'totalNewMessagePushNotification', + args: [count]); + } } \ No newline at end of file diff --git a/model/lib/model.dart b/model/lib/model.dart index d6dd6e2be..edd744e7b 100644 --- a/model/lib/model.dart +++ b/model/lib/model.dart @@ -74,4 +74,6 @@ export 'upload/file_info.dart'; export 'upload/upload_response.dart'; // User export 'user/user_profile.dart'; -export 'user/user_profile_response.dart'; \ No newline at end of file +export 'user/user_profile_response.dart'; +// Notification +export 'notification/notification_payload.dart'; \ No newline at end of file diff --git a/model/lib/notification/notification_payload.dart b/model/lib/notification/notification_payload.dart new file mode 100644 index 000000000..8d2e138de --- /dev/null +++ b/model/lib/notification/notification_payload.dart @@ -0,0 +1,31 @@ + +import 'dart:convert'; + +import 'package:equatable/equatable.dart'; +import 'package:jmap_dart_client/http/converter/email_id_nullable_converter.dart'; +import 'package:jmap_dart_client/http/converter/state_nullable_converter.dart'; +import 'package:jmap_dart_client/jmap/core/state.dart' as jmap; +import 'package:jmap_dart_client/jmap/mail/email/email.dart'; +import 'package:json_annotation/json_annotation.dart'; + +part 'notification_payload.g.dart'; + +@EmailIdNullableConverter() +@StateNullableConverter() +@JsonSerializable(explicitToJson: true, includeIfNull: false) +class NotificationPayload with EquatableMixin { + + final EmailId? emailId; + final jmap.State? newState; + + NotificationPayload({this.emailId, this.newState}); + + factory NotificationPayload.fromJson(Map json) => _$NotificationPayloadFromJson(json); + + Map toJson() => _$NotificationPayloadToJson(this); + + String get encodeToString => jsonEncode(toJson()); + + @override + List get props => [emailId, newState]; +} \ No newline at end of file