mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-06 12:28:29 +00:00
Refactor the NSE so that the original notification content is preserved between all the different processing steps
- building brand new content is probably interfering with how iOS syncs up delivered and seen notifications between devices - simplify all the various steps and make the flows easier to follow
This commit is contained in:
committed by
Stefan Ceriu
parent
7cf1d08b20
commit
5ad25d8492
@@ -425,7 +425,6 @@
|
|||||||
510C4EDF826CA9C6CEEC6C95 /* ManageRoomMemberSheetViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A34D9BCA1A7D9A56E1EAF1D /* ManageRoomMemberSheetViewModel.swift */; };
|
510C4EDF826CA9C6CEEC6C95 /* ManageRoomMemberSheetViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A34D9BCA1A7D9A56E1EAF1D /* ManageRoomMemberSheetViewModel.swift */; };
|
||||||
5139F4BD5A5DF6F8D11A9BDE /* NotificationPermissionsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46D0BA44B1838E65B507B277 /* NotificationPermissionsScreen.swift */; };
|
5139F4BD5A5DF6F8D11A9BDE /* NotificationPermissionsScreen.swift in Sources */ = {isa = PBXBuildFile; fileRef = 46D0BA44B1838E65B507B277 /* NotificationPermissionsScreen.swift */; };
|
||||||
513AF15E0E84711B80D04B1B /* ReportRoomScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C3E9684DCE6B66BD0B5DF67 /* ReportRoomScreenViewModelTests.swift */; };
|
513AF15E0E84711B80D04B1B /* ReportRoomScreenViewModelTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0C3E9684DCE6B66BD0B5DF67 /* ReportRoomScreenViewModelTests.swift */; };
|
||||||
518C93DC6516D3D018DE065F /* UNNotificationRequest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 49E751D7EDB6043238111D90 /* UNNotificationRequest.swift */; };
|
|
||||||
51B3B19FA5F91B455C807BA7 /* RoomPollsHistoryScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E964AF2DFEB31E2B799999F /* RoomPollsHistoryScreenModels.swift */; };
|
51B3B19FA5F91B455C807BA7 /* RoomPollsHistoryScreenModels.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6E964AF2DFEB31E2B799999F /* RoomPollsHistoryScreenModels.swift */; };
|
||||||
523C6800ED85D5810CF18C19 /* OIDCAccountSettingsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1D737F4672021D0A7D218CD /* OIDCAccountSettingsPresenter.swift */; };
|
523C6800ED85D5810CF18C19 /* OIDCAccountSettingsPresenter.swift in Sources */ = {isa = PBXBuildFile; fileRef = C1D737F4672021D0A7D218CD /* OIDCAccountSettingsPresenter.swift */; };
|
||||||
52473A4D7B1FBD4CD1E770C8 /* MatrixEntityRegex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AD1A853D605C2146B0DC028 /* MatrixEntityRegex.swift */; };
|
52473A4D7B1FBD4CD1E770C8 /* MatrixEntityRegex.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6AD1A853D605C2146B0DC028 /* MatrixEntityRegex.swift */; };
|
||||||
@@ -1747,7 +1746,6 @@
|
|||||||
49ABAB186CF00B15C5521D04 /* MenuSheetLabelStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuSheetLabelStyle.swift; sourceTree = "<group>"; };
|
49ABAB186CF00B15C5521D04 /* MenuSheetLabelStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MenuSheetLabelStyle.swift; sourceTree = "<group>"; };
|
||||||
49D2C8E66E83EA578A7F318A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
|
49D2C8E66E83EA578A7F318A /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist; path = Info.plist; sourceTree = "<group>"; };
|
||||||
49E6066092ED45E36BB306F7 /* zh-Hant-TW */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "zh-Hant-TW"; path = "zh-Hant-TW.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
49E6066092ED45E36BB306F7 /* zh-Hant-TW */ = {isa = PBXFileReference; lastKnownFileType = text.plist.stringsdict; name = "zh-Hant-TW"; path = "zh-Hant-TW.lproj/Localizable.stringsdict"; sourceTree = "<group>"; };
|
||||||
49E751D7EDB6043238111D90 /* UNNotificationRequest.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UNNotificationRequest.swift; sourceTree = "<group>"; };
|
|
||||||
4A2B5274C1D3D2999D643786 /* EncryptionResetPasswordScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionResetPasswordScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
4A2B5274C1D3D2999D643786 /* EncryptionResetPasswordScreenViewModelProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EncryptionResetPasswordScreenViewModelProtocol.swift; sourceTree = "<group>"; };
|
||||||
4A5B4CD611DE7E94F5BA87B2 /* AppLockTimerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockTimerTests.swift; sourceTree = "<group>"; };
|
4A5B4CD611DE7E94F5BA87B2 /* AppLockTimerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppLockTimerTests.swift; sourceTree = "<group>"; };
|
||||||
4AB29A2D95D3469B5F016655 /* SecureBackupControllerMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupControllerMock.swift; sourceTree = "<group>"; };
|
4AB29A2D95D3469B5F016655 /* SecureBackupControllerMock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SecureBackupControllerMock.swift; sourceTree = "<group>"; };
|
||||||
@@ -3955,7 +3953,6 @@
|
|||||||
children = (
|
children = (
|
||||||
4959CECEC984B3995616F427 /* DataProtectionManager.swift */,
|
4959CECEC984B3995616F427 /* DataProtectionManager.swift */,
|
||||||
EEAA2832D93EC7D2608703FB /* NSEUserSession.swift */,
|
EEAA2832D93EC7D2608703FB /* NSEUserSession.swift */,
|
||||||
49E751D7EDB6043238111D90 /* UNNotificationRequest.swift */,
|
|
||||||
);
|
);
|
||||||
path = Other;
|
path = Other;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -6733,7 +6730,6 @@
|
|||||||
DDB47D29C6865669288BF87C /* UIFont+AttributedStringBuilder.m in Sources */,
|
DDB47D29C6865669288BF87C /* UIFont+AttributedStringBuilder.m in Sources */,
|
||||||
45D6DC594816288983627484 /* UITestsScreenIdentifier.swift in Sources */,
|
45D6DC594816288983627484 /* UITestsScreenIdentifier.swift in Sources */,
|
||||||
281BED345D59A9A6A99E9D98 /* UNNotificationContent.swift in Sources */,
|
281BED345D59A9A6A99E9D98 /* UNNotificationContent.swift in Sources */,
|
||||||
518C93DC6516D3D018DE065F /* UNNotificationRequest.swift in Sources */,
|
|
||||||
06B55882911B4BF5B14E9851 /* URL.swift in Sources */,
|
06B55882911B4BF5B14E9851 /* URL.swift in Sources */,
|
||||||
D98B5EE8C4F5A2CE84687AE8 /* UTType.swift in Sources */,
|
D98B5EE8C4F5A2CE84687AE8 /* UTType.swift in Sources */,
|
||||||
34357B287357BC0B9715DD51 /* UserAgentBuilder.swift in Sources */,
|
34357B287357BC0B9715DD51 /* UserAgentBuilder.swift in Sources */,
|
||||||
|
|||||||
@@ -39,6 +39,10 @@ extension UNNotificationContent {
|
|||||||
@objc var eventID: String? {
|
@objc var eventID: String? {
|
||||||
userInfo[NotificationConstants.UserInfoKey.eventIdentifier] as? String
|
userInfo[NotificationConstants.UserInfoKey.eventIdentifier] as? String
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc var pusherNotificationClientIdentifier: String? {
|
||||||
|
userInfo[NotificationConstants.UserInfoKey.pusherNotificationClientIdentifier] as? String
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension UNMutableNotificationContent {
|
extension UNMutableNotificationContent {
|
||||||
@@ -69,18 +73,20 @@ extension UNMutableNotificationContent {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func addMediaAttachment(using mediaProvider: MediaProviderProtocol?,
|
var unreadCount: Int? {
|
||||||
mediaSource: MediaSourceProxy) async -> UNMutableNotificationContent {
|
userInfo[NotificationConstants.UserInfoKey.unreadCount] as? Int
|
||||||
guard let mediaProvider else {
|
}
|
||||||
return self
|
|
||||||
}
|
func addMediaAttachment(using mediaProvider: MediaProviderProtocol,
|
||||||
|
mediaSource: MediaSourceProxy) async {
|
||||||
switch await mediaProvider.loadFileFromSource(mediaSource) {
|
switch await mediaProvider.loadFileFromSource(mediaSource) {
|
||||||
case .success(let file):
|
case .success(let file):
|
||||||
do {
|
do {
|
||||||
guard let url = file.url else {
|
guard let url = file.url else {
|
||||||
MXLog.error("Couldn't add media attachment: URL is nil")
|
MXLog.error("Couldn't add media attachment: URL is nil")
|
||||||
return self
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
let identifier = ProcessInfo.processInfo.globallyUniqueString
|
let identifier = ProcessInfo.processInfo.globallyUniqueString
|
||||||
let newURL = try FileManager.default.copyFileToTemporaryDirectory(file: url, with: "\(identifier).\(url.pathExtension)")
|
let newURL = try FileManager.default.copyFileToTemporaryDirectory(file: url, with: "\(identifier).\(url.pathExtension)")
|
||||||
let attachment = try UNNotificationAttachment(identifier: identifier,
|
let attachment = try UNNotificationAttachment(identifier: identifier,
|
||||||
@@ -89,24 +95,21 @@ extension UNMutableNotificationContent {
|
|||||||
attachments.append(attachment)
|
attachments.append(attachment)
|
||||||
} catch {
|
} catch {
|
||||||
MXLog.error("Couldn't add media attachment:: \(error)")
|
MXLog.error("Couldn't add media attachment:: \(error)")
|
||||||
return self
|
return
|
||||||
}
|
}
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
MXLog.error("Couldn't load the file for media attachment: \(error)")
|
MXLog.error("Couldn't load the file for media attachment: \(error)")
|
||||||
}
|
}
|
||||||
|
|
||||||
return self
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func addSenderIcon(using mediaProvider: MediaProviderProtocol?,
|
func addSenderIcon(senderID: String,
|
||||||
senderID: String,
|
|
||||||
senderName: String,
|
senderName: String,
|
||||||
icon: NotificationIcon,
|
icon: NotificationIcon,
|
||||||
forcePlaceholder: Bool = false) async throws -> UNMutableNotificationContent {
|
forcePlaceholder: Bool = false,
|
||||||
|
mediaProvider: MediaProviderProtocol) async throws -> UNMutableNotificationContent {
|
||||||
var fetchedImage: INImage?
|
var fetchedImage: INImage?
|
||||||
let image: INImage
|
let image: INImage
|
||||||
if !forcePlaceholder,
|
if !forcePlaceholder,
|
||||||
let mediaProvider,
|
|
||||||
let mediaSource = icon.mediaSource {
|
let mediaSource = icon.mediaSource {
|
||||||
switch await mediaProvider.loadThumbnailForSource(source: mediaSource, size: .init(width: 100, height: 100)) {
|
switch await mediaProvider.loadThumbnailForSource(source: mediaSource, size: .init(width: 100, height: 100)) {
|
||||||
case .success(let data):
|
case .success(let data):
|
||||||
@@ -164,6 +167,7 @@ extension UNMutableNotificationContent {
|
|||||||
|
|
||||||
// Donate the interaction before updating notification content.
|
// Donate the interaction before updating notification content.
|
||||||
try await interaction.donate()
|
try await interaction.donate()
|
||||||
|
|
||||||
// Update notification content before displaying the
|
// Update notification content before displaying the
|
||||||
// communication notification.
|
// communication notification.
|
||||||
let updatedContent = try updating(from: intent)
|
let updatedContent = try updating(from: intent)
|
||||||
|
|||||||
@@ -41,32 +41,4 @@ extension NotificationItemProxyProtocol {
|
|||||||
var isDM: Bool {
|
var isDM: Bool {
|
||||||
isRoomDirect && roomJoinedMembers <= 2
|
isRoomDirect && roomJoinedMembers <= 2
|
||||||
}
|
}
|
||||||
|
|
||||||
var hasMedia: Bool {
|
|
||||||
if (isDM && senderAvatarMediaSource != nil) ||
|
|
||||||
(!isDM && roomAvatarMediaSource != nil) {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
switch event {
|
|
||||||
case .invite, .none:
|
|
||||||
return false
|
|
||||||
case .timeline(let event):
|
|
||||||
switch try? event.eventType() {
|
|
||||||
case .state, .none:
|
|
||||||
return false
|
|
||||||
case let .messageLike(content):
|
|
||||||
switch content {
|
|
||||||
case let .roomMessage(messageType, _):
|
|
||||||
switch messageType {
|
|
||||||
case .image, .video, .audio:
|
|
||||||
return true
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,70 +18,78 @@ struct NotificationContentBuilder {
|
|||||||
/// - notificationItem: The notification item
|
/// - notificationItem: The notification item
|
||||||
/// - mediaProvider: Media provider to process also media. May be passed nil to ignore media operations.
|
/// - mediaProvider: Media provider to process also media. May be passed nil to ignore media operations.
|
||||||
/// - Returns: A notification content object if the notification should be displayed. Otherwise nil.
|
/// - Returns: A notification content object if the notification should be displayed. Otherwise nil.
|
||||||
func content(for notificationItem: NotificationItemProxyProtocol, mediaProvider: MediaProviderProtocol?) async throws -> UNMutableNotificationContent {
|
func process(notificationContent: UNMutableNotificationContent,
|
||||||
|
notificationItem: NotificationItemProxyProtocol,
|
||||||
|
mediaProvider: MediaProviderProtocol) async -> UNMutableNotificationContent {
|
||||||
|
notificationContent.receiverID = notificationItem.receiverID
|
||||||
|
notificationContent.roomID = notificationItem.roomID
|
||||||
|
|
||||||
|
switch notificationItem.event {
|
||||||
|
case .timeline(let event):
|
||||||
|
notificationContent.eventID = event.eventId()
|
||||||
|
case .invite, .none:
|
||||||
|
notificationContent.eventID = nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// So that the UI groups notification that are received for the same room but also for the same user
|
||||||
|
// Removing the @ fixes an iOS bug where the notification crashes if the mute button is tapped
|
||||||
|
notificationContent.threadIdentifier = "\(notificationItem.receiverID)\(notificationItem.roomID)".replacingOccurrences(of: "@", with: "")
|
||||||
|
|
||||||
|
MXLog.info("isNoisy: \(notificationItem.isNoisy)")
|
||||||
|
notificationContent.sound = notificationItem.isNoisy ? UNNotificationSound(named: UNNotificationSoundName(rawValue: "message.caf")) : nil
|
||||||
|
|
||||||
switch notificationItem.event {
|
switch notificationItem.event {
|
||||||
case .none:
|
case .none:
|
||||||
return processEmpty(notificationItem: notificationItem)
|
return processEmpty(notificationContent)
|
||||||
case .invite:
|
case .invite:
|
||||||
return try await processInvited(notificationItem: notificationItem, mediaProvider: mediaProvider)
|
return await processInvited(notificationContent: notificationContent,
|
||||||
|
notificationItem: notificationItem,
|
||||||
|
mediaProvider: mediaProvider)
|
||||||
case .timeline(let event):
|
case .timeline(let event):
|
||||||
guard let eventType = try? event.eventType() else {
|
guard let eventType = try? event.eventType(),
|
||||||
return processEmpty(notificationItem: notificationItem)
|
case let .messageLike(content) = eventType else {
|
||||||
|
return processEmpty(notificationContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch eventType {
|
let notificationContent = await processMessageLike(notificationContent: notificationContent,
|
||||||
case let .messageLike(content):
|
notificationItem: notificationItem,
|
||||||
switch content {
|
mediaProvider: mediaProvider)
|
||||||
case .roomMessage(let messageType, _):
|
|
||||||
return try await processRoomMessage(notificationItem: notificationItem, messageType: messageType, mediaProvider: mediaProvider)
|
switch content {
|
||||||
case .poll(let question):
|
case .roomMessage(let messageType, _):
|
||||||
return try await processPollStartEvent(notificationItem: notificationItem, pollQuestion: question, mediaProvider: mediaProvider)
|
return await processRoomMessage(notificationContent: notificationContent,
|
||||||
case .callInvite:
|
notificationItem: notificationItem,
|
||||||
return try await processCallInviteEvent(notificationItem: notificationItem, mediaProvider: mediaProvider)
|
messageType: messageType,
|
||||||
case .callNotify:
|
mediaProvider: mediaProvider)
|
||||||
return try await processCallNotifyEvent(notificationItem: notificationItem, mediaProvider: mediaProvider)
|
case .poll(let question):
|
||||||
default:
|
notificationContent.body = L10n.commonPollSummary(question)
|
||||||
return processEmpty(notificationItem: notificationItem)
|
return notificationContent
|
||||||
}
|
case .callInvite:
|
||||||
case .state:
|
notificationContent.body = L10n.commonUnsupportedCall
|
||||||
return processEmpty(notificationItem: notificationItem)
|
return notificationContent
|
||||||
|
case .callNotify:
|
||||||
|
notificationContent.body = L10n.notificationIncomingCall
|
||||||
|
return notificationContent
|
||||||
|
default:
|
||||||
|
return processEmpty(notificationContent)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
func baseMutableContent(for notificationItem: NotificationItemProxyProtocol) -> UNMutableNotificationContent {
|
private func processEmpty(_ notificationContent: UNMutableNotificationContent) -> UNMutableNotificationContent {
|
||||||
let notification = UNMutableNotificationContent()
|
notificationContent.title = InfoPlistReader(bundle: .app).bundleDisplayName
|
||||||
|
notificationContent.body = L10n.notification
|
||||||
|
notificationContent.categoryIdentifier = NotificationConstants.Category.message
|
||||||
|
|
||||||
notification.receiverID = notificationItem.receiverID
|
return notificationContent
|
||||||
notification.roomID = notificationItem.roomID
|
|
||||||
notification.eventID = switch notificationItem.event {
|
|
||||||
case .timeline(let event): event.eventId()
|
|
||||||
case .invite, .none: nil
|
|
||||||
}
|
|
||||||
// So that the UI groups notification that are received for the same room but also for the same user
|
|
||||||
// Removing the @ fixes an iOS bug where the notification crashes if the mute button is tapped
|
|
||||||
notification.threadIdentifier = "\(notificationItem.receiverID)\(notificationItem.roomID)".replacingOccurrences(of: "@", with: "")
|
|
||||||
|
|
||||||
MXLog.info("isNoisy: \(notificationItem.isNoisy)")
|
|
||||||
notification.sound = notificationItem.isNoisy ? UNNotificationSound(named: UNNotificationSoundName(rawValue: "message.caf")) : nil
|
|
||||||
|
|
||||||
return notification
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func processEmpty(notificationItem: NotificationItemProxyProtocol) -> UNMutableNotificationContent {
|
private func processInvited(notificationContent: UNMutableNotificationContent,
|
||||||
let notification = baseMutableContent(for: notificationItem)
|
notificationItem: NotificationItemProxyProtocol,
|
||||||
notification.title = InfoPlistReader(bundle: .app).bundleDisplayName
|
mediaProvider: MediaProviderProtocol) async -> UNMutableNotificationContent {
|
||||||
notification.body = L10n.notification
|
notificationContent.categoryIdentifier = NotificationConstants.Category.invite
|
||||||
notification.categoryIdentifier = NotificationConstants.Category.message
|
|
||||||
return notification
|
|
||||||
}
|
|
||||||
|
|
||||||
private func processInvited(notificationItem: NotificationItemProxyProtocol, mediaProvider: MediaProviderProtocol?) async throws -> UNMutableNotificationContent {
|
|
||||||
var notification = baseMutableContent(for: notificationItem)
|
|
||||||
|
|
||||||
notification.categoryIdentifier = NotificationConstants.Category.invite
|
|
||||||
|
|
||||||
let body: String
|
let body: String
|
||||||
if !notificationItem.isDM {
|
if !notificationItem.isDM {
|
||||||
@@ -90,82 +98,42 @@ struct NotificationContentBuilder {
|
|||||||
body = L10n.notificationInviteBody
|
body = L10n.notificationInviteBody
|
||||||
}
|
}
|
||||||
|
|
||||||
notification = try await notification.addSenderIcon(using: mediaProvider,
|
notificationContent.body = body
|
||||||
senderID: notificationItem.senderID,
|
|
||||||
senderName: notificationItem.senderDisplayName ?? notificationItem.roomDisplayName,
|
|
||||||
icon: icon(for: notificationItem),
|
|
||||||
forcePlaceholder: settings.hideInviteAvatars)
|
|
||||||
notification.body = body
|
|
||||||
|
|
||||||
return notification
|
do {
|
||||||
}
|
return try await notificationContent.addSenderIcon(senderID: notificationItem.senderID,
|
||||||
|
senderName: notificationItem.senderDisplayName ?? notificationItem.roomDisplayName,
|
||||||
private func processRoomMessage(notificationItem: NotificationItemProxyProtocol, messageType: MessageType, mediaProvider: MediaProviderProtocol?) async throws -> UNMutableNotificationContent {
|
icon: icon(for: notificationItem),
|
||||||
var notification = try await processCommonRoomMessage(notificationItem: notificationItem, mediaProvider: mediaProvider)
|
forcePlaceholder: settings.hideInviteAvatars,
|
||||||
|
mediaProvider: mediaProvider)
|
||||||
let displayName = notificationItem.senderDisplayName ?? notificationItem.roomDisplayName
|
} catch {
|
||||||
notification.body = String(messageEventStringBuilder.buildAttributedString(for: messageType, senderDisplayName: displayName, isOutgoing: false).characters)
|
return notificationContent
|
||||||
|
|
||||||
guard settings.timelineMediaVisibility == .always ||
|
|
||||||
(settings.timelineMediaVisibility == .privateOnly && notificationItem.isRoomPrivate)
|
|
||||||
else { return notification }
|
|
||||||
|
|
||||||
switch messageType {
|
|
||||||
case .image(content: let content):
|
|
||||||
notification = await notification.addMediaAttachment(using: mediaProvider,
|
|
||||||
mediaSource: .init(source: content.source,
|
|
||||||
mimeType: content.info?.mimetype))
|
|
||||||
case .audio(content: let content):
|
|
||||||
notification = await notification.addMediaAttachment(using: mediaProvider,
|
|
||||||
mediaSource: .init(source: content.source,
|
|
||||||
mimeType: content.info?.mimetype))
|
|
||||||
case .video(content: let content):
|
|
||||||
notification = await notification.addMediaAttachment(using: mediaProvider,
|
|
||||||
mediaSource: .init(source: content.source,
|
|
||||||
mimeType: content.info?.mimetype))
|
|
||||||
default:
|
|
||||||
break
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return notification
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private func processPollStartEvent(notificationItem: NotificationItemProxyProtocol, pollQuestion: String, mediaProvider: MediaProviderProtocol?) async throws -> UNMutableNotificationContent {
|
private func processMessageLike(notificationContent: UNMutableNotificationContent,
|
||||||
let notification = try await processCommonRoomMessage(notificationItem: notificationItem, mediaProvider: mediaProvider)
|
notificationItem: NotificationItemProxyProtocol,
|
||||||
notification.body = L10n.commonPollSummary(pollQuestion)
|
mediaProvider: MediaProviderProtocol) async -> UNMutableNotificationContent {
|
||||||
return notification
|
notificationContent.title = notificationItem.senderDisplayName ?? notificationItem.roomDisplayName
|
||||||
}
|
if notificationContent.title != notificationItem.roomDisplayName {
|
||||||
|
notificationContent.subtitle = notificationItem.roomDisplayName
|
||||||
private func processCallInviteEvent(notificationItem: NotificationItemProxyProtocol, mediaProvider: MediaProviderProtocol?) async throws -> UNMutableNotificationContent {
|
|
||||||
let notification = try await processCommonRoomMessage(notificationItem: notificationItem, mediaProvider: mediaProvider)
|
|
||||||
notification.body = L10n.commonUnsupportedCall
|
|
||||||
return notification
|
|
||||||
}
|
|
||||||
|
|
||||||
private func processCallNotifyEvent(notificationItem: NotificationItemProxyProtocol, mediaProvider: MediaProviderProtocol?) async throws -> UNMutableNotificationContent {
|
|
||||||
let notification = try await processCommonRoomMessage(notificationItem: notificationItem, mediaProvider: mediaProvider)
|
|
||||||
notification.body = L10n.notificationIncomingCall
|
|
||||||
return notification
|
|
||||||
}
|
|
||||||
|
|
||||||
private func processCommonRoomMessage(notificationItem: NotificationItemProxyProtocol, mediaProvider: MediaProviderProtocol?) async throws -> UNMutableNotificationContent {
|
|
||||||
var notification = baseMutableContent(for: notificationItem)
|
|
||||||
notification.title = notificationItem.senderDisplayName ?? notificationItem.roomDisplayName
|
|
||||||
if notification.title != notificationItem.roomDisplayName {
|
|
||||||
notification.subtitle = notificationItem.roomDisplayName
|
|
||||||
}
|
}
|
||||||
notification.categoryIdentifier = NotificationConstants.Category.message
|
notificationContent.categoryIdentifier = NotificationConstants.Category.message
|
||||||
|
|
||||||
let senderName = if let displayName = notificationItem.senderDisplayName {
|
let senderName = if let displayName = notificationItem.senderDisplayName {
|
||||||
notificationItem.hasMention ? L10n.notificationSenderMentionReply(displayName) : displayName
|
notificationItem.hasMention ? L10n.notificationSenderMentionReply(displayName) : displayName
|
||||||
} else {
|
} else {
|
||||||
notificationItem.roomDisplayName
|
notificationItem.roomDisplayName
|
||||||
}
|
}
|
||||||
notification = try await notification.addSenderIcon(using: mediaProvider,
|
|
||||||
senderID: notificationItem.senderID,
|
do {
|
||||||
senderName: senderName,
|
return try await notificationContent.addSenderIcon(senderID: notificationItem.senderID,
|
||||||
icon: icon(for: notificationItem))
|
senderName: senderName,
|
||||||
return notification
|
icon: icon(for: notificationItem),
|
||||||
|
mediaProvider: mediaProvider)
|
||||||
|
} catch {
|
||||||
|
return notificationContent
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func icon(for notificationItem: NotificationItemProxyProtocol) -> NotificationIcon {
|
func icon(for notificationItem: NotificationItemProxyProtocol) -> NotificationIcon {
|
||||||
@@ -176,4 +144,37 @@ struct NotificationContentBuilder {
|
|||||||
groupInfo: .init(name: notificationItem.roomDisplayName, id: notificationItem.roomID))
|
groupInfo: .init(name: notificationItem.roomDisplayName, id: notificationItem.roomID))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func processRoomMessage(notificationContent: UNMutableNotificationContent,
|
||||||
|
notificationItem: NotificationItemProxyProtocol,
|
||||||
|
messageType: MessageType,
|
||||||
|
mediaProvider: MediaProviderProtocol) async -> UNMutableNotificationContent {
|
||||||
|
let displayName = notificationItem.senderDisplayName ?? notificationItem.roomDisplayName
|
||||||
|
notificationContent.body = String(messageEventStringBuilder.buildAttributedString(for: messageType, senderDisplayName: displayName, isOutgoing: false).characters)
|
||||||
|
|
||||||
|
guard settings.timelineMediaVisibility == .always ||
|
||||||
|
(settings.timelineMediaVisibility == .privateOnly && notificationItem.isRoomPrivate)
|
||||||
|
else {
|
||||||
|
return notificationContent
|
||||||
|
}
|
||||||
|
|
||||||
|
switch messageType {
|
||||||
|
case .image(content: let content):
|
||||||
|
await notificationContent.addMediaAttachment(using: mediaProvider,
|
||||||
|
mediaSource: .init(source: content.source,
|
||||||
|
mimeType: content.info?.mimetype))
|
||||||
|
case .audio(content: let content):
|
||||||
|
await notificationContent.addMediaAttachment(using: mediaProvider,
|
||||||
|
mediaSource: .init(source: content.source,
|
||||||
|
mimeType: content.info?.mimetype))
|
||||||
|
case .video(content: let content):
|
||||||
|
await notificationContent.addMediaAttachment(using: mediaProvider,
|
||||||
|
mediaSource: .init(source: content.source,
|
||||||
|
mimeType: content.info?.mimetype))
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
return notificationContent
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,8 +41,8 @@ private let notificationContentBuilder = NotificationContentBuilder(messageEvent
|
|||||||
settings: settings)
|
settings: settings)
|
||||||
|
|
||||||
class NotificationServiceExtension: UNNotificationServiceExtension {
|
class NotificationServiceExtension: UNNotificationServiceExtension {
|
||||||
private var handler: ((UNNotificationContent) -> Void)?
|
private var contentHandler: ((UNNotificationContent) -> Void)!
|
||||||
private var modifiedContent: UNMutableNotificationContent?
|
private var notificationContent: UNMutableNotificationContent!
|
||||||
|
|
||||||
private let appHooks = AppHooks()
|
private let appHooks = AppHooks()
|
||||||
|
|
||||||
@@ -55,9 +55,9 @@ class NotificationServiceExtension: UNNotificationServiceExtension {
|
|||||||
override func didReceive(_ request: UNNotificationRequest,
|
override func didReceive(_ request: UNNotificationRequest,
|
||||||
withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
|
withContentHandler contentHandler: @escaping (UNNotificationContent) -> Void) {
|
||||||
guard !DataProtectionManager.isDeviceLockedAfterReboot(containerURL: URL.appGroupContainerDirectory),
|
guard !DataProtectionManager.isDeviceLockedAfterReboot(containerURL: URL.appGroupContainerDirectory),
|
||||||
let roomID = request.roomID,
|
let roomID = request.content.roomID,
|
||||||
let eventID = request.eventID,
|
let eventID = request.content.eventID,
|
||||||
let clientID = request.pusherNotificationClientIdentifier,
|
let clientID = request.content.pusherNotificationClientIdentifier,
|
||||||
let credentials = keychainController.restorationTokens().first(where: { $0.restorationToken.pusherNotificationClientIdentifier == clientID }) else {
|
let credentials = keychainController.restorationTokens().first(where: { $0.restorationToken.pusherNotificationClientIdentifier == clientID }) else {
|
||||||
// We cannot process this notification, it might be due to one of these:
|
// We cannot process this notification, it might be due to one of these:
|
||||||
// - Device rebooted and locked
|
// - Device rebooted and locked
|
||||||
@@ -67,10 +67,14 @@ class NotificationServiceExtension: UNNotificationServiceExtension {
|
|||||||
return contentHandler(request.content)
|
return contentHandler(request.content)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
guard let mutableContent = request.content.mutableCopy() as? UNMutableNotificationContent else {
|
||||||
|
return contentHandler(request.content)
|
||||||
|
}
|
||||||
|
|
||||||
Target.nse.configure(logLevel: settings.logLevel, traceLogPacks: settings.traceLogPacks)
|
Target.nse.configure(logLevel: settings.logLevel, traceLogPacks: settings.traceLogPacks)
|
||||||
|
|
||||||
handler = contentHandler
|
self.contentHandler = contentHandler
|
||||||
modifiedContent = request.content.mutableCopy() as? UNMutableNotificationContent
|
notificationContent = mutableContent
|
||||||
|
|
||||||
MXLog.info("\(tag) #########################################")
|
MXLog.info("\(tag) #########################################")
|
||||||
|
|
||||||
@@ -90,7 +94,6 @@ class NotificationServiceExtension: UNNotificationServiceExtension {
|
|||||||
|
|
||||||
await processEvent(eventID,
|
await processEvent(eventID,
|
||||||
roomID: roomID,
|
roomID: roomID,
|
||||||
unreadCount: request.unreadCount,
|
|
||||||
userSession: userSession)
|
userSession: userSession)
|
||||||
} catch {
|
} catch {
|
||||||
MXLog.error("Failed creating user session with error: \(error)")
|
MXLog.error("Failed creating user session with error: \(error)")
|
||||||
@@ -102,77 +105,51 @@ class NotificationServiceExtension: UNNotificationServiceExtension {
|
|||||||
// Called just before the extension will be terminated by the system.
|
// Called just before the extension will be terminated by the system.
|
||||||
// Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
|
// Use this as an opportunity to deliver your "best attempt" at modified content, otherwise the original push payload will be used.
|
||||||
MXLog.warning("\(tag) Extension time will expire")
|
MXLog.warning("\(tag) Extension time will expire")
|
||||||
notify(unreadCount: nil)
|
deliverNotification()
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
private func processEvent(_ eventID: String,
|
private func processEvent(_ eventID: String,
|
||||||
roomID: String,
|
roomID: String,
|
||||||
unreadCount: Int?,
|
|
||||||
userSession: NSEUserSession) async {
|
userSession: NSEUserSession) async {
|
||||||
MXLog.info("\(tag) Processing event: \(eventID) in room: \(roomID)")
|
MXLog.info("\(tag) Processing event: \(eventID) in room: \(roomID)")
|
||||||
|
|
||||||
do {
|
guard let notificationItemProxy = await userSession.notificationItemProxy(roomID: roomID, eventID: eventID) else {
|
||||||
guard let itemProxy = await userSession.notificationItemProxy(roomID: roomID, eventID: eventID) else {
|
MXLog.error("\(tag) Failed retrieving notification item")
|
||||||
MXLog.error("\(tag) Failed retrieving notification item")
|
discardNotification()
|
||||||
return discard(unreadCount: unreadCount)
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
switch await preprocessNotification(itemProxy) {
|
switch await preprocessNotification(notificationItemProxy) {
|
||||||
case .processedShouldDiscard, .unsupportedShouldDiscard:
|
case .processedShouldDiscard, .unsupportedShouldDiscard:
|
||||||
return discard(unreadCount: unreadCount)
|
discardNotification()
|
||||||
case .shouldDisplay:
|
case .shouldDisplay:
|
||||||
break
|
notificationContent = await notificationContentBuilder.process(notificationContent: notificationContent,
|
||||||
}
|
notificationItem: notificationItemProxy,
|
||||||
|
mediaProvider: userSession.mediaProvider)
|
||||||
|
|
||||||
modifiedContent = try await notificationContentBuilder.content(for: itemProxy, mediaProvider: nil)
|
deliverNotification()
|
||||||
|
|
||||||
guard itemProxy.hasMedia else {
|
|
||||||
MXLog.info("\(tag) Notification item doesn't contain media")
|
|
||||||
return notify(unreadCount: unreadCount)
|
|
||||||
}
|
|
||||||
|
|
||||||
MXLog.info("\(tag) Processing media")
|
|
||||||
if let latestContent = try? await notificationContentBuilder.content(for: itemProxy, mediaProvider: userSession.mediaProvider) {
|
|
||||||
modifiedContent = latestContent
|
|
||||||
} else {
|
|
||||||
MXLog.error("\(tag) Failed processing notification media")
|
|
||||||
}
|
|
||||||
|
|
||||||
return notify(unreadCount: unreadCount)
|
|
||||||
} catch {
|
|
||||||
MXLog.error("Failed processing with error: \(error)")
|
|
||||||
return discard(unreadCount: unreadCount)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func notify(unreadCount: Int?) {
|
private func deliverNotification() {
|
||||||
guard let modifiedContent else {
|
|
||||||
MXLog.error("\(tag) Notification modified content invalid")
|
|
||||||
return discard(unreadCount: unreadCount)
|
|
||||||
}
|
|
||||||
|
|
||||||
MXLog.info("\(tag) Displaying notification")
|
MXLog.info("\(tag) Displaying notification")
|
||||||
|
|
||||||
if let unreadCount {
|
contentHandler(notificationContent)
|
||||||
modifiedContent.badge = NSNumber(value: unreadCount)
|
|
||||||
}
|
|
||||||
|
|
||||||
handler?(modifiedContent)
|
|
||||||
cleanUp()
|
cleanUp()
|
||||||
}
|
}
|
||||||
|
|
||||||
private func discard(unreadCount: Int?) {
|
private func discardNotification() {
|
||||||
MXLog.info("\(tag) Discarding notification")
|
MXLog.info("\(tag) Discarding notification")
|
||||||
|
|
||||||
let content = UNMutableNotificationContent()
|
let content = UNMutableNotificationContent()
|
||||||
|
|
||||||
if let unreadCount {
|
if let unreadCount = notificationContent.unreadCount {
|
||||||
content.badge = NSNumber(value: unreadCount)
|
content.badge = NSNumber(value: unreadCount)
|
||||||
}
|
}
|
||||||
|
|
||||||
handler?(content)
|
contentHandler(content)
|
||||||
cleanUp()
|
cleanUp()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -181,8 +158,8 @@ class NotificationServiceExtension: UNNotificationServiceExtension {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func cleanUp() {
|
private func cleanUp() {
|
||||||
handler = nil
|
contentHandler = nil
|
||||||
modifiedContent = nil
|
notificationContent = nil
|
||||||
}
|
}
|
||||||
|
|
||||||
private func preprocessNotification(_ itemProxy: NotificationItemProxyProtocol) async -> NotificationProcessingResult {
|
private func preprocessNotification(_ itemProxy: NotificationItemProxyProtocol) async -> NotificationProcessingResult {
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
//
|
|
||||||
// Copyright 2022-2024 New Vector Ltd.
|
|
||||||
//
|
|
||||||
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|
||||||
// Please see LICENSE files in the repository root for full details.
|
|
||||||
//
|
|
||||||
|
|
||||||
import Foundation
|
|
||||||
import UserNotifications
|
|
||||||
|
|
||||||
extension UNNotificationRequest {
|
|
||||||
var roomID: String? {
|
|
||||||
content.userInfo[NotificationConstants.UserInfoKey.roomIdentifier] as? String
|
|
||||||
}
|
|
||||||
|
|
||||||
var eventID: String? {
|
|
||||||
content.userInfo[NotificationConstants.UserInfoKey.eventIdentifier] as? String
|
|
||||||
}
|
|
||||||
|
|
||||||
var unreadCount: Int? {
|
|
||||||
content.userInfo[NotificationConstants.UserInfoKey.unreadCount] as? Int
|
|
||||||
}
|
|
||||||
|
|
||||||
var pusherNotificationClientIdentifier: String? {
|
|
||||||
content.userInfo[NotificationConstants.UserInfoKey.pusherNotificationClientIdentifier] as? String
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user