From 6ab650722f5ca58f234c2bec65fd77c3833fab00 Mon Sep 17 00:00:00 2001 From: dab246 Date: Mon, 15 Jul 2024 16:49:13 +0700 Subject: [PATCH] TF-2871 Block notification on folders `Sent`, `Outbox`, `Drafts`, `Spam` and `Trash` --- ios/Podfile.lock | 2 +- ios/TwakeCore/Jmap/JmapClient.swift | 18 ++++- ios/TwakeCore/Jmap/Model/Email/Email.swift | 10 +++ .../Model/KeychainSharingSession.swift | 7 +- ios/TwakeMailNSE/NotificationService.swift | 75 ++++++++++++------- .../mailbox_dashboard_controller.dart | 10 ++- .../keychain_sharing_session_extension.dart | 6 +- .../keychain/keychain_sharing_session.dart | 6 ++ .../bindings/network/network_bindings.dart | 5 +- lib/main/utils/ios_sharing_manager.dart | 71 +++++++++++++++++- model/lib/extensions/mailbox_extension.dart | 12 +++ 11 files changed, 182 insertions(+), 40 deletions(-) diff --git a/ios/Podfile.lock b/ios/Podfile.lock index fd2a6e5260..42d69184a5 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -323,7 +323,7 @@ SPEC CHECKSUMS: FirebaseInstallations: 558b1da7d65afeb996fd5c814332f013234ece4e FirebaseMessaging: e345b219fd15d325f0cf2fef28cb8ce00d851b3f fk_user_agent: 1f47ec39291e8372b1d692b50084b0d54103c545 - Flutter: f04841e97a9d0b0a8025694d0796dd46242b2854 + Flutter: e0871f40cf51350855a761d2e70bf5af5b9b5de7 flutter_appauth: 1ce438877bc111c5d8f42da47729909290624886 flutter_downloader: b7301ae057deadd4b1650dc7c05375f10ff12c39 flutter_image_compress_common: ec1d45c362c9d30a3f6a0426c297f47c52007e3e diff --git a/ios/TwakeCore/Jmap/JmapClient.swift b/ios/TwakeCore/Jmap/JmapClient.swift index b7dd4edb86..b2c9173b9e 100644 --- a/ios/TwakeCore/Jmap/JmapClient.swift +++ b/ios/TwakeCore/Jmap/JmapClient.swift @@ -68,6 +68,11 @@ class JmapClient { accountId: String, onComplete: @escaping ([Email], [Error]) -> Void) { guard hasMoreChanges, let sinceState = currentSinceState else { + if !self.totalListEmails.isEmpty { + let sortedListEmails = self.sortListEmails(currentListEmails: self.totalListEmails) + self.totalListEmails = sortedListEmails + } + return onComplete(self.totalListEmails, self.listErrors) } @@ -85,8 +90,7 @@ class JmapClient { if let response = data.parsing(methodName: JmapConstants.EMAIL_GET_METHOD_NAME, methodCallId: "c1") { if let listEmail = response.list, !listEmail.isEmpty { - let sortedListEmails = self.sortListEmails(currentListEmails: listEmail) - self.totalListEmails.append(contentsOf: sortedListEmails) + self.totalListEmails.append(contentsOf: listEmail) } self.hasMoreChanges = response.hasMoreChanges ?? false self.currentSinceState = response.newState @@ -97,6 +101,11 @@ class JmapClient { self.hasMoreChanges = false self.currentSinceState = nil + if !self.totalListEmails.isEmpty { + let sortedListEmails = self.sortListEmails(currentListEmails: self.totalListEmails) + self.totalListEmails = sortedListEmails + } + onComplete(self.totalListEmails, self.listErrors) } }, @@ -105,6 +114,11 @@ class JmapClient { self.hasMoreChanges = false self.currentSinceState = nil + if !self.totalListEmails.isEmpty { + let sortedListEmails = self.sortListEmails(currentListEmails: self.totalListEmails) + self.totalListEmails = sortedListEmails + } + onComplete(self.totalListEmails, self.listErrors) } ) diff --git a/ios/TwakeCore/Jmap/Model/Email/Email.swift b/ios/TwakeCore/Jmap/Model/Email/Email.swift index b546c41873..2799ace894 100644 --- a/ios/TwakeCore/Jmap/Model/Email/Email.swift +++ b/ios/TwakeCore/Jmap/Model/Email/Email.swift @@ -6,6 +6,7 @@ struct Email: Codable { let preview: String? let from: [EmailAddress]? let receivedAt: String? + let mailboxIds: [String: Bool]? func getSenderName() -> String? { if (from == nil || from?.isEmpty == true) { @@ -13,4 +14,13 @@ struct Email: Codable { } return from?.first?.name ?? from?.first?.email } + + func listMailboxIds() -> [String] { + if let mailboxIds = mailboxIds { + let filteredIds = mailboxIds.filter { $0.value }.map { $0.key } + return filteredIds + } else { + return [] + } + } } diff --git a/ios/TwakeMailNSE/Model/KeychainSharingSession.swift b/ios/TwakeMailNSE/Model/KeychainSharingSession.swift index 4567d9cc5d..4ac23ce332 100644 --- a/ios/TwakeMailNSE/Model/KeychainSharingSession.swift +++ b/ios/TwakeMailNSE/Model/KeychainSharingSession.swift @@ -11,6 +11,7 @@ struct KeychainSharingSession: Codable { let basicAuth: String? let tokenEndpoint: String? let oidcScopes: [String]? + let mailboxIdsBlockNotification: [String]? } extension KeychainSharingSession { @@ -25,7 +26,8 @@ extension KeychainSharingSession { tokenOIDC: self.tokenOIDC, basicAuth: self.basicAuth, tokenEndpoint: self.tokenEndpoint, - oidcScopes: self.oidcScopes + oidcScopes: self.oidcScopes, + mailboxIdsBlockNotification: self.mailboxIdsBlockNotification ) } @@ -45,7 +47,8 @@ extension KeychainSharingSession { ), basicAuth: self.basicAuth, tokenEndpoint: self.tokenEndpoint, - oidcScopes: self.oidcScopes + oidcScopes: self.oidcScopes, + mailboxIdsBlockNotification: self.mailboxIdsBlockNotification ) } diff --git a/ios/TwakeMailNSE/NotificationService.swift b/ios/TwakeMailNSE/NotificationService.swift index 7f4f360d1d..3d16b869da 100644 --- a/ios/TwakeMailNSE/NotificationService.swift +++ b/ios/TwakeMailNSE/NotificationService.swift @@ -85,31 +85,16 @@ class NotificationService: UNNotificationServiceExtension { newEmailDeliveryState: newEmailDeliveryState ) - if (emails.count > 1) { - for email in emails { - if (email.id == emails.last?.id) { - self.showModifiedNotification(title: email.getSenderName(), - subtitle: email.subject, - body: email.preview, - badgeCount: emails.count, - userInfo: [JmapConstants.EMAIL_ID : email.id]) - return self.notify() - } else { - self.showNewNotification(title: email.getSenderName(), - subtitle: email.subject, - body: email.preview, - badgeCount: emails.count, - notificationId: email.id, - userInfo: [JmapConstants.EMAIL_ID : email.id]) - } - } + let mailboxIdsBlockNotification = keychainSharingSession.mailboxIdsBlockNotification ?? [] + + if (mailboxIdsBlockNotification.isEmpty) { + return self.showListNotification(emails: emails) } else { - self.showModifiedNotification(title: emails.first!.getSenderName(), - subtitle: emails.first!.subject, - body: emails.first!.preview, - badgeCount: 1, - userInfo: [JmapConstants.EMAIL_ID : emails.first!.id]) - return self.notify() + let emailFiltered = self.filterEmailsToPushNotificacation( + emails: emails, + mailboxIdsBlockNotification: mailboxIdsBlockNotification) + + return self.showListNotification(emails: emailFiltered) } } } catch { @@ -121,7 +106,39 @@ class NotificationService: UNNotificationServiceExtension { ) } } - + + private func filterEmailsToPushNotificacation(emails: [Email], mailboxIdsBlockNotification: [String]) -> [Email] { + return emails.filter { email in + if email.listMailboxIds().isEmpty { + return true + } else if mailboxIdsBlockNotification.contains(where: { $0 == email.listMailboxIds().first }) { + return false + } else { + return true + } + } + } + + private func showListNotification(emails: [Email]) { + for email in emails { + if (email.id == emails.last?.id) { + self.showModifiedNotification(title: email.getSenderName(), + subtitle: email.subject, + body: email.preview, + badgeCount: emails.count, + userInfo: [JmapConstants.EMAIL_ID : email.id]) + return self.notify() + } else { + self.showNewNotification(title: email.getSenderName(), + subtitle: email.subject, + body: email.preview, + badgeCount: emails.count, + notificationId: email.id, + userInfo: [JmapConstants.EMAIL_ID : email.id]) + } + } + } + private func showDefaultNotification(message: String) { self.modifiedContent?.title = InfoPlistReader(bundle: .app).bundleDisplayName self.modifiedContent?.body = message @@ -156,12 +173,12 @@ class NotificationService: UNNotificationServiceExtension { content.sound = .default content.badge = NSNumber(value: badgeCount) content.userInfo = userInfo - + // Create a notification trigger - let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 2, repeats: false) + let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 2, repeats: false) // Create a notification request let request = UNNotificationRequest(identifier: notificationId, content: content, trigger: trigger) - + // Schedule the notification UNUserNotificationCenter.current().add(request) { error in if let error = error { @@ -171,7 +188,7 @@ class NotificationService: UNNotificationServiceExtension { } } } - + private func notify() { guard let modifiedContent else { return discard() 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 b7cbe88902..2039313230 100644 --- a/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart +++ b/lib/features/mailbox_dashboard/presentation/controller/mailbox_dashboard_controller.dart @@ -153,6 +153,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/utils/email_receive_manager.dart'; import 'package:tmail_ui_user/main/utils/ios_notification_manager.dart'; +import 'package:tmail_ui_user/main/utils/ios_sharing_manager.dart'; import 'package:uuid/uuid.dart'; class MailboxDashBoardController extends ReloadableController { @@ -198,6 +199,7 @@ class MailboxDashBoardController extends ReloadableController { DeleteMailboxStateToRefreshInteractor? _deleteMailboxStateToRefreshInteractor; GetAutoCompleteInteractor? _getAutoCompleteInteractor; IOSNotificationManager? _iosNotificationManager; + IOSSharingManager? _iosSharingManager; final scaffoldKey = GlobalKey(); final selectedMailbox = Rxn(); @@ -291,6 +293,7 @@ class MailboxDashBoardController extends ReloadableController { _registerPendingFileInfo(); if (PlatformInfo.isIOS) { _registerPendingCurrentEmailIdInNotification(); + _iosSharingManager = getBinding(); } _handleArguments(); super.onReady(); @@ -517,8 +520,6 @@ class MailboxDashBoardController extends ReloadableController { void _handleSessionFromArguments(Session session) { log('MailboxDashBoardController::_handleSession:'); - updateAccountCache(session); - _setUpComponentsFromSession(session); if (PlatformInfo.isWeb) { @@ -550,6 +551,10 @@ class MailboxDashBoardController extends ReloadableController { getAllSendingEmails(); _storeSessionAction(session); } + + if (PlatformInfo.isIOS) { + _iosSharingManager?.updateMailboxIdsBlockNotificationInKeyChain(session, currentAccountId); + } } void _handleMailtoURL(MailtoArguments arguments) { @@ -2553,6 +2558,7 @@ class MailboxDashBoardController extends ReloadableController { if (PlatformInfo.isIOS) { _iosNotificationManager?.dispose(); _currentEmailIdInNotificationIOSStreamSubscription?.cancel(); + _iosSharingManager = null; } _emailAddressStreamSubscription.cancel(); _emailContentStreamSubscription.cancel(); diff --git a/lib/features/push_notification/data/extensions/keychain_sharing_session_extension.dart b/lib/features/push_notification/data/extensions/keychain_sharing_session_extension.dart index 16bf658522..d2eb1bd2ae 100644 --- a/lib/features/push_notification/data/extensions/keychain_sharing_session_extension.dart +++ b/lib/features/push_notification/data/extensions/keychain_sharing_session_extension.dart @@ -1,20 +1,22 @@ +import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; import 'package:tmail_ui_user/features/push_notification/data/keychain/keychain_sharing_session.dart'; extension KeychainSharingSessionExtension on KeychainSharingSession { - KeychainSharingSession updating({String? emailState}) { + KeychainSharingSession updating({String? emailState, List? mailboxIdsBlockNotification}) { return KeychainSharingSession( accountId: accountId, userName: userName, authenticationType: authenticationType, apiUrl: apiUrl, - emailState: emailState ?? emailState, + emailState: emailState ?? this.emailState, emailDeliveryState: emailDeliveryState, tokenOIDC: tokenOIDC, basicAuth: basicAuth, tokenEndpoint: tokenEndpoint, oidcScopes: oidcScopes, + mailboxIdsBlockNotification: mailboxIdsBlockNotification ?? this.mailboxIdsBlockNotification, ); } } \ No newline at end of file diff --git a/lib/features/push_notification/data/keychain/keychain_sharing_session.dart b/lib/features/push_notification/data/keychain/keychain_sharing_session.dart index 2847726b06..a8d8391754 100644 --- a/lib/features/push_notification/data/keychain/keychain_sharing_session.dart +++ b/lib/features/push_notification/data/keychain/keychain_sharing_session.dart @@ -1,14 +1,17 @@ import 'package:equatable/equatable.dart'; import 'package:jmap_dart_client/http/converter/account_id_converter.dart'; +import 'package:jmap_dart_client/http/converter/mailbox_id_converter.dart'; import 'package:jmap_dart_client/http/converter/user_name_converter.dart'; import 'package:jmap_dart_client/jmap/account_id.dart'; import 'package:jmap_dart_client/jmap/core/user_name.dart'; +import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:model/account/authentication_type.dart'; import 'package:model/oidc/token_oidc.dart'; part 'keychain_sharing_session.g.dart'; +@MailboxIdConverter() @UserNameConverter() @AccountIdConverter() @JsonSerializable(includeIfNull: false, explicitToJson: true) @@ -23,6 +26,7 @@ class KeychainSharingSession with EquatableMixin { String? basicAuth; String? tokenEndpoint; List? oidcScopes; + List? mailboxIdsBlockNotification; KeychainSharingSession({ required this.accountId, @@ -35,6 +39,7 @@ class KeychainSharingSession with EquatableMixin { this.basicAuth, this.tokenEndpoint, this.oidcScopes, + this.mailboxIdsBlockNotification, }); factory KeychainSharingSession.fromJson(Map json) => _$KeychainSharingSessionFromJson(json); @@ -53,5 +58,6 @@ class KeychainSharingSession with EquatableMixin { basicAuth, tokenEndpoint, oidcScopes, + mailboxIdsBlockNotification, ]; } \ No newline at end of file diff --git a/lib/main/bindings/network/network_bindings.dart b/lib/main/bindings/network/network_bindings.dart index d5823d2bed..f5e2b7a009 100644 --- a/lib/main/bindings/network/network_bindings.dart +++ b/lib/main/bindings/network/network_bindings.dart @@ -23,6 +23,7 @@ import 'package:tmail_ui_user/features/login/data/network/dns_service.dart'; import 'package:tmail_ui_user/features/login/data/network/interceptors/authorization_interceptors.dart'; import 'package:tmail_ui_user/features/login/data/network/oidc_http_client.dart'; import 'package:tmail_ui_user/features/login/data/utils/library_platform/app_auth_plugin/app_auth_plugin.dart'; +import 'package:tmail_ui_user/features/mailbox/data/local/mailbox_cache_manager.dart'; import 'package:tmail_ui_user/features/mailbox/data/local/state_cache_manager.dart'; import 'package:tmail_ui_user/features/mailbox/data/network/mailbox_api.dart'; import 'package:tmail_ui_user/features/mailbox_dashboard/data/network/spam_report_api.dart'; @@ -47,12 +48,12 @@ class NetworkBindings extends Bindings { _bindingConnection(); _bindingBaseOption(); _bindingDio(); - _bindingSharing(); _bindingInterceptors(); _bindingApi(); _bindingTransformer(); _bindingServices(); _bindingException(); + _bindingSharing(); } void _bindingBaseOption() { @@ -80,6 +81,8 @@ class NetworkBindings extends Bindings { Get.find(), Get.find(), Get.find(), + Get.find(), + Get.find(), )); } diff --git a/lib/main/utils/ios_sharing_manager.dart b/lib/main/utils/ios_sharing_manager.dart index 62367fb43e..69e02fd0f1 100644 --- a/lib/main/utils/ios_sharing_manager.dart +++ b/lib/main/utils/ios_sharing_manager.dart @@ -3,16 +3,21 @@ import 'dart:convert'; import 'package:core/utils/app_logger.dart'; 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/core/user_name.dart'; +import 'package:jmap_dart_client/jmap/mail/mailbox/mailbox.dart'; import 'package:model/account/authentication_type.dart'; import 'package:model/account/personal_account.dart'; +import 'package:model/extensions/mailbox_extension.dart'; import 'package:model/oidc/token_oidc.dart'; import 'package:tmail_ui_user/features/login/data/local/authentication_info_cache_manager.dart'; import 'package:tmail_ui_user/features/login/data/local/oidc_configuration_cache_manager.dart'; import 'package:tmail_ui_user/features/login/data/local/token_oidc_cache_manager.dart'; import 'package:tmail_ui_user/features/login/data/network/oidc_http_client.dart'; +import 'package:tmail_ui_user/features/mailbox/data/local/mailbox_cache_manager.dart'; import 'package:tmail_ui_user/features/mailbox/data/local/state_cache_manager.dart'; import 'package:tmail_ui_user/features/mailbox/data/model/state_type.dart'; +import 'package:tmail_ui_user/features/mailbox/data/network/mailbox_api.dart'; import 'package:tmail_ui_user/features/push_notification/data/extensions/keychain_sharing_session_extension.dart'; import 'package:tmail_ui_user/features/push_notification/data/keychain/keychain_sharing_manager.dart'; import 'package:tmail_ui_user/features/push_notification/data/keychain/keychain_sharing_session.dart'; @@ -24,6 +29,8 @@ class IOSSharingManager { final AuthenticationInfoCacheManager _authenticationInfoCacheManager; final OidcConfigurationCacheManager _oidcConfigurationCacheManager; final OIDCHttpClient _oidcHttpClient; + final MailboxCacheManager _mailboxCacheManager; + final MailboxAPI _mailboxAPI; IOSSharingManager( this._keychainSharingManager, @@ -32,6 +39,8 @@ class IOSSharingManager { this._authenticationInfoCacheManager, this._oidcConfigurationCacheManager, this._oidcHttpClient, + this._mailboxCacheManager, + this._mailboxAPI, ); bool _validateToSaveKeychain(PersonalAccount personalAccount) { @@ -79,6 +88,10 @@ class IOSSharingManager { final tokenRecords = await _getTokenEndpointAndScopes(); + final mailboxIdsBlockNotification = await _getMailboxIdsBlockNotification( + accountId: personalAccount.accountId!, + userName: personalAccount.userName!); + final keychainSharingSession = KeychainSharingSession( accountId: personalAccount.accountId!, userName: personalAccount.userName!, @@ -90,7 +103,7 @@ class IOSSharingManager { basicAuth: credentialInfo, tokenEndpoint: tokenRecords?.tokenEndpoint, oidcScopes: tokenRecords?.scopes, - ); + mailboxIdsBlockNotification: mailboxIdsBlockNotification); await _keychainSharingManager.save(keychainSharingSession); @@ -184,4 +197,60 @@ class IOSSharingManager { final newKeychain = keychainSharingStored.updating(emailState: newEmailState); await _keychainSharingManager.save(newKeychain); } + + Future updateMailboxIdsBlockNotificationInKeyChain(Session session, AccountId accountId) async { + try { + final keychainSharingStored = await getKeychainSharingSession(accountId); + if (keychainSharingStored == null) { + return; + } + + if (keychainSharingStored.mailboxIdsBlockNotification?.isNotEmpty == true) { + return; + } + + final listMailboxIdBlockNotification = await _getMailboxIdsBlockNotification( + accountId: accountId, + session: session, + userName: session.username); + + final newKeychain = keychainSharingStored.updating( + mailboxIdsBlockNotification: listMailboxIdBlockNotification); + + await _keychainSharingManager.save(newKeychain); + } catch (e) { + logError('IOSSharingManager::updateMailboxIdsBlockNotificationInKeyChain: Exception = $e'); + } + } + + Future?> _getMailboxIdsBlockNotification({ + required AccountId accountId, + required UserName userName, + Session? session + }) async { + try { + final mailboxesCache = await _mailboxCacheManager.getAllMailbox(accountId, userName); + final listMailboxIdBlockNotification = mailboxesCache + .where((mailbox) => mailbox.pushNotificationDeactivated && mailbox.id != null) + .map((mailbox) => mailbox.id!) + .toList(); + log('IOSSharingManager::_getMailboxIdsBlockNotification(): CACHE_MAILBOX_LIST = $listMailboxIdBlockNotification'); + if (listMailboxIdBlockNotification.isNotEmpty) { + return listMailboxIdBlockNotification; + } else if (session != null) { + final mailboxResponse = await _mailboxAPI.getAllMailbox(session, accountId); + final listMailboxIdBlockNotification = mailboxResponse.mailboxes + .where((mailbox) => mailbox.pushNotificationDeactivated && mailbox.id != null) + .map((mailbox) => mailbox.id!) + .toList(); + log('IOSSharingManager::_getMailboxIdsBlockNotification(): NETWORK_MAILBOX_LIST = $listMailboxIdBlockNotification'); + return listMailboxIdBlockNotification; + } else { + return null; + } + } catch (e) { + logError('IOSSharingManager::_getMailboxIdsBlockNotification:Exception: $e'); + return null; + } + } } \ No newline at end of file diff --git a/model/lib/extensions/mailbox_extension.dart b/model/lib/extensions/mailbox_extension.dart index a39976b584..712f84701c 100644 --- a/model/lib/extensions/mailbox_extension.dart +++ b/model/lib/extensions/mailbox_extension.dart @@ -6,6 +6,18 @@ extension MailboxExtension on Mailbox { bool hasRole() => role != null && role!.value.isNotEmpty; + bool get isSpam => role == PresentationMailbox.roleSpam; + + bool get isTrash => role == PresentationMailbox.roleTrash; + + bool get isDrafts => role == PresentationMailbox.roleDrafts; + + bool get isSent => role == PresentationMailbox.roleSent; + + bool get isOutbox => name?.name == PresentationMailbox.outboxRole || role == PresentationMailbox.roleOutbox; + + bool get pushNotificationDeactivated => isOutbox || isSent || isDrafts || isTrash || isSpam; + PresentationMailbox toPresentationMailbox() { return PresentationMailbox( id!,