diff --git a/ElementX/Sources/Application/AppCoordinator.swift b/ElementX/Sources/Application/AppCoordinator.swift index 3cfc00b36..71a8c6660 100644 --- a/ElementX/Sources/Application/AppCoordinator.swift +++ b/ElementX/Sources/Application/AppCoordinator.swift @@ -155,8 +155,15 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationCoordinatorDelegate, } // Handle here the account switching when available - - handleAppRoute(.room(roomID: roomID)) + + switch content.categoryIdentifier { + case NotificationConstants.Category.message: + handleAppRoute(.room(roomID: roomID)) + case NotificationConstants.Category.invite: + handleAppRoute(.invites) + default: + break + } } func handleInlineReply(_ service: NotificationManagerProtocol, content: UNNotificationContent, replyText: String) async { diff --git a/ElementX/Sources/Application/FlowCoordinatorProtocol.swift b/ElementX/Sources/Application/FlowCoordinatorProtocol.swift index 8a5caaa4f..bb6061438 100644 --- a/ElementX/Sources/Application/FlowCoordinatorProtocol.swift +++ b/ElementX/Sources/Application/FlowCoordinatorProtocol.swift @@ -19,4 +19,5 @@ import Foundation @MainActor protocol FlowCoordinatorProtocol { func handleAppRoute(_ appRoute: AppRoute, animated: Bool) + func clearRoute(animated: Bool) } diff --git a/ElementX/Sources/Application/Navigation/AppRouter.swift b/ElementX/Sources/Application/Navigation/AppRouter.swift index ac24863d7..1b4a5ac6f 100644 --- a/ElementX/Sources/Application/Navigation/AppRouter.swift +++ b/ElementX/Sources/Application/Navigation/AppRouter.swift @@ -22,6 +22,7 @@ enum AppRoute { case roomList case room(roomID: String) case roomDetails(roomID: String) + case invites } struct AppRouterManager { diff --git a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift index c37a64d69..1866b0f34 100644 --- a/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/RoomFlowCoordinator.swift @@ -71,8 +71,17 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { stateMachine.tryEvent(.presentRoomDetails(roomID: roomID), userInfo: EventUserInfo(animated: animated)) case .roomList: stateMachine.tryEvent(.dismissRoom, userInfo: EventUserInfo(animated: animated)) + case .invites: + break } } + + func clearRoute(animated: Bool) { + guard stateMachine.state != .initial else { + return + } + stateMachine.tryEvent(.dismissRoom, userInfo: EventUserInfo(animated: animated)) + } // MARK: - Private @@ -290,10 +299,10 @@ class RoomFlowCoordinator: FlowCoordinatorProtocol { } private func dismissRoom(animated: Bool) { - // The room isn't in the same navigation stack of the home screen. - // Animating the popToRoot causes weird animations on when the room is left from room's details - navigationStackCoordinator.popToRoot(animated: false) + // Setting the detail coordinator to nil afirst allows the dismiss to work properly + // if followed immediately by another navigation on iPhone navigationSplitCoordinator.setDetailCoordinator(nil, animated: animated) + navigationStackCoordinator.popToRoot(animated: false) roomProxy = nil timelineController = nil diff --git a/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift index 0ed72a49a..2c72f76d7 100644 --- a/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinator.swift @@ -91,13 +91,22 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol { case .roomList, .initial: break } - + switch appRoute { case .room, .roomDetails, .roomList: roomFlowCoordinator.handleAppRoute(appRoute, animated: animated) + case .invites: + if UIDevice.current.isPhone { + roomFlowCoordinator.clearRoute(animated: animated) + } + stateMachine.processEvent(.showInvitesScreen, userInfo: .init(animated: animated)) } } + func clearRoute(animated: Bool) { + fatalError("not necessary as of right now") + } + // MARK: - Private // swiftlint:disable:next cyclomatic_complexity @@ -141,6 +150,8 @@ class UserSessionFlowCoordinator: FlowCoordinatorProtocol { case (.roomList, .showInvitesScreen, .invitesScreen): self.presentInvitesList(animated: animated) + case (.invitesScreen, .showInvitesScreen, .invitesScreen): + break case (.invitesScreen, .closedInvitesScreen, .roomList): break diff --git a/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinatorStateMachine.swift b/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinatorStateMachine.swift index 35ef23c7f..0f6d5ee75 100644 --- a/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinatorStateMachine.swift +++ b/ElementX/Sources/FlowCoordinators/UserSessionFlowCoordinatorStateMachine.swift @@ -131,6 +131,9 @@ class UserSessionFlowCoordinatorStateMachine { case (.showInvitesScreen, .roomList(let selectedRoomId)): return .invitesScreen(selectedRoomId: selectedRoomId) + case (.showInvitesScreen, .invitesScreen(let selectedRoomId)): + return .invitesScreen(selectedRoomId: selectedRoomId) + case (.closedInvitesScreen, .invitesScreen(let selectedRoomId)): return .roomList(selectedRoomId: selectedRoomId) diff --git a/ElementX/Sources/Other/Extensions/UNNotificationContent.swift b/ElementX/Sources/Other/Extensions/UNNotificationContent.swift index 221e92a44..54b1659cf 100644 --- a/ElementX/Sources/Other/Extensions/UNNotificationContent.swift +++ b/ElementX/Sources/Other/Extensions/UNNotificationContent.swift @@ -18,24 +18,13 @@ import Foundation import Intents import UserNotifications -enum NotificationIconType { - case sender(mediaSource: MediaSourceProxy?) - case group(mediaSource: MediaSourceProxy?, groupName: String) +struct NotificationIcon { + let mediaSource: MediaSourceProxy? + // Required as the key to set images for groups + let groupName: String? - var mediaSource: MediaSourceProxy? { - switch self { - case .group(let mediaSource, _), .sender(let mediaSource): - return mediaSource - } - } - - var isSenderIcon: Bool { - switch self { - case .sender: - return true - case .group: - return false - } + var shouldDisplayAsGroup: Bool { + groupName != nil } } @@ -122,9 +111,9 @@ extension UNMutableNotificationContent { func addSenderIcon(using mediaProvider: MediaProviderProtocol?, senderID: String, senderName: String, - iconType: NotificationIconType) async throws -> UNMutableNotificationContent { + icon: NotificationIcon) async throws -> UNMutableNotificationContent { var image = INImage(named: "") - if let mediaSource = iconType.mediaSource { + if let mediaSource = icon.mediaSource { switch await mediaProvider?.loadImageDataFromSource(mediaSource) { case .success(let data): image = INImage(imageData: data) @@ -139,14 +128,14 @@ extension UNMutableNotificationContent { let sender = INPerson(personHandle: senderHandle, nameComponents: nil, displayName: senderName, - image: iconType.isSenderIcon ? image : nil, + image: !icon.shouldDisplayAsGroup ? image : nil, contactIdentifier: nil, customIdentifier: nil) // These are required to show the group name as subtitle var speakableGroupName: INSpeakableString? var recipients: [INPerson]? - if case let .group(_, groupName) = iconType { + if let groupName = icon.groupName { let meHandle = INPersonHandle(value: receiverID, type: .unknown) let me = INPerson(personHandle: meHandle, nameComponents: nil, displayName: nil, image: nil, contactIdentifier: nil, customIdentifier: nil, isMe: true) speakableGroupName = INSpeakableString(spokenPhrase: groupName) diff --git a/ElementX/Sources/Services/Notification/Proxy/NotificationItemProxy.swift b/ElementX/Sources/Services/Notification/Proxy/NotificationItemProxy.swift index 63d746928..6bebdd198 100644 --- a/ElementX/Sources/Services/Notification/Proxy/NotificationItemProxy.swift +++ b/ElementX/Sources/Services/Notification/Proxy/NotificationItemProxy.swift @@ -41,7 +41,11 @@ protocol NotificationItemProxyProtocol { var isDirect: Bool { get } - var isEncrypted: Bool? { get } + /// Returns `true` if the event of the notification belongs to an encrypted room + var isRoomEncrypted: Bool? { get } + + /// Returns `true` if was not possible to decrypt the notification content + var isEncrypted: Bool { get } } extension NotificationItemProxyProtocol { @@ -87,7 +91,7 @@ struct NotificationItemProxy: NotificationItemProxyProtocol { notificationItem.isDirect } - var isEncrypted: Bool? { + var isRoomEncrypted: Bool? { notificationItem.isEncrypted } @@ -106,6 +110,20 @@ struct NotificationItemProxy: NotificationItemProxyProtocol { } return nil } + + var isEncrypted: Bool { + switch event.type { + case .messageLike(let content): + switch content { + case .roomEncrypted: + return true + default: + return false + } + default: + return false + } + } } struct EmptyNotificationItemProxy: NotificationItemProxyProtocol { @@ -133,13 +151,15 @@ struct EmptyNotificationItemProxy: NotificationItemProxyProtocol { var isDirect: Bool { false } - var isEncrypted: Bool? { nil } + var isRoomEncrypted: Bool? { nil } var senderAvatarMediaSource: MediaSourceProxy? { nil } var roomAvatarMediaSource: MediaSourceProxy? { nil } var notificationIdentifier: String { "" } + + var isEncrypted: Bool { false } } extension NotificationItemProxyProtocol { @@ -228,13 +248,22 @@ extension NotificationItemProxyProtocol { 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) + let icon: NotificationIcon + let body: String + if !isDirect { + icon = NotificationIcon(mediaSource: roomAvatarMediaSource, groupName: roomDisplayName) + body = L10n.notificationRoomInviteBody + } else { + icon = NotificationIcon(mediaSource: senderAvatarMediaSource, groupName: nil) + body = L10n.notificationInviteBody + } + notification = try await notification.addSenderIcon(using: mediaProvider, senderID: event.senderID, - senderName: senderDisplayName ?? event.senderID, - iconType: iconType) - notification.body = L10n.notificationInviteBody + senderName: senderDisplayName ?? roomDisplayName, + icon: icon) + notification.body = body + return notification } @@ -274,17 +303,17 @@ extension NotificationItemProxyProtocol { notification.categoryIdentifier = NotificationConstants.Category.message let senderName = senderDisplayName ?? roomDisplayName - let iconType: NotificationIconType + let icon: NotificationIcon if !isDirect { - iconType = .group(mediaSource: roomAvatarMediaSource, groupName: roomDisplayName) + icon = NotificationIcon(mediaSource: roomAvatarMediaSource, groupName: roomDisplayName) } else { - iconType = .sender(mediaSource: senderAvatarMediaSource) + icon = NotificationIcon(mediaSource: senderAvatarMediaSource, groupName: nil) } notification = try await notification.addSenderIcon(using: mediaProvider, senderID: event.senderID, senderName: senderName, - iconType: iconType) + icon: icon) return notification }