Files
letro-ios/ElementX/Sources/Screens/RoomNotificationSettingsScreen/RoomNotificationSettingsScreenViewModel.swift
Stefan Ceriu 1ae6fc67c4 SDK update (#3891)
* Bump the RustSDK to v25.03.11

* Replace oidc login prompt with nil following the changes from https://github.com/matrix-org/matrix-rust-sdk/pull/4761

```
/// * `prompt` - The desired user experience in the web UI. No value means
///   that the user wishes to login into an existing account, and a value of
///   `Create` means that the user wishes to register a new account.
```

* Fix trailing closure warnings

* Update the client proxy after making `getNotificationSettings()` and  `cachedAvatarUrl()` async (they used to be blocking on the rust side).

* Move `Room.isEncrypted` to the info publisher and manually update the encryption state when creating the room.

* Bump the SDK again to v25.03.12 - This introduces a new way to configure the tokio runtime that we can use to have extensions use less memory
- introduce a new Target struct that takes care of setting up rust services (tracing and tokio) for our various targets
- cleanup MXLog and friends

* Address PR comments

* Bump the SDK again, switch back to using `.consent` as the OIDC login prompt (which was reintroduced in matrix-org/matrix-rust-sdk/pull/4791)
2025-03-13 11:17:37 +02:00

169 lines
7.4 KiB
Swift

//
// Copyright 2022-2024 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 SwiftUI
typealias RoomNotificationSettingsScreenViewModelType = StateStoreViewModel<RoomNotificationSettingsScreenViewState, RoomNotificationSettingsScreenViewAction>
class RoomNotificationSettingsScreenViewModel: RoomNotificationSettingsScreenViewModelType, RoomNotificationSettingsScreenViewModelProtocol {
private let actionsSubject: PassthroughSubject<RoomNotificationSettingsScreenViewModelAction, Never> = .init()
private let notificationSettingsProxy: NotificationSettingsProxyProtocol
private let roomProxy: JoinedRoomProxyProtocol
// periphery:ignore - cancellable tasks cancel when reassigned
@CancellableTask private var fetchNotificationSettingsTask: Task<Void, Error>?
var actions: AnyPublisher<RoomNotificationSettingsScreenViewModelAction, Never> {
actionsSubject.eraseToAnyPublisher()
}
init(notificationSettingsProxy: NotificationSettingsProxyProtocol, roomProxy: JoinedRoomProxyProtocol, displayAsUserDefinedRoomSettings: Bool) {
let bindings = RoomNotificationSettingsScreenViewStateBindings()
self.notificationSettingsProxy = notificationSettingsProxy
self.roomProxy = roomProxy
let navigationTitle = displayAsUserDefinedRoomSettings ? roomProxy.infoPublisher.value.displayName : L10n.screenRoomDetailsNotificationTitle
let customSettingsSectionHeader = displayAsUserDefinedRoomSettings ? L10n.screenRoomNotificationSettingsRoomCustomSettingsTitle : L10n.screenRoomNotificationSettingsCustomSettingsTitle
super.init(initialViewState: RoomNotificationSettingsScreenViewState(bindings: bindings,
displayAsUserDefinedRoomSettings: displayAsUserDefinedRoomSettings,
navigationTitle: navigationTitle ?? L10n.screenRoomDetailsNotificationTitle,
customSettingsSectionHeader: customSettingsSectionHeader))
setupNotificationSettingsSubscription()
fetchNotificationSettings()
}
// MARK: - Public
override func process(viewAction: RoomNotificationSettingsScreenViewAction) {
switch viewAction {
case .changedAllowCustomSettings:
toogleCustomSetting()
case .setCustomMode(let mode):
setCustomMode(mode)
case .customSettingFootnoteLinkTapped:
actionsSubject.send(.openGlobalSettings)
case .deleteCustomSettingTapped:
Task { await deleteCustomSetting() }
}
}
// MARK: - Private
private func setupNotificationSettingsSubscription() {
notificationSettingsProxy.callbacks
.receive(on: DispatchQueue.main)
.sink { [weak self] callback in
guard let self else { return }
switch callback {
case .settingsDidChange:
self.fetchNotificationSettings()
}
}
.store(in: &cancellables)
}
private func fetchNotificationSettings() {
fetchNotificationSettingsTask = Task {
await fetchRoomNotificationSettings()
}
}
private func fetchRoomNotificationSettings() async {
let isEncrypted = roomProxy.infoPublisher.value.isEncrypted
state.shouldDisplayMentionsOnlyDisclaimer = isEncrypted ? await !notificationSettingsProxy.canPushEncryptedEventsToDevice() : false
do {
// `isOneToOne` here is not the same as `isDirect` on the room. From the point of view of the push rule, a one-to-one room is a room with exactly two active members.
let settings = try await notificationSettingsProxy.getNotificationSettings(roomId: roomProxy.id,
isEncrypted: isEncrypted,
isOneToOne: roomProxy.infoPublisher.value.activeMembersCount == 2)
guard !Task.isCancelled else { return }
state.notificationSettingsState = .loaded(settings: settings)
if !state.isRestoringDefaultSetting {
state.bindings.allowCustomSetting = !settings.isDefault
}
} catch {
state.notificationSettingsState = .error
displayError(.loadingSettingsFailed)
}
}
private func toogleCustomSetting() {
guard case .loaded(let settings) = state.notificationSettingsState else { return }
guard state.bindings.allowCustomSetting == settings.isDefault else { return }
if state.bindings.allowCustomSetting {
setCustomMode(settings.mode)
} else {
restoreDefaultSetting()
}
}
private func restoreDefaultSetting() {
state.isRestoringDefaultSetting = true
Task {
do {
try await notificationSettingsProxy.restoreDefaultNotificationMode(roomId: roomProxy.id)
} catch {
displayError(.restoreDefaultFailed)
}
state.isRestoringDefaultSetting = false
}
}
private func setCustomMode(_ mode: RoomNotificationModeProxy) {
// Check if the new mode is already the current one
if case .loaded(let currentSettings) = state.notificationSettingsState {
if !currentSettings.isDefault, currentSettings.mode == mode {
return
}
}
state.pendingCustomMode = mode
Task {
do {
try await notificationSettingsProxy.setNotificationMode(roomId: roomProxy.id, mode: mode)
} catch {
displayError(.setModeFailed)
}
state.pendingCustomMode = nil
}
}
private func displayError(_ type: RoomNotificationSettingsScreenErrorType) {
switch type {
case .loadingSettingsFailed:
state.bindings.alertInfo = AlertInfo(id: type,
title: L10n.commonError,
message: L10n.screenRoomNotificationSettingsErrorLoadingSettings)
case .setModeFailed:
state.bindings.alertInfo = AlertInfo(id: type,
title: L10n.commonError,
message: L10n.screenRoomNotificationSettingsErrorSettingMode)
case .restoreDefaultFailed:
state.bindings.alertInfo = AlertInfo(id: type,
title: L10n.commonError,
message: L10n.screenRoomNotificationSettingsErrorRestoringDefault)
}
}
private func deleteCustomSetting() async {
state.deletingCustomSetting = true
do {
try await notificationSettingsProxy.restoreDefaultNotificationMode(roomId: roomProxy.id)
actionsSubject.send(.dismiss)
} catch {
displayError(.restoreDefaultFailed)
}
state.deletingCustomSetting = false
}
}