Disable Local Push Notifications + Start Handling invite notifications (#882)

* disabled local pushes and fixed the issue with the image rendering

* disabled local notifications for now and started implementing invite notificatons

* disabled local notifications for now and started implementing invite notificatons

* removed code that should not have changed

* removed useless comment

* refactored name for category

* fixed a test

* fix

* fix for missing try await

* pr comments
This commit is contained in:
Mauro
2023-05-12 12:45:59 +02:00
committed by GitHub
parent 185710adf4
commit 23137726de
9 changed files with 133 additions and 74 deletions
@@ -11,7 +11,7 @@
{ {
"identity" : "compound-ios", "identity" : "compound-ios",
"kind" : "remoteSourceControl", "kind" : "remoteSourceControl",
"location" : "https://github.com/vector-im/compound-ios", "location" : "https://github.com/vector-im/compound-ios.git",
"state" : { "state" : {
"revision" : "b4f34edce9003ba1fde31500312f317b728f4ee3" "revision" : "b4f34edce9003ba1fde31500312f317b728f4ee3"
} }
@@ -507,15 +507,14 @@ extension AppCoordinator: NotificationManagerDelegate {
func notificationTapped(_ service: NotificationManagerProtocol, content: UNNotificationContent) async { func notificationTapped(_ service: NotificationManagerProtocol, content: UNNotificationContent) async {
MXLog.info("[AppCoordinator] tappedNotification") MXLog.info("[AppCoordinator] tappedNotification")
// We store the room identifier into the thread identifier guard let roomID = content.roomID,
guard !content.threadIdentifier.isEmpty,
content.receiverID != nil else { content.receiverID != nil else {
return return
} }
// Handle here the account switching when available // Handle here the account switching when available
handleAppRoute(.room(roomID: content.threadIdentifier)) handleAppRoute(.room(roomID: roomID))
} }
func handleInlineReply(_ service: NotificationManagerProtocol, content: UNNotificationContent, replyText: String) async { func handleInlineReply(_ service: NotificationManagerProtocol, content: UNNotificationContent, replyText: String) async {
@@ -155,6 +155,8 @@ final class AppSettings {
@UserPreference(key: UserDefaultsKeys.enableInAppNotifications, defaultValue: true, storageType: .userDefaults(store)) @UserPreference(key: UserDefaultsKeys.enableInAppNotifications, defaultValue: true, storageType: .userDefaults(store))
var enableInAppNotifications var enableInAppNotifications
let enableLocalPushNotifications = false
/// Tag describing which set of device specific rules a pusher executes. /// Tag describing which set of device specific rules a pusher executes.
@UserPreference(key: UserDefaultsKeys.pusherProfileTag, storageType: .userDefaults(store)) @UserPreference(key: UserDefaultsKeys.pusherProfileTag, storageType: .userDefaults(store))
var pusherProfileTag: String? var pusherProfileTag: String?
@@ -125,9 +125,9 @@ extension UNMutableNotificationContent {
iconType: NotificationIconType) async throws -> UNMutableNotificationContent { iconType: NotificationIconType) async throws -> UNMutableNotificationContent {
var image: INImage? var image: INImage?
if let mediaSource = iconType.mediaSource { if let mediaSource = iconType.mediaSource {
switch await mediaProvider?.loadFileFromSource(mediaSource) { switch await mediaProvider?.loadImageDataFromSource(mediaSource) {
case .success(let mediaFile): case .success(let data):
image = try INImage(imageData: Data(contentsOf: mediaFile.url)) image = INImage(imageData: data)
case .failure(let error): case .failure(let error):
MXLog.error("Couldn't add sender icon: \(error)") MXLog.error("Couldn't add sender icon: \(error)")
case .none: case .none:
@@ -110,8 +110,10 @@ class ClientProxy: ClientProxyProtocol {
client.setDelegate(delegate: delegate) client.setDelegate(delegate: delegate)
// Set up sync listener for generating local notifications. // Set up sync listener for generating local notifications.
await Task.dispatch(on: clientQueue) { if ServiceLocator.shared.settings.enableLocalPushNotifications {
client.setNotificationDelegate(notificationDelegate: delegate) await Task.dispatch(on: clientQueue) {
client.setNotificationDelegate(notificationDelegate: delegate)
}
} }
configureSlidingSync() configureSlidingSync()
@@ -326,7 +328,9 @@ class ClientProxy: ClientProxyProtocol {
buildAndConfigureVisibleRoomsSlidingSyncList() buildAndConfigureVisibleRoomsSlidingSyncList()
buildAndConfigureAllRoomsSlidingSyncList() buildAndConfigureAllRoomsSlidingSyncList()
buildAndConfigureInvitesSlidingSyncList() buildAndConfigureInvitesSlidingSyncList()
buildAndConfigureNotificationsSlidingSyncList() if ServiceLocator.shared.settings.enableLocalPushNotifications {
buildAndConfigureNotificationsSlidingSyncList()
}
guard let visibleRoomsListBuilder else { guard let visibleRoomsListBuilder else {
MXLog.error("Visible rooms sliding sync view unavailable") MXLog.error("Visible rooms sliding sync view unavailable")
@@ -537,11 +541,13 @@ class ClientProxy: ClientProxyProtocol {
MXLog.error("Invites sliding sync view unavailable") MXLog.error("Invites sliding sync view unavailable")
} }
if let notificationsListBuilder { if ServiceLocator.shared.settings.enableLocalPushNotifications {
MXLog.info("Registering notifications view") if let notificationsListBuilder {
_ = slidingSync?.addList(listBuilder: notificationsListBuilder) MXLog.info("Registering notifications view")
} else { _ = slidingSync?.addList(listBuilder: notificationsListBuilder)
MXLog.error("Notifications sliding sync view unavailable") } else {
MXLog.error("Notifications sliding sync view unavailable")
}
} }
} }
@@ -34,14 +34,19 @@ class NotificationManager: NSObject, NotificationManagerProtocol {
weak var delegate: NotificationManagerDelegate? weak var delegate: NotificationManagerDelegate?
func start() { func start() {
let replyAction = UNTextInputNotificationAction(identifier: NotificationConstants.Action.inlineReply, // Not implemented yet
title: L10n.actionQuickReply, // let replyAction = UNTextInputNotificationAction(identifier: NotificationConstants.Action.inlineReply,
options: []) // title: L10n.actionQuickReply,
let replyCategory = UNNotificationCategory(identifier: NotificationConstants.Category.reply, // options: [])
actions: [replyAction], let messageCategory = UNNotificationCategory(identifier: NotificationConstants.Category.message,
intentIdentifiers: [], actions: [],
options: []) intentIdentifiers: [],
notificationCenter.setNotificationCategories([replyCategory]) options: [])
let inviteCategory = UNNotificationCategory(identifier: NotificationConstants.Category.invite,
actions: [],
intentIdentifiers: [],
options: [])
notificationCenter.setNotificationCategories([messageCategory, inviteCategory])
notificationCenter.delegate = self notificationCenter.delegate = self
} }
@@ -28,7 +28,8 @@ enum NotificationConstants {
enum Category { enum Category {
static let discard = "discard" static let discard = "discard"
static let reply = "reply" static let message = "message"
static let invite = "invite"
} }
enum Action { enum Action {
@@ -33,13 +33,15 @@ protocol NotificationItemProxyProtocol {
var roomDisplayName: String { get } var roomDisplayName: String { get }
var roomCanonicalAlias: String? { get }
var roomAvatarMediaSource: MediaSourceProxy? { get } var roomAvatarMediaSource: MediaSourceProxy? { get }
var isNoisy: Bool { get } var isNoisy: Bool { get }
var isDirect: Bool { get } var isDirect: Bool { get }
var isEncrypted: Bool { get } var isEncrypted: Bool? { get }
} }
extension NotificationItemProxyProtocol { extension NotificationItemProxyProtocol {
@@ -73,6 +75,10 @@ struct NotificationItemProxy: NotificationItemProxyProtocol {
notificationItem.roomDisplayName notificationItem.roomDisplayName
} }
var roomCanonicalAlias: String? {
notificationItem.roomCanonicalAlias
}
var isNoisy: Bool { var isNoisy: Bool {
notificationItem.isNoisy notificationItem.isNoisy
} }
@@ -81,8 +87,8 @@ struct NotificationItemProxy: NotificationItemProxyProtocol {
notificationItem.isDirect notificationItem.isDirect
} }
var isEncrypted: Bool { var isEncrypted: Bool? {
notificationItem.isEncrypted ?? false notificationItem.isEncrypted
} }
var senderAvatarMediaSource: MediaSourceProxy? { var senderAvatarMediaSource: MediaSourceProxy? {
@@ -119,13 +125,15 @@ struct EmptyNotificationItemProxy: NotificationItemProxyProtocol {
var roomDisplayName: String { "" } var roomDisplayName: String { "" }
var roomCanonicalAlias: String? { nil }
var roomAvatarURL: String? { nil } var roomAvatarURL: String? { nil }
var isNoisy: Bool { false } var isNoisy: Bool { false }
var isDirect: Bool { false } var isDirect: Bool { false }
var isEncrypted: Bool { false } var isEncrypted: Bool? { nil }
var senderAvatarMediaSource: MediaSourceProxy? { nil } var senderAvatarMediaSource: MediaSourceProxy? { nil }
@@ -135,6 +143,18 @@ struct EmptyNotificationItemProxy: NotificationItemProxyProtocol {
} }
extension NotificationItemProxyProtocol { extension NotificationItemProxyProtocol {
var baseMutableContent: UNMutableNotificationContent {
let notification = UNMutableNotificationContent()
notification.receiverID = receiverID
notification.roomID = roomID
notification.eventID = event.eventID
notification.notificationID = id
notification.sound = isNoisy ? UNNotificationSound(named: UNNotificationSoundName(rawValue: "message.caf")) : nil
// So that the UI groups notification that are received for the same room but also for the same user
notification.threadIdentifier = "\(receiverID)\(roomID)"
return notification
}
var requiresMediaProvider: Bool { var requiresMediaProvider: Bool {
if senderAvatarMediaSource != nil || roomAvatarMediaSource != nil { if senderAvatarMediaSource != nil || roomAvatarMediaSource != nil {
return true return true
@@ -157,7 +177,6 @@ extension NotificationItemProxyProtocol {
} }
} }
// swiftlint: disable cyclomatic_complexity
/// Process the receiver item proxy /// Process the receiver item proxy
/// - Parameters: /// - Parameters:
/// - receiverId: identifier of the user that has received the notification /// - receiverId: identifier of the user that has received the notification
@@ -169,27 +188,14 @@ extension NotificationItemProxyProtocol {
return processEmpty() return processEmpty()
} else { } else {
switch event.type { switch event.type {
case .none, .state: case .none:
return processEmpty() return processEmpty()
case let .state(content):
return try await processStateEvent(content: content, mediaProvider: mediaProvider)
case let .messageLike(content): case let .messageLike(content):
switch content { switch content {
case .roomMessage(messageType: let messageType): case .roomMessage(messageType: let messageType):
switch messageType { return try await processRoomMessage(messageType: messageType, mediaProvider: mediaProvider)
case .emote(content: let content):
return try await processEmote(content: content, mediaProvider: mediaProvider)
case .image(content: let content):
return try await processImage(content: content, mediaProvider: mediaProvider)
case .audio(content: let content):
return try await processAudio(content: content, mediaProvider: mediaProvider)
case .video(content: let content):
return try await processVideo(content: content, mediaProvider: mediaProvider)
case .file(content: let content):
return try await processFile(content: content, mediaProvider: mediaProvider)
case .notice(content: let content):
return try await processNotice(content: content, mediaProvider: mediaProvider)
case .text(content: let content):
return try await processText(content: content, mediaProvider: mediaProvider)
}
default: default:
return processEmpty() return processEmpty()
} }
@@ -197,39 +203,75 @@ extension NotificationItemProxyProtocol {
} }
} }
// swiftlint: enable cyclomatic_complexity
// MARK: - Private // MARK: - Private
private func processStateEvent(content: StateEventContent, mediaProvider: MediaProviderProtocol?) async throws -> UNMutableNotificationContent {
switch content {
case let .roomMemberContent(userId, membershipState):
switch membershipState {
case .invite:
if userId == receiverID {
return try await processInvited(mediaProvider: mediaProvider)
} else {
return processEmpty()
}
default:
return processEmpty()
}
default:
return processEmpty()
}
}
private func processInvited(mediaProvider: MediaProviderProtocol?) async throws -> UNMutableNotificationContent {
var notification = baseMutableContent
notification.categoryIdentifier = NotificationConstants.Category.invite
// Sadly as of right now we can't get from the NSE context any information for invited rooms, so we will only display the user name and a simple message
let iconType = NotificationIconType.sender(mediaSource: senderAvatarMediaSource)
notification = try await notification.addSenderIcon(using: mediaProvider,
senderID: event.senderID,
senderName: senderDisplayName ?? event.senderID,
iconType: iconType)
notification.body = L10n.screenInvitesInvitedYou("")
return notification
}
private func processRoomMessage(messageType: MessageType, mediaProvider: MediaProviderProtocol?) async throws -> UNMutableNotificationContent {
switch messageType {
case .emote(content: let content):
return try await processEmote(content: content, mediaProvider: mediaProvider)
case .image(content: let content):
return try await processImage(content: content, mediaProvider: mediaProvider)
case .audio(content: let content):
return try await processAudio(content: content, mediaProvider: mediaProvider)
case .video(content: let content):
return try await processVideo(content: content, mediaProvider: mediaProvider)
case .file(content: let content):
return try await processFile(content: content, mediaProvider: mediaProvider)
case .notice(content: let content):
return try await processNotice(content: content, mediaProvider: mediaProvider)
case .text(content: let content):
return try await processText(content: content, mediaProvider: mediaProvider)
}
}
private func processEmpty() -> UNMutableNotificationContent { private func processEmpty() -> UNMutableNotificationContent {
let notification = UNMutableNotificationContent() let notification = baseMutableContent
notification.receiverID = receiverID
notification.roomID = roomID
notification.eventID = event.eventID
notification.notificationID = id
notification.title = InfoPlistReader(bundle: .app).bundleDisplayName notification.title = InfoPlistReader(bundle: .app).bundleDisplayName
notification.body = L10n.notification notification.body = L10n.notification
notification.threadIdentifier = roomID notification.categoryIdentifier = NotificationConstants.Category.message
notification.categoryIdentifier = NotificationConstants.Category.reply
notification.sound = isNoisy ? UNNotificationSound(named: UNNotificationSoundName(rawValue: "message.caf")) : nil
return notification return notification
} }
private func processCommon(mediaProvider: MediaProviderProtocol?) async throws -> UNMutableNotificationContent { private func processCommon(mediaProvider: MediaProviderProtocol?) async throws -> UNMutableNotificationContent {
var notification = UNMutableNotificationContent() var notification = baseMutableContent
notification.receiverID = receiverID
notification.roomID = roomID
notification.eventID = event.eventID
notification.notificationID = id
notification.title = senderDisplayName ?? roomDisplayName notification.title = senderDisplayName ?? roomDisplayName
if notification.title != roomDisplayName { if notification.title != roomDisplayName {
notification.subtitle = roomDisplayName notification.subtitle = roomDisplayName
} }
// We can store the room identifier into the thread identifier since it's used for notifications notification.categoryIdentifier = NotificationConstants.Category.message
// that belong to the same group
notification.threadIdentifier = roomID
notification.categoryIdentifier = NotificationConstants.Category.reply
notification.sound = isNoisy ? UNNotificationSound(named: UNNotificationSoundName(rawValue: "message.caf")) : nil
let senderName = senderDisplayName ?? roomDisplayName let senderName = senderDisplayName ?? roomDisplayName
let iconType: NotificationIconType let iconType: NotificationIconType
@@ -101,14 +101,18 @@ final class NotificationManagerTests: XCTestCase {
} }
func test_whenStart_notificationCategoriesAreSet() throws { func test_whenStart_notificationCategoriesAreSet() throws {
let replyAction = UNTextInputNotificationAction(identifier: NotificationConstants.Action.inlineReply, // let replyAction = UNTextInputNotificationAction(identifier: NotificationConstants.Action.inlineReply,
title: L10n.actionQuickReply, // title: L10n.actionQuickReply,
options: []) // options: [])
let replyCategory = UNNotificationCategory(identifier: NotificationConstants.Category.reply, let messageCategory = UNNotificationCategory(identifier: NotificationConstants.Category.message,
actions: [replyAction], actions: [],
intentIdentifiers: [], intentIdentifiers: [],
options: []) options: [])
XCTAssertEqual(notificationCenter.notificationCategoriesValue, [replyCategory]) let inviteCategory = UNNotificationCategory(identifier: NotificationConstants.Category.invite,
actions: [],
intentIdentifiers: [],
options: [])
XCTAssertEqual(notificationCenter.notificationCategoriesValue, [messageCategory, inviteCategory])
} }
func test_whenStart_delegateIsSet() throws { func test_whenStart_delegateIsSet() throws {