TF-2871 Handle click notification to open detailed email on iOS

This commit is contained in:
dab246
2024-07-11 09:46:23 +07:00
committed by Dat H. Pham
parent 9ffd20f1a7
commit 501d4d38d9
32 changed files with 416 additions and 354 deletions
-6
View File
@@ -21,8 +21,6 @@
F522E8862C0EE8B600DDA35B /* CoreUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = F522E8852C0EE8B600DDA35B /* CoreUtils.swift */; };
F522E8872C0EE8B600DDA35B /* CoreUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = F522E8852C0EE8B600DDA35B /* CoreUtils.swift */; };
F522E8882C0EE8B600DDA35B /* CoreUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = F522E8852C0EE8B600DDA35B /* CoreUtils.swift */; };
F522E88A2C0F117900DDA35B /* IntegerExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F522E8892C0F117900DDA35B /* IntegerExtensions.swift */; };
F522E88B2C0F117900DDA35B /* IntegerExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F522E8892C0F117900DDA35B /* IntegerExtensions.swift */; };
F52F993027FD6EB900346091 /* ShareViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = F52F992F27FD6EB900346091 /* ShareViewController.swift */; };
F52F993327FD6EB900346091 /* MainInterface.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = F52F993127FD6EB900346091 /* MainInterface.storyboard */; };
F52F993727FD6EB900346091 /* TeamMailShareExtension.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = F52F992D27FD6EB900346091 /* TeamMailShareExtension.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
@@ -139,7 +137,6 @@
B2EAFF659572E6B9F5AFAAF8 /* Pods-Runner.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Runner.debug.xcconfig"; path = "Target Support Files/Pods-Runner/Pods-Runner.debug.xcconfig"; sourceTree = "<group>"; };
F522E87E2C0EE23400DDA35B /* AuthenticationSSOTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationSSOTests.swift; sourceTree = "<group>"; };
F522E8852C0EE8B600DDA35B /* CoreUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CoreUtils.swift; sourceTree = "<group>"; };
F522E8892C0F117900DDA35B /* IntegerExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IntegerExtensions.swift; sourceTree = "<group>"; };
F52F992D27FD6EB900346091 /* TeamMailShareExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = TeamMailShareExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; };
F52F992F27FD6EB900346091 /* ShareViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShareViewController.swift; sourceTree = "<group>"; };
F52F993227FD6EB900346091 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/MainInterface.storyboard; sourceTree = "<group>"; };
@@ -446,7 +443,6 @@
children = (
F5E7D8782B38763B0009BB8A /* DateExtensions.swift */,
F5E7D87B2B38764F0009BB8A /* StringExtensions.swift */,
F522E8892C0F117900DDA35B /* IntegerExtensions.swift */,
);
path = Extensions;
sourceTree = "<group>";
@@ -748,7 +744,6 @@
files = (
F5E7D87C2B38764F0009BB8A /* StringExtensions.swift in Sources */,
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
F522E88A2C0F117900DDA35B /* IntegerExtensions.swift in Sources */,
F5E7D8792B38763B0009BB8A /* DateExtensions.swift in Sources */,
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
F522E8862C0EE8B600DDA35B /* CoreUtils.swift in Sources */,
@@ -787,7 +782,6 @@
F53D1E7F2B2E3C2600051FD0 /* JmapConstants.swift in Sources */,
F5E7D8822B3876F60009BB8A /* AuthenticationCredential.swift in Sources */,
F5D4EA032B2ABF090090DDFC /* NotificationService.swift in Sources */,
F522E88B2C0F117900DDA35B /* IntegerExtensions.swift in Sources */,
F522E8872C0EE8B600DDA35B /* CoreUtils.swift in Sources */,
F53D1E8A2B2E4BB700051FD0 /* TokenOidc.swift in Sources */,
F53D1E862B2E401B00051FD0 /* JmapRequestGenerator.swift in Sources */,
+28 -17
View File
@@ -8,7 +8,7 @@ import flutter_local_notifications
@objc class AppDelegate: FlutterAppDelegate {
var notificationInteractionChannel: FlutterMethodChannel?
var initialNotificationInfo: Any?
var currentEmailId: String?
override func application(
_ application: UIApplication,
@@ -17,7 +17,12 @@ import flutter_local_notifications
GeneratedPluginRegistrant.register(with: self)
createNotificationInteractionChannel()
initialNotificationInfo = launchOptions?[.remoteNotification]
if let payload = launchOptions?[.remoteNotification] as? [AnyHashable : Any],
let emailId = payload[JmapConstants.EMAIL_ID] as? String,
!emailId.isEmpty {
currentEmailId = emailId
}
if #available(iOS 10.0, *) {
UNUserNotificationCenter.current().delegate = self as UNUserNotificationCenterDelegate
@@ -90,9 +95,7 @@ import flutter_local_notifications
TwakeLogger.shared.log(message: "AppDelegate::userNotificationCenter::willPresent:newBadgeCount: \(newBadgeCount)")
updateAppBadger(newBadgeCount: newBadgeCount)
}
if let emailId = notification.request.content.userInfo[JmapConstants.EMAIL_ID] as? String,
!emailId.isEmpty,
!isAppForegroundActive() {
if validateDisplayPushNotification(userInfo: notification.request.content.userInfo) {
completionHandler([.alert, .badge, .sound])
} else {
completionHandler([])
@@ -105,12 +108,24 @@ import flutter_local_notifications
let newBadgeCount = currentBadgeCount > 0 ? currentBadgeCount - 1 : 0
updateAppBadger(newBadgeCount: newBadgeCount)
if let emailId = response.notification.request.content.userInfo[JmapConstants.EMAIL_ID] as? String {
self.notificationInteractionChannel?.invokeMethod("openEmail", arguments: emailId)
let userInfo = response.notification.request.content.userInfo
if let emailId = userInfo[JmapConstants.EMAIL_ID] as? String, !emailId.isEmpty {
self.notificationInteractionChannel?.invokeMethod(
CoreUtils.CURRENT_EMAIL_ID_IN_NOTIFICATION_CLICK_WHEN_APP_FOREGROUND_OR_BACKGROUND,
arguments: emailId)
}
completionHandler()
}
private func validateDisplayPushNotification(userInfo: [AnyHashable : Any]) -> Bool {
if let emailId = userInfo[JmapConstants.EMAIL_ID] as? String, !emailId.isEmpty, UIApplication.shared.applicationState != .active {
return true
}
return false
}
}
extension AppDelegate {
@@ -134,25 +149,21 @@ extension AppDelegate {
}
}
private func isAppForegroundActive() -> Bool {
return UIApplication.shared.applicationState == .active
}
private func createNotificationInteractionChannel() {
let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
self.notificationInteractionChannel = FlutterMethodChannel(
name: "notification_interaction_channel",
name: CoreUtils.NOTIFICATION_INTERACTION_CHANNEL_NAME,
binaryMessenger: controller.binaryMessenger
)
self.notificationInteractionChannel?.setMethodCallHandler { (call, result) in
switch call.method {
case "getInitialNotificationInfo":
result(self.initialNotificationInfo)
self.initialNotificationInfo = nil
default:
break
case CoreUtils.CURRENT_EMAIL_ID_IN_NOTIFICATION_CLICK_WHEN_APP_TERMINATED:
result(self.currentEmailId)
self.currentEmailId = nil
default:
break
}
}
}
@@ -11,4 +11,8 @@ extension Date {
dateFormatter.locale = Locale(identifier: CoreUtils.EN_US_POSIX_LOCALE)
return dateFormatter.string(from: self)
}
func adding(seconds: Int) -> Date {
return self.addingTimeInterval(TimeInterval(seconds))
}
}
@@ -1,11 +0,0 @@
import Foundation
extension Int {
func convertMillisecondsToDate() -> Date {
return Date(timeIntervalSince1970: TimeInterval(self) / 1000)
}
func convertMillisecondsToISO8601String() -> String {
return convertMillisecondsToDate().convertDateToISO8601String()
}
}
@@ -41,9 +41,12 @@ class AuthenticationInterceptor: RequestInterceptor {
}
let newRefreshToken = tokenResponse.refreshToken ?? authenticationSSO.refreshToken
let expireTime = tokenResponse.expiresTime != nil
? tokenResponse.expiresTime!.convertMillisecondsToISO8601String()
: nil
var expireTime: String? = nil
if (tokenResponse.expiresTime != nil) {
expireTime = CoreUtils.shared.getCurrentDate().adding(seconds: tokenResponse.expiresTime!).convertDateToISO8601String()
}
self.authentication = AuthenticationSSO(
type: AuthenticationType.oidc,
+3
View File
@@ -5,6 +5,9 @@ class CoreUtils {
static let ISO8601_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS"
static let EN_US_POSIX_LOCALE = "en_US_POSIX"
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"
func getCurrentDate() -> Date {
if #available(iOS 15, *) {
+43 -2
View File
@@ -5,25 +5,39 @@ import XCTest
class DateConversionTests: XCTestCase {
func testConvertValidISO8601StringToDate() {
// Arrange
let validDateString = "2024-05-20T22:54:57.958"
// Act
let date = validDateString.convertISO8601StringToDate()
// Assert
XCTAssertNotNil(date, "Date should not be nil")
// Arrange
let calendar = Calendar.current
let expectedComponents = DateComponents(year: 2024, month: 5, day: 20, hour: 22, minute: 54, second: 57, nanosecond: 958000000)
// Act
let expectedDate = calendar.date(from: expectedComponents)
// Assert
XCTAssertEqual(date, expectedDate, "Converted date does not match expected date")
}
func testInvalidISO8601String() {
// Arrange
let invalidDateString = "Invalid Date String"
// Act
let date = invalidDateString.convertISO8601StringToDate()
// Assert
XCTAssertNil(date, "The conversion should return nil for an invalid date string.")
}
func testConvertValidDateToISO8601String() {
// Arrange
let validDate = Calendar.current.date(
from: DateComponents(
year: 2024,
@@ -35,11 +49,38 @@ class DateConversionTests: XCTestCase {
nanosecond: 958000000
)
)
let expectedDateString = "2024-05-20T22:54:57.958"
// Act
let validDateString = validDate!.convertDateToISO8601String()
// Assert
XCTAssertEqual(validDateString, expectedDateString, "Converted date string does not match expected date string")
}
func testAddingSeconds() {
// Arrange
let initialDate = Date()
let secondsToAdd = 60
let expectedDate = initialDate.addingTimeInterval(TimeInterval(secondsToAdd))
// Act
let resultDate = initialDate.adding(seconds: secondsToAdd)
// Assert
XCTAssertEqual(resultDate, expectedDate, "The date after adding seconds should be correct.")
}
func testAddingNegativeSeconds() {
// Arrange
let initialDate = Date()
let secondsToSubtract = -60
let expectedDate = initialDate.addingTimeInterval(TimeInterval(secondsToSubtract))
// Act
let resultDate = initialDate.adding(seconds: secondsToSubtract)
// Assert
XCTAssertEqual(resultDate, expectedDate, "The date after subtracting seconds should be correct.")
}
}