From c1dfce49350357e234c5035aaebb1831572a9750 Mon Sep 17 00:00:00 2001 From: Mauro <34335419+Velin92@users.noreply.github.com> Date: Wed, 28 May 2025 19:51:34 +0200 Subject: [PATCH] Handle media previews and invite avatars through the account data (#4142) * added account data media display policy handling to NSE * pr suggestions * update compound * code improvement --- ElementX.xcodeproj/project.pbxproj | 2 +- .../xcshareddata/swiftpm/Package.resolved | 6 +- .../Sources/Application/AppCoordinator.swift | 54 ++++- .../Sources/Application/AppSettings.swift | 16 -- .../SettingsFlowCoordinator.swift | 4 +- ElementX/Sources/Mocks/ClientProxyMock.swift | 6 + .../Mocks/Generated/GeneratedMocks.swift | 214 ++++++++++++++++++ .../Mocks/Generated/SDKGeneratedMocks.swift | 89 +++++++- ElementX/Sources/Other/SDKListener.swift | 4 + .../HomeScreen/HomeScreenViewModel.swift | 4 +- .../JoinRoomScreenViewModel.swift | 4 +- .../JoinRoomScreen/View/JoinRoomScreen.swift | 3 +- .../View/SecurityAndPrivacyScreen.swift | 3 +- .../AdvancedSettingsScreenCoordinator.swift | 7 +- .../AdvancedSettingsScreenModels.swift | 8 +- .../AdvancedSettingsScreenViewModel.swift | 74 +++++- .../View/AdvancedSettingsScreen.swift | 26 ++- .../Screens/Timeline/TimelineViewModel.swift | 6 +- .../Sources/Services/Client/ClientProxy.swift | 104 +++++++++ .../Services/Client/ClientProxyProtocol.swift | 18 ++ NSE/Sources/NSEUserSession.swift | 22 ++ NSE/Sources/NotificationContentBuilder.swift | 9 +- NSE/Sources/NotificationHandler.swift | 2 +- project.yml | 2 +- 24 files changed, 626 insertions(+), 61 deletions(-) diff --git a/ElementX.xcodeproj/project.pbxproj b/ElementX.xcodeproj/project.pbxproj index 9debf0b62..d8dfb16b6 100644 --- a/ElementX.xcodeproj/project.pbxproj +++ b/ElementX.xcodeproj/project.pbxproj @@ -8836,7 +8836,7 @@ repositoryURL = "https://github.com/element-hq/compound-ios"; requirement = { kind = revision; - revision = 81ba8bd8b3971beac252129c5466d7eac2f2ec31; + revision = 139d4f3d1ed19cc513686e5e39750ecef54c8403; }; }; F76A08D0EA29A07A54F4EB4D /* XCRemoteSwiftPackageReference "swift-collections" */ = { diff --git a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved index cb67c2839..8613a5d84 100644 --- a/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved +++ b/ElementX.xcodeproj/project.xcworkspace/xcshareddata/swiftpm/Package.resolved @@ -15,7 +15,7 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/element-hq/compound-ios", "state" : { - "revision" : "81ba8bd8b3971beac252129c5466d7eac2f2ec31" + "revision" : "139d4f3d1ed19cc513686e5e39750ecef54c8403" } }, { @@ -203,8 +203,8 @@ "kind" : "remoteSourceControl", "location" : "https://github.com/SFSafeSymbols/SFSafeSymbols", "state" : { - "revision" : "e2e28f4e56e1769c2ec3c61c9355fc64eb7a535a", - "version" : "5.3.0" + "revision" : "3dd282d3269b061853a3b3bcd23a509d2aa166ce", + "version" : "6.2.0" } }, { diff --git a/ElementX/Sources/Application/AppCoordinator.swift b/ElementX/Sources/Application/AppCoordinator.swift index df73c34d9..11b0ffc4f 100644 --- a/ElementX/Sources/Application/AppCoordinator.swift +++ b/ElementX/Sources/Application/AppCoordinator.swift @@ -30,11 +30,12 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg private var userSession: UserSessionProtocol? { didSet { userSessionObserver?.cancel() - if userSession != nil { + if let userSession { configureElementCallService() configureNotificationManager() observeUserSessionChanges() startSync() + performSettingsToAccountDataMigration(userSession: userSession) } } } @@ -399,17 +400,54 @@ class AppCoordinator: AppCoordinatorProtocol, AuthenticationFlowCoordinatorDeleg Tracing.deleteLogFiles() MXLog.info("Migrating to v1.6.7, log files have been wiped") } + } + + // This could be removed once the adotpion of 25.06.x is widespread. + private func performSettingsToAccountDataMigration(userSession: UserSessionProtocol) { + guard let userDefaults = UserDefaults(suiteName: InfoPlistReader.main.appGroupIdentifier) else { + return + } - if oldVersion < Version(25, 4, 2) { - MXLog.info("Migrating to v25.04.2, checking if hideTimelineMedia flag can be migrated to timelineMediaVisibility") - // Migration for the old `hideTimelineMedia` flag. - if let userDefaults = UserDefaults(suiteName: InfoPlistReader.main.appGroupIdentifier), - let hideTimelineMedia = userDefaults.value(forKey: "hideTimelineMedia") as? Bool { - appSettings.timelineMediaVisibility = hideTimelineMedia ? .never : .always + let hideInviteAvatars = userDefaults.value(forKey: "hideInviteAvatars") as? Bool + let timelineMediaVisibility = userDefaults + .data(forKey: "timelineMediaVisibility") + .flatMap { + try? JSONDecoder().decode(TimelineMediaVisibility.self, from: $0) + } + let hideTimelineMedia = userDefaults.value(forKey: "hideTimelineMedia") as? Bool + + guard hideInviteAvatars != nil || timelineMediaVisibility != nil || hideTimelineMedia != nil else { + // No migration needed, no local settings found. + return + } + + Task { + switch await userSession.clientProxy.fetchMediaPreviewConfiguration() { + case let .success(config): + guard config == nil else { + // Found a server configuration, no need to migrate. + userDefaults.removeObject(forKey: "hideInviteAvatars") + userDefaults.removeObject(forKey: "timelineMediaVisibility") + userDefaults.removeObject(forKey: "hideTimelineMedia") + return + } + + if let hideInviteAvatars, case .success = await userSession.clientProxy.setHideInviteAvatars(hideInviteAvatars) { + userDefaults.removeObject(forKey: "hideInviteAvatars") + } + + if let timelineMediaVisibility, case .success = await userSession.clientProxy.setTimelineMediaVisibility(timelineMediaVisibility) { + userDefaults.removeObject(forKey: "timelineMediaVisibility") + } else if let hideTimelineMedia, case .success = await userSession.clientProxy.setTimelineMediaVisibility(hideTimelineMedia ? .never : .always) { + userDefaults.removeObject(forKey: "hideTimelineMedia") + } + case let .failure(error): + MXLog.error("Could not perform migration, failed to fetch media preview config: \(error)") + return } } } - + /// Clears the keychain, app support directory etc ready for a fresh use. /// - Parameter includingSettings: Whether to additionally wipe the user's app settings too. private func wipeUserData(includingSettings: Bool = false) { diff --git a/ElementX/Sources/Application/AppSettings.swift b/ElementX/Sources/Application/AppSettings.swift index df1e10ba3..168b3c2dd 100644 --- a/ElementX/Sources/Application/AppSettings.swift +++ b/ElementX/Sources/Application/AppSettings.swift @@ -17,8 +17,6 @@ protocol CommonSettingsProtocol { var logLevel: LogLevel { get } var traceLogPacks: Set { get } var enableOnlySignedDeviceIsolationMode: Bool { get } - var hideInviteAvatars: Bool { get } - var timelineMediaVisibility: TimelineMediaVisibility { get } var hideQuietNotificationAlerts: Bool { get } } @@ -46,8 +44,6 @@ final class AppSettings { case optimizeMediaUploads case appAppearance case sharePresence - case hideInviteAvatars - case timelineMediaVisibility case isNewBloomEnabled case elementCallBaseURLOverride @@ -368,20 +364,8 @@ final class AppSettings { @UserPreference(key: UserDefaultsKeys.enableOnlySignedDeviceIsolationMode, defaultValue: false, storageType: .userDefaults(store)) var enableOnlySignedDeviceIsolationMode - @UserPreference(key: UserDefaultsKeys.hideInviteAvatars, defaultValue: false, storageType: .userDefaults(store)) - var hideInviteAvatars - - @UserPreference(key: UserDefaultsKeys.timelineMediaVisibility, defaultValue: TimelineMediaVisibility.always, storageType: .userDefaults(store)) - var timelineMediaVisibility - @UserPreference(key: UserDefaultsKeys.hideQuietNotificationAlerts, defaultValue: false, storageType: .userDefaults(store)) var hideQuietNotificationAlerts } extension AppSettings: CommonSettingsProtocol { } - -enum TimelineMediaVisibility: Codable { - case always - case privateOnly - case never -} diff --git a/ElementX/Sources/FlowCoordinators/SettingsFlowCoordinator.swift b/ElementX/Sources/FlowCoordinators/SettingsFlowCoordinator.swift index 16ec6d016..538945969 100644 --- a/ElementX/Sources/FlowCoordinators/SettingsFlowCoordinator.swift +++ b/ElementX/Sources/FlowCoordinators/SettingsFlowCoordinator.swift @@ -226,7 +226,9 @@ class SettingsFlowCoordinator: FlowCoordinatorProtocol { private func presentAdvancedSettings() { let coordinator = AdvancedSettingsScreenCoordinator(parameters: .init(appSettings: parameters.appSettings, - analytics: parameters.analytics)) + analytics: parameters.analytics, + clientProxy: parameters.userSession.clientProxy, + userIndicatorController: parameters.userIndicatorController)) navigationStackCoordinator.push(coordinator) } diff --git a/ElementX/Sources/Mocks/ClientProxyMock.swift b/ElementX/Sources/Mocks/ClientProxyMock.swift index aa1c97912..be1505c3e 100644 --- a/ElementX/Sources/Mocks/ClientProxyMock.swift +++ b/ElementX/Sources/Mocks/ClientProxyMock.swift @@ -17,6 +17,9 @@ struct ClientProxyMockConfiguration { var roomDirectorySearchProxy: RoomDirectorySearchProxyProtocol? var recoveryState: SecureBackupRecoveryState = .enabled + + var timelineMediaVisibility = TimelineMediaVisibility.always + var hideInviteAvatars = false } enum ClientProxyMockError: Error { @@ -94,5 +97,8 @@ extension ClientProxyMock { userIdentityForReturnValue = .success(UserIdentityProxyMock(configuration: .init())) underlyingIsReportRoomSupported = true + + underlyingTimelineMediaVisibilityPublisher = CurrentValueSubject(configuration.timelineMediaVisibility).asCurrentValuePublisher() + underlyingHideInviteAvatarsPublisher = CurrentValueSubject(configuration.hideInviteAvatars).asCurrentValuePublisher() } } diff --git a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift index 204b0f574..a7b0738fc 100644 --- a/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift +++ b/ElementX/Sources/Mocks/Generated/GeneratedMocks.swift @@ -2271,6 +2271,16 @@ class ClientProxyMock: ClientProxyProtocol, @unchecked Sendable { set(value) { underlyingIgnoredUsersPublisher = value } } var underlyingIgnoredUsersPublisher: CurrentValuePublisher<[String]?, Never>! + var timelineMediaVisibilityPublisher: CurrentValuePublisher { + get { return underlyingTimelineMediaVisibilityPublisher } + set(value) { underlyingTimelineMediaVisibilityPublisher = value } + } + var underlyingTimelineMediaVisibilityPublisher: CurrentValuePublisher! + var hideInviteAvatarsPublisher: CurrentValuePublisher { + get { return underlyingHideInviteAvatarsPublisher } + set(value) { underlyingHideInviteAvatarsPublisher = value } + } + var underlyingHideInviteAvatarsPublisher: CurrentValuePublisher! var pusherNotificationClientIdentifier: String? var roomSummaryProvider: RoomSummaryProviderProtocol { get { return underlyingRoomSummaryProvider } @@ -4363,6 +4373,70 @@ class ClientProxyMock: ClientProxyProtocol, @unchecked Sendable { return clearCachesReturnValue } } + //MARK: - fetchMediaPreviewConfiguration + + var fetchMediaPreviewConfigurationUnderlyingCallsCount = 0 + var fetchMediaPreviewConfigurationCallsCount: Int { + get { + if Thread.isMainThread { + return fetchMediaPreviewConfigurationUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = fetchMediaPreviewConfigurationUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + fetchMediaPreviewConfigurationUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + fetchMediaPreviewConfigurationUnderlyingCallsCount = newValue + } + } + } + } + var fetchMediaPreviewConfigurationCalled: Bool { + return fetchMediaPreviewConfigurationCallsCount > 0 + } + + var fetchMediaPreviewConfigurationUnderlyingReturnValue: Result! + var fetchMediaPreviewConfigurationReturnValue: Result! { + get { + if Thread.isMainThread { + return fetchMediaPreviewConfigurationUnderlyingReturnValue + } else { + var returnValue: Result? = nil + DispatchQueue.main.sync { + returnValue = fetchMediaPreviewConfigurationUnderlyingReturnValue + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + fetchMediaPreviewConfigurationUnderlyingReturnValue = newValue + } else { + DispatchQueue.main.sync { + fetchMediaPreviewConfigurationUnderlyingReturnValue = newValue + } + } + } + } + var fetchMediaPreviewConfigurationClosure: (() async -> Result)? + + func fetchMediaPreviewConfiguration() async -> Result { + fetchMediaPreviewConfigurationCallsCount += 1 + if let fetchMediaPreviewConfigurationClosure = fetchMediaPreviewConfigurationClosure { + return await fetchMediaPreviewConfigurationClosure() + } else { + return fetchMediaPreviewConfigurationReturnValue + } + } //MARK: - ignoreUser var ignoreUserUnderlyingCallsCount = 0 @@ -5103,6 +5177,146 @@ class ClientProxyMock: ClientProxyProtocol, @unchecked Sendable { return userIdentityForReturnValue } } + //MARK: - setTimelineMediaVisibility + + var setTimelineMediaVisibilityUnderlyingCallsCount = 0 + var setTimelineMediaVisibilityCallsCount: Int { + get { + if Thread.isMainThread { + return setTimelineMediaVisibilityUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = setTimelineMediaVisibilityUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + setTimelineMediaVisibilityUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + setTimelineMediaVisibilityUnderlyingCallsCount = newValue + } + } + } + } + var setTimelineMediaVisibilityCalled: Bool { + return setTimelineMediaVisibilityCallsCount > 0 + } + var setTimelineMediaVisibilityReceivedValue: TimelineMediaVisibility? + var setTimelineMediaVisibilityReceivedInvocations: [TimelineMediaVisibility] = [] + + var setTimelineMediaVisibilityUnderlyingReturnValue: Result! + var setTimelineMediaVisibilityReturnValue: Result! { + get { + if Thread.isMainThread { + return setTimelineMediaVisibilityUnderlyingReturnValue + } else { + var returnValue: Result? = nil + DispatchQueue.main.sync { + returnValue = setTimelineMediaVisibilityUnderlyingReturnValue + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + setTimelineMediaVisibilityUnderlyingReturnValue = newValue + } else { + DispatchQueue.main.sync { + setTimelineMediaVisibilityUnderlyingReturnValue = newValue + } + } + } + } + var setTimelineMediaVisibilityClosure: ((TimelineMediaVisibility) async -> Result)? + + func setTimelineMediaVisibility(_ value: TimelineMediaVisibility) async -> Result { + setTimelineMediaVisibilityCallsCount += 1 + setTimelineMediaVisibilityReceivedValue = value + DispatchQueue.main.async { + self.setTimelineMediaVisibilityReceivedInvocations.append(value) + } + if let setTimelineMediaVisibilityClosure = setTimelineMediaVisibilityClosure { + return await setTimelineMediaVisibilityClosure(value) + } else { + return setTimelineMediaVisibilityReturnValue + } + } + //MARK: - setHideInviteAvatars + + var setHideInviteAvatarsUnderlyingCallsCount = 0 + var setHideInviteAvatarsCallsCount: Int { + get { + if Thread.isMainThread { + return setHideInviteAvatarsUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = setHideInviteAvatarsUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + setHideInviteAvatarsUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + setHideInviteAvatarsUnderlyingCallsCount = newValue + } + } + } + } + var setHideInviteAvatarsCalled: Bool { + return setHideInviteAvatarsCallsCount > 0 + } + var setHideInviteAvatarsReceivedValue: Bool? + var setHideInviteAvatarsReceivedInvocations: [Bool] = [] + + var setHideInviteAvatarsUnderlyingReturnValue: Result! + var setHideInviteAvatarsReturnValue: Result! { + get { + if Thread.isMainThread { + return setHideInviteAvatarsUnderlyingReturnValue + } else { + var returnValue: Result? = nil + DispatchQueue.main.sync { + returnValue = setHideInviteAvatarsUnderlyingReturnValue + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + setHideInviteAvatarsUnderlyingReturnValue = newValue + } else { + DispatchQueue.main.sync { + setHideInviteAvatarsUnderlyingReturnValue = newValue + } + } + } + } + var setHideInviteAvatarsClosure: ((Bool) async -> Result)? + + func setHideInviteAvatars(_ value: Bool) async -> Result { + setHideInviteAvatarsCallsCount += 1 + setHideInviteAvatarsReceivedValue = value + DispatchQueue.main.async { + self.setHideInviteAvatarsReceivedInvocations.append(value) + } + if let setHideInviteAvatarsClosure = setHideInviteAvatarsClosure { + return await setHideInviteAvatarsClosure(value) + } else { + return setHideInviteAvatarsReturnValue + } + } //MARK: - loadMediaContentForSource var loadMediaContentForSourceThrowableError: Error? diff --git a/ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift b/ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift index 275c5be31..0973e1ab8 100644 --- a/ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift +++ b/ElementX/Sources/Mocks/Generated/SDKGeneratedMocks.swift @@ -1050,6 +1050,75 @@ open class ClientSDKMock: MatrixRustSDK.Client, @unchecked Sendable { } } + //MARK: - fetchMediaPreviewConfig + + open var fetchMediaPreviewConfigThrowableError: Error? + var fetchMediaPreviewConfigUnderlyingCallsCount = 0 + open var fetchMediaPreviewConfigCallsCount: Int { + get { + if Thread.isMainThread { + return fetchMediaPreviewConfigUnderlyingCallsCount + } else { + var returnValue: Int? = nil + DispatchQueue.main.sync { + returnValue = fetchMediaPreviewConfigUnderlyingCallsCount + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + fetchMediaPreviewConfigUnderlyingCallsCount = newValue + } else { + DispatchQueue.main.sync { + fetchMediaPreviewConfigUnderlyingCallsCount = newValue + } + } + } + } + open var fetchMediaPreviewConfigCalled: Bool { + return fetchMediaPreviewConfigCallsCount > 0 + } + + var fetchMediaPreviewConfigUnderlyingReturnValue: MediaPreviewConfig? + open var fetchMediaPreviewConfigReturnValue: MediaPreviewConfig? { + get { + if Thread.isMainThread { + return fetchMediaPreviewConfigUnderlyingReturnValue + } else { + var returnValue: MediaPreviewConfig?? = nil + DispatchQueue.main.sync { + returnValue = fetchMediaPreviewConfigUnderlyingReturnValue + } + + return returnValue! + } + } + set { + if Thread.isMainThread { + fetchMediaPreviewConfigUnderlyingReturnValue = newValue + } else { + DispatchQueue.main.sync { + fetchMediaPreviewConfigUnderlyingReturnValue = newValue + } + } + } + } + open var fetchMediaPreviewConfigClosure: (() async throws -> MediaPreviewConfig?)? + + open override func fetchMediaPreviewConfig() async throws -> MediaPreviewConfig? { + if let error = fetchMediaPreviewConfigThrowableError { + throw error + } + fetchMediaPreviewConfigCallsCount += 1 + if let fetchMediaPreviewConfigClosure = fetchMediaPreviewConfigClosure { + return try await fetchMediaPreviewConfigClosure() + } else { + return fetchMediaPreviewConfigReturnValue + } + } + //MARK: - getDmRoom open var getDmRoomUserIdThrowableError: Error? @@ -1156,13 +1225,13 @@ open class ClientSDKMock: MatrixRustSDK.Client, @unchecked Sendable { return getInviteAvatarsDisplayPolicyCallsCount > 0 } - var getInviteAvatarsDisplayPolicyUnderlyingReturnValue: InviteAvatars! - open var getInviteAvatarsDisplayPolicyReturnValue: InviteAvatars! { + var getInviteAvatarsDisplayPolicyUnderlyingReturnValue: InviteAvatars? + open var getInviteAvatarsDisplayPolicyReturnValue: InviteAvatars? { get { if Thread.isMainThread { return getInviteAvatarsDisplayPolicyUnderlyingReturnValue } else { - var returnValue: InviteAvatars? = nil + var returnValue: InviteAvatars?? = nil DispatchQueue.main.sync { returnValue = getInviteAvatarsDisplayPolicyUnderlyingReturnValue } @@ -1180,9 +1249,9 @@ open class ClientSDKMock: MatrixRustSDK.Client, @unchecked Sendable { } } } - open var getInviteAvatarsDisplayPolicyClosure: (() async throws -> InviteAvatars)? + open var getInviteAvatarsDisplayPolicyClosure: (() async throws -> InviteAvatars?)? - open override func getInviteAvatarsDisplayPolicy() async throws -> InviteAvatars { + open override func getInviteAvatarsDisplayPolicy() async throws -> InviteAvatars? { if let error = getInviteAvatarsDisplayPolicyThrowableError { throw error } @@ -1375,13 +1444,13 @@ open class ClientSDKMock: MatrixRustSDK.Client, @unchecked Sendable { return getMediaPreviewDisplayPolicyCallsCount > 0 } - var getMediaPreviewDisplayPolicyUnderlyingReturnValue: MediaPreviews! - open var getMediaPreviewDisplayPolicyReturnValue: MediaPreviews! { + var getMediaPreviewDisplayPolicyUnderlyingReturnValue: MediaPreviews? + open var getMediaPreviewDisplayPolicyReturnValue: MediaPreviews? { get { if Thread.isMainThread { return getMediaPreviewDisplayPolicyUnderlyingReturnValue } else { - var returnValue: MediaPreviews? = nil + var returnValue: MediaPreviews?? = nil DispatchQueue.main.sync { returnValue = getMediaPreviewDisplayPolicyUnderlyingReturnValue } @@ -1399,9 +1468,9 @@ open class ClientSDKMock: MatrixRustSDK.Client, @unchecked Sendable { } } } - open var getMediaPreviewDisplayPolicyClosure: (() async throws -> MediaPreviews)? + open var getMediaPreviewDisplayPolicyClosure: (() async throws -> MediaPreviews?)? - open override func getMediaPreviewDisplayPolicy() async throws -> MediaPreviews { + open override func getMediaPreviewDisplayPolicy() async throws -> MediaPreviews? { if let error = getMediaPreviewDisplayPolicyThrowableError { throw error } diff --git a/ElementX/Sources/Other/SDKListener.swift b/ElementX/Sources/Other/SDKListener.swift index 985a21776..3e3d82bec 100644 --- a/ElementX/Sources/Other/SDKListener.swift +++ b/ElementX/Sources/Other/SDKListener.swift @@ -30,6 +30,10 @@ extension SDKListener: QrLoginProgressListener where T == QrLoginProgress { // MARK: ClientProxy +extension SDKListener: MediaPreviewConfigListener where T == MediaPreviewConfig? { + func onChange(mediaPreviewConfig: MediaPreviewConfig?) { onUpdateClosure(mediaPreviewConfig) } +} + extension SDKListener: SyncServiceStateObserver where T == SyncServiceState { func onUpdate(state: SyncServiceState) { onUpdateClosure(state) } } diff --git a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift index cd5aac873..a072aef08 100644 --- a/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift +++ b/ElementX/Sources/Screens/HomeScreen/HomeScreenViewModel.swift @@ -104,7 +104,9 @@ class HomeScreenViewModel: HomeScreenViewModelType, HomeScreenViewModelProtocol } .store(in: &cancellables) - appSettings.$hideInviteAvatars + userSession.clientProxy.hideInviteAvatarsPublisher + .removeDuplicates() + .receive(on: DispatchQueue.main) .weakAssign(to: \.state.hideInviteAvatars, on: self) .store(in: &cancellables) diff --git a/ElementX/Sources/Screens/JoinRoomScreen/JoinRoomScreenViewModel.swift b/ElementX/Sources/Screens/JoinRoomScreen/JoinRoomScreenViewModel.swift index 4edcd010a..8fec297ca 100644 --- a/ElementX/Sources/Screens/JoinRoomScreen/JoinRoomScreenViewModel.swift +++ b/ElementX/Sources/Screens/JoinRoomScreen/JoinRoomScreenViewModel.swift @@ -41,7 +41,9 @@ class JoinRoomScreenViewModel: JoinRoomScreenViewModelType, JoinRoomScreenViewMo super.init(initialViewState: JoinRoomScreenViewState(roomID: roomID), mediaProvider: mediaProvider) - appSettings.$hideInviteAvatars + clientProxy.hideInviteAvatarsPublisher + .removeDuplicates() + .receive(on: DispatchQueue.main) .weakAssign(to: \.state.hideInviteAvatars, on: self) .store(in: &cancellables) diff --git a/ElementX/Sources/Screens/JoinRoomScreen/View/JoinRoomScreen.swift b/ElementX/Sources/Screens/JoinRoomScreen/View/JoinRoomScreen.swift index 8b01865a2..087a8390c 100644 --- a/ElementX/Sources/Screens/JoinRoomScreen/View/JoinRoomScreen.swift +++ b/ElementX/Sources/Screens/JoinRoomScreen/View/JoinRoomScreen.swift @@ -363,9 +363,8 @@ struct JoinRoomScreen_Previews: PreviewProvider, TestablePreview { static func makeViewModel(mode: JoinRoomScreenMode, hideInviteAvatars: Bool = false) -> JoinRoomScreenViewModel { let appSettings = AppSettings() appSettings.knockingEnabled = true - appSettings.hideInviteAvatars = hideInviteAvatars - let clientProxy = ClientProxyMock(.init()) + let clientProxy = ClientProxyMock(.init(hideInviteAvatars: hideInviteAvatars)) switch mode { case .unknown: diff --git a/ElementX/Sources/Screens/SecurityAndPrivacyScreen/View/SecurityAndPrivacyScreen.swift b/ElementX/Sources/Screens/SecurityAndPrivacyScreen/View/SecurityAndPrivacyScreen.swift index 0cbee785b..ae25fba68 100644 --- a/ElementX/Sources/Screens/SecurityAndPrivacyScreen/View/SecurityAndPrivacyScreen.swift +++ b/ElementX/Sources/Screens/SecurityAndPrivacyScreen/View/SecurityAndPrivacyScreen.swift @@ -158,7 +158,8 @@ struct SecurityAndPrivacyScreen: View { Section { ListRow(label: .plain(title: L10n.screenSecurityAndPrivacyRoomDirectoryVisibilityToggleTitle), - details: .isWaiting(context.desiredSettings.isVisibileInRoomDirectory == nil), + details: + context.desiredSettings.isVisibileInRoomDirectory == nil ? .isWaiting(true) : nil, kind: context.desiredSettings.isVisibileInRoomDirectory == nil ? .label : .toggle(binding)) } footer: { Text(L10n.screenSecurityAndPrivacyRoomDirectoryVisibilitySectionFooter(context.viewState.serverName)) diff --git a/ElementX/Sources/Screens/Settings/AvancedOptionsScreen/AdvancedSettingsScreenCoordinator.swift b/ElementX/Sources/Screens/Settings/AvancedOptionsScreen/AdvancedSettingsScreenCoordinator.swift index 473dfccee..cec2ff570 100644 --- a/ElementX/Sources/Screens/Settings/AvancedOptionsScreen/AdvancedSettingsScreenCoordinator.swift +++ b/ElementX/Sources/Screens/Settings/AvancedOptionsScreen/AdvancedSettingsScreenCoordinator.swift @@ -11,13 +11,18 @@ import SwiftUI struct AdvancedSettingsScreenCoordinatorParameters { let appSettings: AppSettings let analytics: AnalyticsService + let clientProxy: ClientProxyProtocol + let userIndicatorController: UserIndicatorControllerProtocol } final class AdvancedSettingsScreenCoordinator: CoordinatorProtocol { private var viewModel: AdvancedSettingsScreenViewModelProtocol init(parameters: AdvancedSettingsScreenCoordinatorParameters) { - viewModel = AdvancedSettingsScreenViewModel(advancedSettings: parameters.appSettings, analytics: parameters.analytics) + viewModel = AdvancedSettingsScreenViewModel(advancedSettings: parameters.appSettings, + analytics: parameters.analytics, + clientProxy: parameters.clientProxy, + userIndicatorController: parameters.userIndicatorController) } func toPresentable() -> AnyView { diff --git a/ElementX/Sources/Screens/Settings/AvancedOptionsScreen/AdvancedSettingsScreenModels.swift b/ElementX/Sources/Screens/Settings/AvancedOptionsScreen/AdvancedSettingsScreenModels.swift index eb06d5d47..7b58f976a 100644 --- a/ElementX/Sources/Screens/Settings/AvancedOptionsScreen/AdvancedSettingsScreenModels.swift +++ b/ElementX/Sources/Screens/Settings/AvancedOptionsScreen/AdvancedSettingsScreenModels.swift @@ -8,6 +8,10 @@ import Foundation struct AdvancedSettingsScreenViewState: BindableState { + var timelineMediaVisibility: TimelineMediaVisibility + var hideInviteAvatars: Bool + var isWaitingTimelineMediaVisibility = false + var isWaitingHideInviteAvatars = false var bindings: AdvancedSettingsScreenViewStateBindings } @@ -28,14 +32,14 @@ struct AdvancedSettingsScreenViewStateBindings { enum AdvancedSettingsScreenViewAction { case optimizeMediaUploadsChanged + case updateTimelineMediaVisibility(TimelineMediaVisibility) + case updateHideInviteAvatars(Bool) } protocol AdvancedSettingsProtocol: AnyObject { var viewSourceEnabled: Bool { get set } var appAppearance: AppAppearance { get set } var sharePresence: Bool { get set } - var timelineMediaVisibility: TimelineMediaVisibility { get set } - var hideInviteAvatars: Bool { get set } var optimizeMediaUploads: Bool { get set } } diff --git a/ElementX/Sources/Screens/Settings/AvancedOptionsScreen/AdvancedSettingsScreenViewModel.swift b/ElementX/Sources/Screens/Settings/AvancedOptionsScreen/AdvancedSettingsScreenViewModel.swift index 9edcd44d0..e1c929e46 100644 --- a/ElementX/Sources/Screens/Settings/AvancedOptionsScreen/AdvancedSettingsScreenViewModel.swift +++ b/ElementX/Sources/Screens/Settings/AvancedOptionsScreen/AdvancedSettingsScreenViewModel.swift @@ -12,12 +12,36 @@ typealias AdvancedSettingsScreenViewModelType = StateStoreViewModelV2? + private var hideInviteAvatarsTask: Task? + + init(advancedSettings: AdvancedSettingsProtocol, + analytics: AnalyticsService, + clientProxy: ClientProxyProtocol, + userIndicatorController: UserIndicatorControllerProtocol) { self.analytics = analytics + self.clientProxy = clientProxy + self.userIndicatorController = userIndicatorController - let state = AdvancedSettingsScreenViewState(bindings: .init(advancedSettings: advancedSettings)) + let state = AdvancedSettingsScreenViewState(timelineMediaVisibility: clientProxy.timelineMediaVisibilityPublisher.value, + hideInviteAvatars: clientProxy.hideInviteAvatarsPublisher.value, + bindings: .init(advancedSettings: advancedSettings)) super.init(initialViewState: state) + + clientProxy.timelineMediaVisibilityPublisher + .removeDuplicates() + .receive(on: DispatchQueue.main) + .weakAssign(to: \.state.timelineMediaVisibility, on: self) + .store(in: &cancellables) + + clientProxy.hideInviteAvatarsPublisher + .removeDuplicates() + .receive(on: DispatchQueue.main) + .weakAssign(to: \.state.hideInviteAvatars, on: self) + .store(in: &cancellables) } override func process(viewAction: AdvancedSettingsScreenViewAction) { @@ -25,6 +49,52 @@ class AdvancedSettingsScreenViewModel: AdvancedSettingsScreenViewModelType, Adva case .optimizeMediaUploadsChanged: // Note: Using a view action here as sinking the AppSettings publisher tracks the initial value. analytics.trackInteraction(name: state.bindings.optimizeMediaUploads ? .MobileSettingsOptimizeMediaUploadsEnabled : .MobileSettingsOptimizeMediaUploadsDisabled) + case let .updateHideInviteAvatars(value): + hideInviteAvatarsTask = Task { [weak self] in await self?.updateHideInviteAvatars(value) } + case let .updateTimelineMediaVisibility(value): + timelineMediaVisibilityTask = Task { [weak self] in await self?.updateTimelineMediaVisibility(value) } + } + } + + private func updateTimelineMediaVisibility(_ value: TimelineMediaVisibility) async { + defer { + timelineMediaVisibilityTask = nil + state.isWaitingTimelineMediaVisibility = false + } + + let previousState = state.timelineMediaVisibility + state.isWaitingTimelineMediaVisibility = true + state.timelineMediaVisibility = value + // If the other value is updating wait also for it to finish + await hideInviteAvatarsTask?.value + + switch await clientProxy.setTimelineMediaVisibility(value) { + case .success: + break + case .failure: + state.timelineMediaVisibility = previousState + userIndicatorController.submitIndicator(.init(title: L10n.errorUnknown)) + } + } + + private func updateHideInviteAvatars(_ value: Bool) async { + defer { + hideInviteAvatarsTask = nil + state.isWaitingHideInviteAvatars = false + } + + let previousState = state.hideInviteAvatars + state.isWaitingHideInviteAvatars = true + state.hideInviteAvatars = value + // If the other value is updating wait also for it to finish + await timelineMediaVisibilityTask?.value + + switch await clientProxy.setHideInviteAvatars(value) { + case .success: + break + case .failure: + state.hideInviteAvatars = previousState + userIndicatorController.submitIndicator(.init(title: L10n.errorUnknown)) } } } diff --git a/ElementX/Sources/Screens/Settings/AvancedOptionsScreen/View/AdvancedSettingsScreen.swift b/ElementX/Sources/Screens/Settings/AvancedOptionsScreen/View/AdvancedSettingsScreen.swift index e313f5b49..54f1e481f 100644 --- a/ElementX/Sources/Screens/Settings/AvancedOptionsScreen/View/AdvancedSettingsScreen.swift +++ b/ElementX/Sources/Screens/Settings/AvancedOptionsScreen/View/AdvancedSettingsScreen.swift @@ -42,21 +42,39 @@ struct AdvancedSettingsScreen: View { .navigationBarTitleDisplayMode(.inline) } + @ViewBuilder private var moderationAndSafetySection: some View { + let binding = Binding(get: { + context.viewState.hideInviteAvatars + }, set: { newValue in + context.send(viewAction: .updateHideInviteAvatars(newValue)) + }) + Section { ListRow(label: .plain(title: L10n.screenAdvancedSettingsHideInviteAvatarsToggleTitle), - kind: .toggle($context.hideInviteAvatars)) + details: context.viewState.isWaitingHideInviteAvatars ? .isWaiting(true) : nil, + kind: .toggle(binding)) + .disabled(context.viewState.isWaitingHideInviteAvatars) } header: { Text(L10n.screenAdvancedSettingsModerationAndSafetySectionTitle) .compoundListSectionHeader() } } + @ViewBuilder private var timelineMediaSection: some View { + let binding = Binding(get: { + context.viewState.timelineMediaVisibility + }, set: { newValue in + context.send(viewAction: .updateTimelineMediaVisibility(newValue)) + }) + Section { ListRow(label: .plain(title: L10n.screenAdvancedSettingsShowMediaTimelineTitle), - kind: .inlinePicker(selection: $context.timelineMediaVisibility, + details: .isWaiting(context.viewState.isWaitingTimelineMediaVisibility), + kind: .inlinePicker(selection: binding, items: TimelineMediaVisibility.items)) + .disabled(context.viewState.isWaitingTimelineMediaVisibility) } header: { Text(L10n.screenAdvancedSettingsShowMediaTimelineTitle) .compoundListSectionHeader() @@ -84,7 +102,9 @@ private extension AppAppearance { struct AdvancedSettingsScreen_Previews: PreviewProvider, TestablePreview { static let viewModel = AdvancedSettingsScreenViewModel(advancedSettings: ServiceLocator.shared.settings, - analytics: ServiceLocator.shared.analytics) + analytics: ServiceLocator.shared.analytics, + clientProxy: ClientProxyMock(.init()), + userIndicatorController: UserIndicatorControllerMock()) static var previews: some View { NavigationStack { AdvancedSettingsScreen(context: viewModel.context) diff --git a/ElementX/Sources/Screens/Timeline/TimelineViewModel.swift b/ElementX/Sources/Screens/Timeline/TimelineViewModel.swift index eae7ef764..ac99c4751 100644 --- a/ElementX/Sources/Screens/Timeline/TimelineViewModel.swift +++ b/ElementX/Sources/Screens/Timeline/TimelineViewModel.swift @@ -88,7 +88,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol { timelineControllerFactory: timelineControllerFactory, clientProxy: clientProxy) - let hideTimelineMedia = switch appSettings.timelineMediaVisibility { + let hideTimelineMedia = switch clientProxy.timelineMediaVisibilityPublisher.value { case .always: false case .privateOnly: @@ -525,7 +525,7 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol { .weakAssign(to: \.state.isViewSourceEnabled, on: self) .store(in: &cancellables) - appSettings.$timelineMediaVisibility + clientProxy.timelineMediaVisibilityPublisher .removeDuplicates() .flatMap { [weak self] timelineMediaVisibility -> AnyPublisher in switch timelineMediaVisibility { @@ -538,10 +538,10 @@ class TimelineViewModel: TimelineViewModelType, TimelineViewModelProtocol { return roomProxy.infoPublisher .map { !$0.isPrivate } .removeDuplicates() - .receive(on: DispatchQueue.main) .eraseToAnyPublisher() } } + .receive(on: DispatchQueue.main) .weakAssign(to: \.state.hideTimelineMedia, on: self) .store(in: &cancellables) } diff --git a/ElementX/Sources/Services/Client/ClientProxy.swift b/ElementX/Sources/Services/Client/ClientProxy.swift index 872871956..deeb81fc9 100644 --- a/ElementX/Sources/Services/Client/ClientProxy.swift +++ b/ElementX/Sources/Services/Client/ClientProxy.swift @@ -39,6 +39,9 @@ class ClientProxy: ClientProxyProtocol { // periphery:ignore - required for instance retention in the rust codebase private var sendQueueListenerTaskHandle: TaskHandle? + // periphery:ignore - required for instance retention in the rust codebase + private var mediaPreviewConfigListenerTaskHandle: TaskHandle? + private var delegateHandle: TaskHandle? // These following summary providers both operate on the same allRooms() list but @@ -132,6 +135,16 @@ class ClientProxy: ClientProxyProtocol { verificationStateSubject.asCurrentValuePublisher() } + private let timelineMediaVisibilitySubject = CurrentValueSubject(.always) + var timelineMediaVisibilityPublisher: CurrentValuePublisher { + timelineMediaVisibilitySubject.asCurrentValuePublisher() + } + + private let hideInviteAvatarsSubject = CurrentValueSubject(false) + var hideInviteAvatarsPublisher: CurrentValuePublisher { + hideInviteAvatarsSubject.asCurrentValuePublisher() + } + var roomsToAwait: Set = [] private let sendQueueStatusSubject = CurrentValueSubject(false) @@ -230,6 +243,10 @@ class ClientProxy: ClientProxyProtocol { MXLog.error("Failed setting media retention policy with error: \(error)") } } + + Task { + mediaPreviewConfigListenerTaskHandle = await createMediaPreviewConfigObserver() + } } var userID: String { @@ -724,6 +741,16 @@ class ClientProxy: ClientProxyProtocol { return .failure(.sdkError(error)) } } + + func fetchMediaPreviewConfiguration() async -> Result { + do { + let config = try await client.fetchMediaPreviewConfig() + return .success(config) + } catch { + MXLog.error("Failed fetching media preview config with error: \(error)") + return .failure(.sdkError(error)) + } + } // MARK: Ignored users @@ -798,6 +825,28 @@ class ClientProxy: ClientProxyProtocol { return users.elements } + // MARK: Moderation & Safety + + func setTimelineMediaVisibility(_ value: TimelineMediaVisibility) async -> Result { + do { + try await client.setMediaPreviewDisplayPolicy(policy: value.rustValue) + return .success(()) + } catch { + MXLog.error("Failed to set timeline media visibility: \(error)") + return .failure(.sdkError(error)) + } + } + + func setHideInviteAvatars(_ value: Bool) async -> Result { + do { + try await client.setInviteAvatarsDisplayPolicy(policy: value ? .off : .on) + return .success(()) + } catch { + MXLog.error("Failed to set hide invite avatars: \(error)") + return .failure(.sdkError(error)) + } + } + // MARK: - Private private func cacheAccountURL() async { @@ -867,6 +916,26 @@ class ClientProxy: ClientProxyProtocol { } }) } + + private func createMediaPreviewConfigObserver() async -> TaskHandle? { + do { + return try await client.subscribeToMediaPreviewConfig(listener: SDKListener { [weak self] config in + guard let self else { return } + + if let config { + timelineMediaVisibilitySubject.send(config.mediaPreviewVisibility) + hideInviteAvatarsSubject.send(config.hideInviteAvatars) + } else { + // return default values + timelineMediaVisibilitySubject.send(.always) + hideInviteAvatarsSubject.send(false) + } + }) + } catch { + MXLog.error("Failed creating media preview config observer: \(error)") + return nil + } + } private func createRoomListServiceObserver(_ roomListService: RoomListService) -> TaskHandle { roomListService.state(listener: SDKListener { [weak self] state in @@ -1133,3 +1202,38 @@ private struct ClientProxyServices { self.roomListService = roomListService } } + +private extension MediaPreviewConfig { + var mediaPreviewVisibility: TimelineMediaVisibility { + switch mediaPreviews { + case .on: + .always + case .private: + .privateOnly + case .off: + .never + } + } + + var hideInviteAvatars: Bool { + switch inviteAvatars { + case .off: + true + case .on: + false + } + } +} + +private extension TimelineMediaVisibility { + var rustValue: MediaPreviews { + switch self { + case .always: + .on + case .never: + .off + case .privateOnly: + .private + } + } +} diff --git a/ElementX/Sources/Services/Client/ClientProxyProtocol.swift b/ElementX/Sources/Services/Client/ClientProxyProtocol.swift index bc979ed1d..a11d0a7c4 100644 --- a/ElementX/Sources/Services/Client/ClientProxyProtocol.swift +++ b/ElementX/Sources/Services/Client/ClientProxyProtocol.swift @@ -63,6 +63,13 @@ enum SessionVerificationState { case unverified } +// The `Decodable` conformance is just for the purpose of migration +enum TimelineMediaVisibility: Decodable { + case always + case privateOnly + case never +} + // sourcery: AutoMockable protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol { var actionsPublisher: AnyPublisher { get } @@ -93,6 +100,10 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol { /// We delay fetching this until after the first sync. Nil until then var ignoredUsersPublisher: CurrentValuePublisher<[String]?, Never> { get } + var timelineMediaVisibilityPublisher: CurrentValuePublisher { get } + + var hideInviteAvatarsPublisher: CurrentValuePublisher { get } + var pusherNotificationClientIdentifier: String? { get } var roomSummaryProvider: RoomSummaryProviderProtocol { get } @@ -185,6 +196,8 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol { func isAliasAvailable(_ alias: String) async -> Result @discardableResult func clearCaches() async -> Result + + func fetchMediaPreviewConfiguration() async -> Result // MARK: - Ignored users @@ -210,4 +223,9 @@ protocol ClientProxyProtocol: AnyObject, MediaLoaderProtocol { func resetIdentity() async -> Result func userIdentity(for userID: String) async -> Result + + // MARK: - Moderation & Safety + + func setTimelineMediaVisibility(_ value: TimelineMediaVisibility) async -> Result + func setHideInviteAvatars(_ value: Bool) async -> Result } diff --git a/NSE/Sources/NSEUserSession.swift b/NSE/Sources/NSEUserSession.swift index d5ab5e5ba..1d1c40d85 100644 --- a/NSE/Sources/NSEUserSession.swift +++ b/NSE/Sources/NSEUserSession.swift @@ -18,6 +18,28 @@ final class NSEUserSession { imageCache: .onlyOnDisk, networkMonitor: nil) private let delegateHandle: TaskHandle? + + var mediaPreviewVisibility: MediaPreviews { + get async { + do { + return try await baseClient.getMediaPreviewDisplayPolicy() ?? .on + } catch { + MXLog.error("Failed to get media preview visibility, defaulting to on. Error: \(error)") + return .on + } + } + } + + var inviteAvatarsVisibility: InviteAvatars { + get async { + do { + return try await baseClient.getInviteAvatarsDisplayPolicy() ?? .on + } catch { + MXLog.error("Failed to get invite avatars visibility, defaulting to on. Error: \(error)") + return .on + } + } + } init(credentials: KeychainCredentials, roomID: String, diff --git a/NSE/Sources/NotificationContentBuilder.swift b/NSE/Sources/NotificationContentBuilder.swift index f31473aae..8c6bc5528 100644 --- a/NSE/Sources/NotificationContentBuilder.swift +++ b/NSE/Sources/NotificationContentBuilder.swift @@ -15,7 +15,7 @@ import Version struct NotificationContentBuilder { let messageEventStringBuilder: RoomMessageEventStringBuilder - let settings: CommonSettingsProtocol + let userSession: NSEUserSession /// Process the given notification item proxy /// - Parameters: @@ -104,7 +104,7 @@ struct NotificationContentBuilder { senderID: notificationItem.senderID, senderName: notificationItem.senderDisplayName ?? notificationItem.roomDisplayName, icon: icon(for: notificationItem), - forcePlaceholder: settings.hideInviteAvatars, + forcePlaceholder: userSession.inviteAvatarsVisibility == .off, mediaProvider: mediaProvider) } @@ -146,8 +146,9 @@ struct NotificationContentBuilder { 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) + let timelineMediaVisibility = await userSession.mediaPreviewVisibility + guard timelineMediaVisibility == .on || + (timelineMediaVisibility == .private && notificationItem.isRoomPrivate) else { return } diff --git a/NSE/Sources/NotificationHandler.swift b/NSE/Sources/NotificationHandler.swift index c863d5f6c..e0c35d655 100644 --- a/NSE/Sources/NotificationHandler.swift +++ b/NSE/Sources/NotificationHandler.swift @@ -36,7 +36,7 @@ class NotificationHandler { destination: .notification) notificationContentBuilder = NotificationContentBuilder(messageEventStringBuilder: eventStringBuilder, - settings: settings) + userSession: userSession) } func processEvent(_ eventID: String, roomID: String) async { diff --git a/project.yml b/project.yml index 12a71ff60..b6c4ca8e4 100644 --- a/project.yml +++ b/project.yml @@ -69,7 +69,7 @@ packages: # path: ../matrix-rust-sdk Compound: url: https://github.com/element-hq/compound-ios - revision: 81ba8bd8b3971beac252129c5466d7eac2f2ec31 + revision: 139d4f3d1ed19cc513686e5e39750ecef54c8403 # path: ../compound-ios AnalyticsEvents: url: https://github.com/matrix-org/matrix-analytics-events