mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-02 21:33:26 +00:00
Open the room live when tapping the notification for its newest message
Tapping a message notification always opened the room on a timeline focussed on that event, even when it was the newest message and the live timeline already ends with it. On tap, the notification manager now checks whether any other delivered notification for the same room is more recent. If none is, the room is opened live instead of focussed. Older notifications in a room keep focussing on their event.
This commit is contained in:
@@ -408,7 +408,7 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
|
|||||||
return !userSessionFlowCoordinator.isDisplayingRoomScreen(withRoomID: roomID)
|
return !userSessionFlowCoordinator.isDisplayingRoomScreen(withRoomID: roomID)
|
||||||
}
|
}
|
||||||
|
|
||||||
func notificationTapped(content: UNNotificationContent) async {
|
func notificationTapped(content: UNNotificationContent, isNewestInRoom: Bool) async {
|
||||||
MXLog.info("Tapped Notification")
|
MXLog.info("Tapped Notification")
|
||||||
|
|
||||||
guard let roomID = content.roomID,
|
guard let roomID = content.roomID,
|
||||||
@@ -426,11 +426,12 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg
|
|||||||
handleAppRoute(.room(roomID: roomID, via: []), windowType: nil)
|
handleAppRoute(.room(roomID: roomID, via: []), windowType: nil)
|
||||||
} else if appSettings.threadsEnabled, let threadRootEventID = content.threadRootEventID {
|
} else if appSettings.threadsEnabled, let threadRootEventID = content.threadRootEventID {
|
||||||
handleAppRoute(.thread(roomID: roomID, threadRootEventID: threadRootEventID, focusEventID: eventID), windowType: nil)
|
handleAppRoute(.thread(roomID: roomID, threadRootEventID: threadRootEventID, focusEventID: eventID), windowType: nil)
|
||||||
} else if let eventID {
|
} else if let eventID, !isNewestInRoom {
|
||||||
// Only track main timeline event deeplinking
|
// Only track main timeline event deeplinking
|
||||||
analyticsService.signpost.startTransaction(.notificationToMessage)
|
analyticsService.signpost.startTransaction(.notificationToMessage)
|
||||||
handleAppRoute(.event(eventID: eventID, roomID: roomID, via: []), windowType: nil)
|
handleAppRoute(.event(eventID: eventID, roomID: roomID, via: []), windowType: nil)
|
||||||
} else {
|
} else {
|
||||||
|
// The live timeline already ends with the newest message, so there's no need to focus on it.
|
||||||
handleAppRoute(.room(roomID: roomID, via: []), windowType: nil)
|
handleAppRoute(.room(roomID: roomID, via: []), windowType: nil)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -259,11 +259,21 @@ extension NotificationManager: UNUserNotificationCenterDelegate {
|
|||||||
content: response.notification.request.content,
|
content: response.notification.request.content,
|
||||||
replyText: response.userText)
|
replyText: response.userText)
|
||||||
case UNNotificationDefaultActionIdentifier:
|
case UNNotificationDefaultActionIdentifier:
|
||||||
await delegate?.notificationTapped(content: response.notification.request.content)
|
await delegate?.notificationTapped(content: response.notification.request.content,
|
||||||
|
isNewestInRoom: isNewestInRoom(response.notification))
|
||||||
default:
|
default:
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Whether no other delivered notification for the same room is more recent.
|
||||||
|
private func isNewestInRoom(_ notification: UNNotification) async -> Bool {
|
||||||
|
guard let roomID = notification.request.content.roomID else { return false }
|
||||||
|
|
||||||
|
return await notificationCenter.deliveredNotifications().allSatisfy { deliveredNotification in
|
||||||
|
deliveredNotification.request.content.roomID != roomID || deliveredNotification.date <= notification.date
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension UNUserNotificationCenter: UserNotificationCenterProtocol { }
|
extension UNUserNotificationCenter: UserNotificationCenterProtocol { }
|
||||||
|
|||||||
@@ -11,7 +11,8 @@ import UserNotifications
|
|||||||
|
|
||||||
protocol NotificationManagerDelegate: AnyObject {
|
protocol NotificationManagerDelegate: AnyObject {
|
||||||
func shouldDisplayInAppNotification(content: UNNotificationContent) -> Bool
|
func shouldDisplayInAppNotification(content: UNNotificationContent) -> Bool
|
||||||
func notificationTapped(content: UNNotificationContent) async
|
/// - Parameter isNewestInRoom: Whether no other delivered notification for the same room is more recent.
|
||||||
|
func notificationTapped(content: UNNotificationContent, isNewestInRoom: Bool) async
|
||||||
func handleInlineReply(_ service: NotificationManagerProtocol,
|
func handleInlineReply(_ service: NotificationManagerProtocol,
|
||||||
content: UNNotificationContent,
|
content: UNNotificationContent,
|
||||||
replyText: String) async
|
replyText: String) async
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ final class NotificationManagerTests {
|
|||||||
private var shouldDisplayInAppNotificationReturnValue = false
|
private var shouldDisplayInAppNotificationReturnValue = false
|
||||||
private var handleInlineReplyDelegateCalled = false
|
private var handleInlineReplyDelegateCalled = false
|
||||||
private var notificationTappedDelegateCalled = false
|
private var notificationTappedDelegateCalled = false
|
||||||
|
private var notificationTappedIsNewestInRoom: Bool?
|
||||||
private var registerForRemoteNotificationsDelegateCalled: (() -> Void)?
|
private var registerForRemoteNotificationsDelegateCalled: (() -> Void)?
|
||||||
private let appSettings: AppSettings
|
private let appSettings: AppSettings
|
||||||
|
|
||||||
@@ -238,6 +239,31 @@ final class NotificationManagerTests {
|
|||||||
#expect(notificationTappedDelegateCalled)
|
#expect(notificationTappedDelegateCalled)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func whenTappedNotificationIsTheNewestInItsRoom_delegateIsToldItIsNewest() async throws {
|
||||||
|
notificationManager.delegate = self
|
||||||
|
let date = Date.now
|
||||||
|
notificationCenter.deliveredNotificationsReturnValue = try [UNNotification.with(userInfo: [NotificationConstants.UserInfoKey.roomIdentifier: "!room:matrix.org"], date: date - 60),
|
||||||
|
UNNotification.with(userInfo: [NotificationConstants.UserInfoKey.roomIdentifier: "!other:matrix.org"], date: date + 60)]
|
||||||
|
|
||||||
|
let response = try UNTextInputNotificationResponse.with(userInfo: [NotificationConstants.UserInfoKey.roomIdentifier: "!room:matrix.org"], date: date)
|
||||||
|
await notificationManager.userNotificationCenter(UNUserNotificationCenter.current(), didReceive: response)
|
||||||
|
|
||||||
|
#expect(notificationTappedIsNewestInRoom == true)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
func whenTappedNotificationIsOlderThanAnotherInItsRoom_delegateIsToldItIsNotNewest() async throws {
|
||||||
|
notificationManager.delegate = self
|
||||||
|
let date = Date.now
|
||||||
|
notificationCenter.deliveredNotificationsReturnValue = try [UNNotification.with(userInfo: [NotificationConstants.UserInfoKey.roomIdentifier: "!room:matrix.org"], date: date + 60)]
|
||||||
|
|
||||||
|
let response = try UNTextInputNotificationResponse.with(userInfo: [NotificationConstants.UserInfoKey.roomIdentifier: "!room:matrix.org"], date: date)
|
||||||
|
await notificationManager.userNotificationCenter(UNUserNotificationCenter.current(), didReceive: response)
|
||||||
|
|
||||||
|
#expect(notificationTappedIsNewestInRoom == false)
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
func updatingAppBadgeCountUsesTheClientSideCount() async {
|
func updatingAppBadgeCountUsesTheClientSideCount() async {
|
||||||
clientProxy.totalUnreadNotifications = 7
|
clientProxy.totalUnreadNotifications = 7
|
||||||
@@ -272,8 +298,9 @@ extension NotificationManagerTests: @MainActor NotificationManagerDelegate {
|
|||||||
shouldDisplayInAppNotificationReturnValue
|
shouldDisplayInAppNotificationReturnValue
|
||||||
}
|
}
|
||||||
|
|
||||||
func notificationTapped(content: UNNotificationContent) async {
|
func notificationTapped(content: UNNotificationContent, isNewestInRoom: Bool) async {
|
||||||
notificationTappedDelegateCalled = true
|
notificationTappedDelegateCalled = true
|
||||||
|
notificationTappedIsNewestInRoom = isNewestInRoom
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleInlineReply(_ service: ElementX.NotificationManagerProtocol, content: UNNotificationContent, replyText: String) async {
|
func handleInlineReply(_ service: ElementX.NotificationManagerProtocol, content: UNNotificationContent, replyText: String) async {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import UserNotifications
|
|||||||
import XCTest
|
import XCTest
|
||||||
|
|
||||||
extension UNNotification {
|
extension UNNotification {
|
||||||
static func with(userInfo: [AnyHashable: Any]) throws -> UNNotification {
|
static func with(userInfo: [AnyHashable: Any], date: Date? = nil) throws -> UNNotification {
|
||||||
let content = UNMutableNotificationContent()
|
let content = UNMutableNotificationContent()
|
||||||
content.userInfo = userInfo
|
content.userInfo = userInfo
|
||||||
let request = UNNotificationRequest(identifier: "",
|
let request = UNNotificationRequest(identifier: "",
|
||||||
@@ -20,6 +20,9 @@ extension UNNotification {
|
|||||||
let archiver = MockCoder(requiringSecureCoding: false)
|
let archiver = MockCoder(requiringSecureCoding: false)
|
||||||
let notification = try XCTUnwrap(UNNotification(coder: archiver))
|
let notification = try XCTUnwrap(UNNotification(coder: archiver))
|
||||||
notification.setValue(request, forKey: "request")
|
notification.setValue(request, forKey: "request")
|
||||||
|
if let date {
|
||||||
|
notification.setValue(date, forKey: "date")
|
||||||
|
}
|
||||||
return notification
|
return notification
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,15 +10,9 @@ import UserNotifications
|
|||||||
import XCTest
|
import XCTest
|
||||||
|
|
||||||
extension UNTextInputNotificationResponse {
|
extension UNTextInputNotificationResponse {
|
||||||
static func with(userInfo: [AnyHashable: Any], actionIdentifier: String = UNNotificationDefaultActionIdentifier) throws -> UNNotificationResponse {
|
static func with(userInfo: [AnyHashable: Any], date: Date? = nil, actionIdentifier: String = UNNotificationDefaultActionIdentifier) throws -> UNNotificationResponse {
|
||||||
let content = UNMutableNotificationContent()
|
let notification = try UNNotification.with(userInfo: userInfo, date: date)
|
||||||
content.userInfo = userInfo
|
|
||||||
let request = UNNotificationRequest(identifier: "",
|
|
||||||
content: content,
|
|
||||||
trigger: nil)
|
|
||||||
let archiver = MockCoder(requiringSecureCoding: false)
|
let archiver = MockCoder(requiringSecureCoding: false)
|
||||||
let notification = try XCTUnwrap(UNNotification(coder: archiver))
|
|
||||||
notification.setValue(request, forKey: "request")
|
|
||||||
|
|
||||||
let response = try XCTUnwrap(UNTextInputNotificationResponse(coder: archiver))
|
let response = try XCTUnwrap(UNTextInputNotificationResponse(coder: archiver))
|
||||||
response.setValue(notification, forKey: "notification")
|
response.setValue(notification, forKey: "notification")
|
||||||
|
|||||||
Reference in New Issue
Block a user