// // Copyright 2025 Element Creations Ltd. // Copyright 2023-2025 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 Combine import Foundation import MatrixRustSDK private final class WeakNotificationSettingsProxy: NotificationSettingsDelegate { private weak var proxy: NotificationSettingsProxy? init(proxy: NotificationSettingsProxy) { self.proxy = proxy } // MARK: - NotificationSettingsDelegate func settingsDidChange() { Task { await proxy?.settingsDidChange() } } } final class NotificationSettingsProxy: NotificationSettingsProxyProtocol { private(set) var notificationSettings: MatrixRustSDK.NotificationSettingsProtocol let callbacks = PassthroughSubject() init(notificationSettings: MatrixRustSDK.NotificationSettingsProtocol) { self.notificationSettings = notificationSettings notificationSettings.setDelegate(delegate: WeakNotificationSettingsProxy(proxy: self)) } func getNotificationSettings(roomId: String, isEncrypted: Bool, isOneToOne: Bool) async throws -> RoomNotificationSettingsProxyProtocol { let roomMotificationSettings = try await notificationSettings.getRoomNotificationSettings(roomId: roomId, isEncrypted: isEncrypted, isOneToOne: isOneToOne) return RoomNotificationSettingsProxy(roomNotificationSettings: roomMotificationSettings) } func setNotificationMode(roomId: String, mode: RoomNotificationModeProxy) async throws { try await notificationSettings.setRoomNotificationMode(roomId: roomId, mode: mode.roomNotificationMode) await updatedSettings() } func getUserDefinedRoomNotificationMode(roomId: String) async throws -> RoomNotificationModeProxy? { let roomNotificationMode = try await notificationSettings.getUserDefinedRoomNotificationMode(roomId: roomId) return roomNotificationMode.flatMap { RoomNotificationModeProxy.from(roomNotificationMode: $0) } } func getDefaultRoomNotificationMode(isEncrypted: Bool, isOneToOne: Bool) async -> RoomNotificationModeProxy { let roomNotificationMode = await notificationSettings.getDefaultRoomNotificationMode(isEncrypted: isEncrypted, isOneToOne: isOneToOne) return RoomNotificationModeProxy.from(roomNotificationMode: roomNotificationMode) } func setDefaultRoomNotificationMode(isEncrypted: Bool, isOneToOne: Bool, mode: RoomNotificationModeProxy) async throws { do { try await notificationSettings.setDefaultRoomNotificationMode(isEncrypted: isEncrypted, isOneToOne: isOneToOne, mode: mode.roomNotificationMode) } catch NotificationSettingsError.RuleNotFound(let ruleId) { // `setDefaultRoomNotificationMode` updates multiple rules including unstable rules (e.g. the polls push rules defined in the MSC3930) // since production home servers may not have these rules yet, we drop the RuleNotFound error MXLog.warning("Unable to find the rule: \(ruleId)") return } await updatedSettings() } func restoreDefaultNotificationMode(roomId: String) async throws { try await notificationSettings.restoreDefaultRoomNotificationMode(roomId: roomId) await updatedSettings() } func unmuteRoom(roomId: String, isEncrypted: Bool, isOneToOne: Bool) async throws { try await notificationSettings.unmuteRoom(roomId: roomId, isEncrypted: isEncrypted, isOneToOne: isOneToOne) await updatedSettings() } func isRoomMentionEnabled() async throws -> Bool { try await notificationSettings.isRoomMentionEnabled() } func setRoomMentionEnabled(enabled: Bool) async throws { try await notificationSettings.setRoomMentionEnabled(enabled: enabled) await updatedSettings() } func isCallEnabled() async throws -> Bool { try await notificationSettings.isCallEnabled() } func setCallEnabled(enabled: Bool) async throws { try await notificationSettings.setCallEnabled(enabled: enabled) await updatedSettings() } func isInviteForMeEnabled() async throws -> Bool { try await notificationSettings.isInviteForMeEnabled() } func setInviteForMeEnabled(enabled: Bool) async throws { try await notificationSettings.setInviteForMeEnabled(enabled: enabled) await updatedSettings() } func getRoomsWithUserDefinedRules() async throws -> [String] { await notificationSettings.getRoomsWithUserDefinedRules(enabled: true) } func canPushEncryptedEventsToDevice() async -> Bool { await notificationSettings.canPushEncryptedEventToDevice() } // MARK: - Private func updatedSettings() async { // The timeout avoids having to wait indefinitely. This can happen when setting a mode that is already the current mode, // as in this case no API call is made by the RustSDK and the push rules are therefore not updated. _ = await callbacks .timeout(.seconds(2.0), scheduler: DispatchQueue.main, options: nil, customError: nil) .values.first { $0 == .settingsDidChange } } @MainActor func settingsDidChange() { callbacks.send(.settingsDidChange) } }