TF-3275 Fix FCM iOS foreground desync (#3314)
This commit is contained in:
@@ -0,0 +1,24 @@
|
||||
# 55. iOS FCM routing
|
||||
|
||||
Date: 2024-12-05
|
||||
|
||||
## Status
|
||||
|
||||
Accepted
|
||||
|
||||
## Context
|
||||
|
||||
- Notification from FCM contains `mutable-content: true`
|
||||
- Those notification will be transfered to Notification Service Extension ([See here](https://developer.apple.com/documentation/usernotifications/modifying-content-in-newly-delivered-notifications#Configure-the-payload-for-the-remote-notification)) whether the app is in background or foreground
|
||||
- Due to the app is in foreground, no notification will be shown
|
||||
- Due to NSE handle the data, FCM on Flutter side cannot handle it
|
||||
|
||||
## Decision
|
||||
|
||||
- We check if the app is in foreground or not
|
||||
- If the app is in foreground, we will not modify the payload and route the payload to FCM foreground method channel
|
||||
- If the app is in background or terminated, we will keep the current implementation
|
||||
|
||||
## Consequences
|
||||
|
||||
- Twake Mail iOS app will get latest updates when app is in foreground
|
||||
@@ -1381,6 +1381,7 @@
|
||||
F5D4EA082B2ABF090090DDFC /* Debug */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
APP_GROUP_ID = group.com.linagora.teammail;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
@@ -1427,6 +1428,7 @@
|
||||
F5D4EA092B2ABF090090DDFC /* Release */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
APP_GROUP_ID = group.com.linagora.teammail;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
@@ -1467,6 +1469,7 @@
|
||||
F5D4EA0A2B2ABF090090DDFC /* Profile */ = {
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
APP_GROUP_ID = group.com.linagora.teammail;
|
||||
ASSETCATALOG_COMPILER_GENERATE_SWIFT_ASSET_SYMBOL_EXTENSIONS = YES;
|
||||
CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE;
|
||||
CLANG_CXX_LANGUAGE_STANDARD = "gnu++20";
|
||||
|
||||
@@ -8,6 +8,7 @@ import flutter_local_notifications
|
||||
@objc class AppDelegate: FlutterAppDelegate {
|
||||
|
||||
var notificationInteractionChannel: FlutterMethodChannel?
|
||||
var fcmMethodChannel: FlutterMethodChannel?
|
||||
var currentEmailId: String?
|
||||
|
||||
override func application(
|
||||
@@ -17,6 +18,7 @@ import flutter_local_notifications
|
||||
GeneratedPluginRegistrant.register(with: self)
|
||||
|
||||
createNotificationInteractionChannel()
|
||||
createFcmMethodChannel()
|
||||
|
||||
if let payload = launchOptions?[.remoteNotification] as? [AnyHashable : Any],
|
||||
let emailId = payload[JmapConstants.EMAIL_ID] as? String,
|
||||
@@ -52,6 +54,14 @@ import flutter_local_notifications
|
||||
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
|
||||
}
|
||||
|
||||
override func applicationDidEnterBackground(_ application: UIApplication) {
|
||||
updateApplicationStateInUserDefaults(false)
|
||||
}
|
||||
|
||||
override func applicationWillTerminate(_ application: UIApplication) {
|
||||
updateApplicationStateInUserDefaults(false)
|
||||
}
|
||||
|
||||
override func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool {
|
||||
let sharingIntent = SwiftReceiveSharingIntentPlugin.instance
|
||||
if sharingIntent.hasMatchingSchemePrefix(url: url) {
|
||||
@@ -69,6 +79,7 @@ import flutter_local_notifications
|
||||
|
||||
override func applicationDidBecomeActive(_ application: UIApplication) {
|
||||
removeAppBadger()
|
||||
updateApplicationStateInUserDefaults(true)
|
||||
}
|
||||
|
||||
private func handleEmailAndress(open url: URL) -> URL? {
|
||||
@@ -90,12 +101,19 @@ import flutter_local_notifications
|
||||
willPresent notification: UNNotification,
|
||||
withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void) {
|
||||
TwakeLogger.shared.log(message: "AppDelegate::userNotificationCenter::willPresent::notification: \(notification)")
|
||||
TwakeLogger.shared.log(message: "AppDelegate::userNotificationCenter::willPresent::notificationContent: \(notification.request.content.userInfo)")
|
||||
if let notificationBadgeCount = notification.request.content.badge?.intValue, notificationBadgeCount > 0 {
|
||||
let newBadgeCount = UIApplication.shared.applicationIconBadgeNumber + notificationBadgeCount
|
||||
TwakeLogger.shared.log(message: "AppDelegate::userNotificationCenter::willPresent:newBadgeCount: \(newBadgeCount)")
|
||||
updateAppBadger(newBadgeCount: newBadgeCount)
|
||||
}
|
||||
if validateDisplayPushNotification(userInfo: notification.request.content.userInfo) {
|
||||
if UIApplication.shared.applicationState == .active {
|
||||
fcmMethodChannel?.invokeMethod(
|
||||
CoreUtils.FCM_ON_MESSAGE_METHOD_NAME,
|
||||
arguments: notification.request.content.userInfo)
|
||||
|
||||
completionHandler([])
|
||||
} else if validateDisplayPushNotification(userInfo: notification.request.content.userInfo) {
|
||||
completionHandler([.alert, .badge, .sound])
|
||||
} else {
|
||||
completionHandler([])
|
||||
@@ -167,4 +185,19 @@ extension AppDelegate {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func createFcmMethodChannel() {
|
||||
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
|
||||
|
||||
self.fcmMethodChannel = FlutterMethodChannel(
|
||||
name: CoreUtils.FCM_METHOD_CHANNEL_NAME,
|
||||
binaryMessenger: controller.binaryMessenger
|
||||
)
|
||||
}
|
||||
|
||||
private func updateApplicationStateInUserDefaults(_ appIsActive: Bool) {
|
||||
let appGroupId = (Bundle.main.object(forInfoDictionaryKey: "AppGroupId") as? String) ?? "group.\(Bundle.main.bundleIdentifier!)"
|
||||
let userDefaults = UserDefaults(suiteName: appGroupId)
|
||||
userDefaults?.set(appIsActive, forKey: CoreUtils.APPLICATION_STATE)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,9 @@ class CoreUtils {
|
||||
static let NOTIFICATION_INTERACTION_CHANNEL_NAME = "notification_interaction_channel"
|
||||
static let CURRENT_EMAIL_ID_IN_NOTIFICATION_CLICK_WHEN_APP_FOREGROUND_OR_BACKGROUND = "current_email_id_in_notification_click_when_app_foreground_or_background"
|
||||
static let CURRENT_EMAIL_ID_IN_NOTIFICATION_CLICK_WHEN_APP_TERMINATED = "current_email_id_in_notification_click_when_app_terminated"
|
||||
static let FCM_METHOD_CHANNEL_NAME = "plugins.flutter.io/firebase_messaging"
|
||||
static let FCM_ON_MESSAGE_METHOD_NAME = "Messaging#onMessage"
|
||||
static let APPLICATION_STATE = "applicationState"
|
||||
|
||||
func getCurrentDate() -> Date {
|
||||
if #available(iOS 15, *) {
|
||||
|
||||
@@ -13,5 +13,7 @@
|
||||
<key>NSExtensionPrincipalClass</key>
|
||||
<string>$(PRODUCT_MODULE_NAME).NotificationService</string>
|
||||
</dict>
|
||||
<key>AppGroupId</key>
|
||||
<string>${APP_GROUP_ID}</string>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -16,6 +16,14 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
handler = contentHandler
|
||||
modifiedContent = (request.content.mutableCopy() as? UNMutableNotificationContent)
|
||||
|
||||
let appGroupId = (Bundle.main.object(forInfoDictionaryKey: "AppGroupId") as? String) ?? "group.\(Bundle.main.bundleIdentifier!)"
|
||||
let userDefaults = UserDefaults(suiteName: appGroupId)
|
||||
let isAppActive = userDefaults?.value(forKey: CoreUtils.APPLICATION_STATE) as? Bool
|
||||
if isAppActive == true {
|
||||
self.modifiedContent?.userInfo = request.content.userInfo.merging(["data": request.content.userInfo], uniquingKeysWith: {(_, new) in new})
|
||||
contentHandler(self.modifiedContent ?? request.content)
|
||||
}
|
||||
|
||||
guard let payloadData = request.content.userInfo as? [String: Any],
|
||||
!keychainController.retrieveSharingSessions().isEmpty else {
|
||||
self.showDefaultNotification(message: NSLocalizedString(self.newNotificationDefaultMessageKey, comment: "Localizable"))
|
||||
|
||||
Reference in New Issue
Block a user