2023-09-13 12:30:41 +03:00
|
|
|
//
|
2024-09-06 16:34:30 +03:00
|
|
|
// Copyright 2023, 2024 New Vector Ltd.
|
2023-09-13 12:30:41 +03:00
|
|
|
//
|
2025-01-06 11:27:37 +01:00
|
|
|
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial
|
|
|
|
// Please see LICENSE files in the repository root for full details.
|
2023-09-13 12:30:41 +03:00
|
|
|
//
|
|
|
|
|
|
|
|
import Foundation
|
|
|
|
import MatrixRustSDK
|
|
|
|
import UserNotifications
|
|
|
|
|
|
|
|
struct NotificationContentBuilder {
|
|
|
|
let messageEventStringBuilder: RoomMessageEventStringBuilder
|
2024-10-03 17:59:39 +01:00
|
|
|
let settings: CommonSettingsProtocol
|
2023-09-13 12:30:41 +03:00
|
|
|
|
|
|
|
/// Process the given notification item proxy
|
|
|
|
/// - Parameters:
|
|
|
|
/// - notificationItem: The notification item
|
|
|
|
/// - 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.
|
|
|
|
func content(for notificationItem: NotificationItemProxyProtocol, mediaProvider: MediaProviderProtocol?) async throws -> UNMutableNotificationContent {
|
|
|
|
switch notificationItem.event {
|
|
|
|
case .none:
|
|
|
|
return processEmpty(notificationItem: notificationItem)
|
|
|
|
case .invite:
|
|
|
|
return try await processInvited(notificationItem: notificationItem, mediaProvider: mediaProvider)
|
|
|
|
case .timeline(let event):
|
2023-09-28 11:21:17 +02:00
|
|
|
guard let eventType = try? event.eventType() else {
|
|
|
|
return processEmpty(notificationItem: notificationItem)
|
|
|
|
}
|
|
|
|
|
|
|
|
switch eventType {
|
2023-09-13 12:30:41 +03:00
|
|
|
case let .messageLike(content):
|
|
|
|
switch content {
|
2023-09-15 10:01:09 +01:00
|
|
|
case .roomMessage(let messageType, _):
|
2023-09-13 12:30:41 +03:00
|
|
|
return try await processRoomMessage(notificationItem: notificationItem, messageType: messageType, mediaProvider: mediaProvider)
|
2023-09-28 11:21:17 +02:00
|
|
|
case .poll(let question):
|
|
|
|
return try await processPollStartEvent(notificationItem: notificationItem, pollQuestion: question, mediaProvider: mediaProvider)
|
2024-03-02 19:12:02 +02:00
|
|
|
case .callInvite:
|
|
|
|
return try await processCallInviteEvent(notificationItem: notificationItem, mediaProvider: mediaProvider)
|
2024-05-17 15:13:57 +03:00
|
|
|
case .callNotify:
|
|
|
|
return try await processCallNotifyEvent(notificationItem: notificationItem, mediaProvider: mediaProvider)
|
2023-09-13 12:30:41 +03:00
|
|
|
default:
|
|
|
|
return processEmpty(notificationItem: notificationItem)
|
|
|
|
}
|
2023-09-28 11:21:17 +02:00
|
|
|
case .state:
|
2023-09-13 12:30:41 +03:00
|
|
|
return processEmpty(notificationItem: notificationItem)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// MARK: - Private
|
|
|
|
|
|
|
|
func baseMutableContent(for notificationItem: NotificationItemProxyProtocol) -> UNMutableNotificationContent {
|
|
|
|
let notification = UNMutableNotificationContent()
|
2025-02-11 09:59:46 +00:00
|
|
|
|
2023-09-13 12:30:41 +03:00
|
|
|
notification.receiverID = notificationItem.receiverID
|
|
|
|
notification.roomID = notificationItem.roomID
|
2024-08-22 13:49:00 +01:00
|
|
|
notification.eventID = switch notificationItem.event {
|
|
|
|
case .timeline(let event): event.eventId()
|
|
|
|
case .invite, .none: nil
|
|
|
|
}
|
2023-09-13 12:30:41 +03:00
|
|
|
// 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: "")
|
2025-02-11 09:59:46 +00:00
|
|
|
|
|
|
|
MXLog.info("isNoisy: \(notificationItem.isNoisy)")
|
|
|
|
notification.sound = notificationItem.isNoisy ? UNNotificationSound(named: UNNotificationSoundName(rawValue: "message.caf")) : nil
|
|
|
|
|
2023-09-13 12:30:41 +03:00
|
|
|
return notification
|
|
|
|
}
|
|
|
|
|
|
|
|
private func processEmpty(notificationItem: NotificationItemProxyProtocol) -> UNMutableNotificationContent {
|
|
|
|
let notification = baseMutableContent(for: notificationItem)
|
|
|
|
notification.title = InfoPlistReader(bundle: .app).bundleDisplayName
|
|
|
|
notification.body = L10n.notification
|
|
|
|
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
|
|
|
|
if !notificationItem.isDM {
|
|
|
|
body = L10n.notificationRoomInviteBody
|
|
|
|
} else {
|
|
|
|
body = L10n.notificationInviteBody
|
|
|
|
}
|
|
|
|
|
|
|
|
notification = try await notification.addSenderIcon(using: mediaProvider,
|
|
|
|
senderID: notificationItem.senderID,
|
|
|
|
senderName: notificationItem.senderDisplayName ?? notificationItem.roomDisplayName,
|
|
|
|
icon: icon(for: notificationItem))
|
|
|
|
notification.body = body
|
|
|
|
|
|
|
|
return notification
|
|
|
|
}
|
|
|
|
|
|
|
|
private func processRoomMessage(notificationItem: NotificationItemProxyProtocol, messageType: MessageType, mediaProvider: MediaProviderProtocol?) async throws -> UNMutableNotificationContent {
|
|
|
|
var notification = try await processCommonRoomMessage(notificationItem: notificationItem, mediaProvider: mediaProvider)
|
|
|
|
|
2023-11-09 19:12:11 +01:00
|
|
|
let displayName = notificationItem.senderDisplayName ?? notificationItem.roomDisplayName
|
2024-08-22 12:51:05 +01:00
|
|
|
notification.body = String(messageEventStringBuilder.buildAttributedString(for: messageType, senderDisplayName: displayName).characters)
|
2023-09-13 12:30:41 +03:00
|
|
|
|
2024-10-03 17:59:39 +01:00
|
|
|
guard !settings.hideTimelineMedia else { return notification }
|
|
|
|
|
2023-09-13 12:30:41 +03:00
|
|
|
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
|
|
|
|
}
|
|
|
|
|
2023-09-28 11:21:17 +02:00
|
|
|
private func processPollStartEvent(notificationItem: NotificationItemProxyProtocol, pollQuestion: String, mediaProvider: MediaProviderProtocol?) async throws -> UNMutableNotificationContent {
|
|
|
|
let notification = try await processCommonRoomMessage(notificationItem: notificationItem, mediaProvider: mediaProvider)
|
|
|
|
notification.body = L10n.commonPollSummary(pollQuestion)
|
|
|
|
return notification
|
|
|
|
}
|
2024-03-02 19:12:02 +02:00
|
|
|
|
|
|
|
private func processCallInviteEvent(notificationItem: NotificationItemProxyProtocol, mediaProvider: MediaProviderProtocol?) async throws -> UNMutableNotificationContent {
|
|
|
|
let notification = try await processCommonRoomMessage(notificationItem: notificationItem, mediaProvider: mediaProvider)
|
2024-11-11 17:45:06 +00:00
|
|
|
notification.body = L10n.commonUnsupportedCall
|
2024-03-02 19:12:02 +02:00
|
|
|
return notification
|
|
|
|
}
|
2024-05-17 15:13:57 +03:00
|
|
|
|
|
|
|
private func processCallNotifyEvent(notificationItem: NotificationItemProxyProtocol, mediaProvider: MediaProviderProtocol?) async throws -> UNMutableNotificationContent {
|
|
|
|
let notification = try await processCommonRoomMessage(notificationItem: notificationItem, mediaProvider: mediaProvider)
|
2025-01-31 17:02:55 +01:00
|
|
|
notification.body = L10n.notificationIncomingCall
|
2024-05-17 15:13:57 +03:00
|
|
|
return notification
|
|
|
|
}
|
2023-09-28 11:21:17 +02:00
|
|
|
|
2023-09-13 12:30:41 +03:00
|
|
|
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
|
|
|
|
|
2024-08-22 12:51:05 +01:00
|
|
|
let senderName = if let displayName = notificationItem.senderDisplayName {
|
|
|
|
notificationItem.hasMention ? L10n.notificationSenderMentionReply(displayName) : displayName
|
|
|
|
} else {
|
|
|
|
notificationItem.roomDisplayName
|
|
|
|
}
|
2023-09-13 12:30:41 +03:00
|
|
|
notification = try await notification.addSenderIcon(using: mediaProvider,
|
|
|
|
senderID: notificationItem.senderID,
|
2024-08-22 12:51:05 +01:00
|
|
|
senderName: senderName,
|
2023-09-13 12:30:41 +03:00
|
|
|
icon: icon(for: notificationItem))
|
|
|
|
return notification
|
|
|
|
}
|
|
|
|
|
|
|
|
func icon(for notificationItem: NotificationItemProxyProtocol) -> NotificationIcon {
|
|
|
|
if notificationItem.isDM {
|
|
|
|
return NotificationIcon(mediaSource: notificationItem.senderAvatarMediaSource, groupInfo: nil)
|
|
|
|
} else {
|
|
|
|
return NotificationIcon(mediaSource: notificationItem.roomAvatarMediaSource,
|
|
|
|
groupInfo: .init(name: notificationItem.roomDisplayName, id: notificationItem.roomID))
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|