Add a new AccountSettings type and compose it with AppSettings into UserSettings.

The new type doesn't contain any settings yet.
This commit is contained in:
Doug
2026-09-25 13:42:37 +01:00
committed by Stefan Ceriu
parent 44fb05f489
commit 54cc1cba60
4 changed files with 74 additions and 14 deletions
@@ -0,0 +1,49 @@
//
// Copyright 2026 Element Creations Ltd.
//
// SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
// Please see LICENSE files in the repository root for full details.
//
import Foundation
/// The settings for a signed in account, composed of the app wide settings and the settings
/// specific to that account.
@dynamicMemberLookup
final nonisolated class UserSettings: Sendable {
let app: AppSettings
let account: AccountSettings
init(appSettings: AppSettings, accountSettings: AccountSettings) {
self.app = appSettings
self.account = accountSettings
}
static func volatile() -> UserSettings {
let appSettings = AppSettings.volatile()
return UserSettings(appSettings: appSettings, accountSettings: .init())
}
subscript<Value>(dynamicMember keyPath: ReferenceWritableKeyPath<AppSettings, Value>) -> Value {
get { app[keyPath: keyPath] }
set { app[keyPath: keyPath] = newValue }
}
subscript<Value>(dynamicMember keyPath: KeyPath<AppSettings, Value>) -> Value {
app[keyPath: keyPath]
}
subscript<Value>(dynamicMember keyPath: ReferenceWritableKeyPath<AccountSettings, Value>) -> Value {
get { account[keyPath: keyPath] }
set { account[keyPath: keyPath] = newValue }
}
subscript<Value>(dynamicMember keyPath: KeyPath<AccountSettings, Value>) -> Value {
account[keyPath: keyPath]
}
}
/// Settings specific to an account.
struct AccountSettings {
// Placeholder for now, implementation to follow with a dedicated user defaults store scoped to a specific user ID.
}
@@ -15,11 +15,11 @@ class UserSession: UserSessionProtocol {
private var authErrorCancellable: AnyCancellable?
let clientProxy: ClientProxyProtocol
let mediaProvider: MediaProviderProtocol
let userSettings: UserSettings
let mediaProvider: MediaProviderProtocol
let voiceMessageMediaManager: VoiceMessageMediaManagerProtocol
let liveLocationManager: LiveLocationManagerProtocol
/// Scans media content, `nil` when no content scanner is configured for the server.
let contentScannerService: ContentScannerServiceProtocol?
@@ -30,8 +30,14 @@ class UserSession: UserSessionProtocol {
sessionSecurityStateSubject.asCurrentValuePublisher()
}
init(clientProxy: ClientProxyProtocol, mediaProvider: MediaProviderProtocol, voiceMessageMediaManager: VoiceMessageMediaManagerProtocol, liveLocationManager: LiveLocationManagerProtocol) {
init(clientProxy: ClientProxyProtocol,
userSettings: UserSettings,
mediaProvider: MediaProviderProtocol,
voiceMessageMediaManager: VoiceMessageMediaManagerProtocol,
liveLocationManager: LiveLocationManagerProtocol) {
self.clientProxy = clientProxy
self.userSettings = userSettings
self.mediaProvider = mediaProvider
self.voiceMessageMediaManager = voiceMessageMediaManager
self.liveLocationManager = liveLocationManager
@@ -21,10 +21,11 @@ struct SessionSecurityState: Equatable {
// sourcery: AutoMockable
protocol UserSessionProtocol: Sendable {
var clientProxy: ClientProxyProtocol { get }
var userSettings: UserSettings { get }
var mediaProvider: MediaProviderProtocol { get }
var voiceMessageMediaManager: VoiceMessageMediaManagerProtocol { get }
var liveLocationManager: LiveLocationManagerProtocol { get }
/// Scans media content, `nil` when no content scanner is configured for the server.
var contentScannerService: ContentScannerServiceProtocol? { get }
@@ -59,9 +59,11 @@ class UserSessionStore: UserSessionStoreProtocol {
return .failure(.missingCredentials)
}
switch await restorePreviousLogin(credentials) {
let userSettings = UserSettings(appSettings: appSettings, accountSettings: .init())
switch await restorePreviousLogin(credentials, userSettings: userSettings) {
case .success(let clientProxy):
return await .success(buildUserSessionWithClient(clientProxy))
return await .success(buildUserSessionWithClient(clientProxy, userSettings: userSettings))
case .failure(let error):
MXLog.error("Failed restoring login with error: \(error)")
@@ -77,7 +79,8 @@ class UserSessionStore: UserSessionStoreProtocol {
do {
let session = try client.session()
let userID = try client.userId()
let clientProxy = try await setupProxyForClient(client)
let userSettings = UserSettings(appSettings: appSettings, accountSettings: .init())
let clientProxy = try await setupProxyForClient(client, userSettings: userSettings)
keychainController.setRestorationToken(RestorationToken(session: session,
sessionDirectories: sessionDirectories,
@@ -87,7 +90,7 @@ class UserSessionStore: UserSessionStoreProtocol {
MXLog.info("Set up session for user \(userID) at: \(sessionDirectories)")
return await .success(buildUserSessionWithClient(clientProxy))
return await .success(buildUserSessionWithClient(clientProxy, userSettings: userSettings))
} catch {
MXLog.error("Failed creating user session with error: \(error)")
return .failure(.failedSettingUpSession)
@@ -106,7 +109,7 @@ class UserSessionStore: UserSessionStoreProtocol {
// MARK: - Private
private func buildUserSessionWithClient(_ clientProxy: ClientProxyProtocol) async -> UserSessionProtocol {
private func buildUserSessionWithClient(_ clientProxy: ClientProxyProtocol, userSettings: UserSettings) async -> UserSessionProtocol {
let mediaProvider = MediaProvider(mediaLoader: clientProxy.mediaLoader,
imageCache: .onlyInMemory,
homeserverReachabilityPublisher: clientProxy.homeserverReachabilityPublisher)
@@ -115,16 +118,17 @@ class UserSessionStore: UserSessionStoreProtocol {
let liveLocationManager = await MainActor.run {
LiveLocationManager(clientProxy: clientProxy,
appSettings: appSettings)
userSettings: userSettings)
}
return UserSession(clientProxy: clientProxy,
userSettings: userSettings,
mediaProvider: mediaProvider,
voiceMessageMediaManager: voiceMessageMediaManager,
liveLocationManager: liveLocationManager)
}
private func restorePreviousLogin(_ credentials: KeychainCredentials) async -> Result<ClientProxyProtocol, UserSessionStoreError> {
private func restorePreviousLogin(_ credentials: KeychainCredentials, userSettings: UserSettings) async -> Result<ClientProxyProtocol, UserSessionStoreError> {
guard credentials.restorationToken.sessionDirectories.isNonTransientUserDataValid() else {
MXLog.error("Failed restoring login, missing non-transient user data")
return .failure(.failedRestoringLogin)
@@ -142,7 +146,7 @@ class UserSessionStore: UserSessionStoreProtocol {
Task(priority: .low) { await appHooks.remoteSettingsHook.updateCache(using: client) }
return try await .success(setupProxyForClient(client))
return try await .success(setupProxyForClient(client, userSettings: userSettings))
} catch UserSessionStoreError.failedSettingUpClientProxy(let error) {
// If this has failed, there is likely something wrong with the creation of the sync service
// There is nothing we can do, but at the same time we don't want the user to the get logged out
@@ -154,11 +158,11 @@ class UserSessionStore: UserSessionStoreProtocol {
}
}
private func setupProxyForClient(_ client: ClientProtocol) async throws -> ClientProxyProtocol {
private func setupProxyForClient(_ client: ClientProtocol, userSettings: UserSettings) async throws -> ClientProxyProtocol {
do {
return try await ClientProxy(client: client,
networkMonitor: networkMonitor,
appSettings: appSettings,
userSettings: userSettings,
analyticsService: analyticsService)
} catch {
throw UserSessionStoreError.failedSettingUpClientProxy(error)