mirror of
https://github.com/Novaloop-AG/element-x-ios.git
synced 2026-11-04 19:44:05 +00:00
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:
@@ -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?
|
private var authErrorCancellable: AnyCancellable?
|
||||||
|
|
||||||
let clientProxy: ClientProxyProtocol
|
let clientProxy: ClientProxyProtocol
|
||||||
let mediaProvider: MediaProviderProtocol
|
let userSettings: UserSettings
|
||||||
|
|
||||||
|
let mediaProvider: MediaProviderProtocol
|
||||||
let voiceMessageMediaManager: VoiceMessageMediaManagerProtocol
|
let voiceMessageMediaManager: VoiceMessageMediaManagerProtocol
|
||||||
let liveLocationManager: LiveLocationManagerProtocol
|
let liveLocationManager: LiveLocationManagerProtocol
|
||||||
|
|
||||||
/// Scans media content, `nil` when no content scanner is configured for the server.
|
/// Scans media content, `nil` when no content scanner is configured for the server.
|
||||||
let contentScannerService: ContentScannerServiceProtocol?
|
let contentScannerService: ContentScannerServiceProtocol?
|
||||||
|
|
||||||
@@ -30,8 +30,14 @@ class UserSession: UserSessionProtocol {
|
|||||||
sessionSecurityStateSubject.asCurrentValuePublisher()
|
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.clientProxy = clientProxy
|
||||||
|
self.userSettings = userSettings
|
||||||
|
|
||||||
self.mediaProvider = mediaProvider
|
self.mediaProvider = mediaProvider
|
||||||
self.voiceMessageMediaManager = voiceMessageMediaManager
|
self.voiceMessageMediaManager = voiceMessageMediaManager
|
||||||
self.liveLocationManager = liveLocationManager
|
self.liveLocationManager = liveLocationManager
|
||||||
|
|||||||
@@ -21,10 +21,11 @@ struct SessionSecurityState: Equatable {
|
|||||||
// sourcery: AutoMockable
|
// sourcery: AutoMockable
|
||||||
protocol UserSessionProtocol: Sendable {
|
protocol UserSessionProtocol: Sendable {
|
||||||
var clientProxy: ClientProxyProtocol { get }
|
var clientProxy: ClientProxyProtocol { get }
|
||||||
|
var userSettings: UserSettings { get }
|
||||||
|
|
||||||
var mediaProvider: MediaProviderProtocol { get }
|
var mediaProvider: MediaProviderProtocol { get }
|
||||||
var voiceMessageMediaManager: VoiceMessageMediaManagerProtocol { get }
|
var voiceMessageMediaManager: VoiceMessageMediaManagerProtocol { get }
|
||||||
var liveLocationManager: LiveLocationManagerProtocol { get }
|
var liveLocationManager: LiveLocationManagerProtocol { get }
|
||||||
|
|
||||||
/// Scans media content, `nil` when no content scanner is configured for the server.
|
/// Scans media content, `nil` when no content scanner is configured for the server.
|
||||||
var contentScannerService: ContentScannerServiceProtocol? { get }
|
var contentScannerService: ContentScannerServiceProtocol? { get }
|
||||||
|
|
||||||
|
|||||||
@@ -59,9 +59,11 @@ class UserSessionStore: UserSessionStoreProtocol {
|
|||||||
return .failure(.missingCredentials)
|
return .failure(.missingCredentials)
|
||||||
}
|
}
|
||||||
|
|
||||||
switch await restorePreviousLogin(credentials) {
|
let userSettings = UserSettings(appSettings: appSettings, accountSettings: .init())
|
||||||
|
|
||||||
|
switch await restorePreviousLogin(credentials, userSettings: userSettings) {
|
||||||
case .success(let clientProxy):
|
case .success(let clientProxy):
|
||||||
return await .success(buildUserSessionWithClient(clientProxy))
|
return await .success(buildUserSessionWithClient(clientProxy, userSettings: userSettings))
|
||||||
case .failure(let error):
|
case .failure(let error):
|
||||||
MXLog.error("Failed restoring login with error: \(error)")
|
MXLog.error("Failed restoring login with error: \(error)")
|
||||||
|
|
||||||
@@ -77,7 +79,8 @@ class UserSessionStore: UserSessionStoreProtocol {
|
|||||||
do {
|
do {
|
||||||
let session = try client.session()
|
let session = try client.session()
|
||||||
let userID = try client.userId()
|
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,
|
keychainController.setRestorationToken(RestorationToken(session: session,
|
||||||
sessionDirectories: sessionDirectories,
|
sessionDirectories: sessionDirectories,
|
||||||
@@ -87,7 +90,7 @@ class UserSessionStore: UserSessionStoreProtocol {
|
|||||||
|
|
||||||
MXLog.info("Set up session for user \(userID) at: \(sessionDirectories)")
|
MXLog.info("Set up session for user \(userID) at: \(sessionDirectories)")
|
||||||
|
|
||||||
return await .success(buildUserSessionWithClient(clientProxy))
|
return await .success(buildUserSessionWithClient(clientProxy, userSettings: userSettings))
|
||||||
} catch {
|
} catch {
|
||||||
MXLog.error("Failed creating user session with error: \(error)")
|
MXLog.error("Failed creating user session with error: \(error)")
|
||||||
return .failure(.failedSettingUpSession)
|
return .failure(.failedSettingUpSession)
|
||||||
@@ -106,7 +109,7 @@ class UserSessionStore: UserSessionStoreProtocol {
|
|||||||
|
|
||||||
// MARK: - Private
|
// MARK: - Private
|
||||||
|
|
||||||
private func buildUserSessionWithClient(_ clientProxy: ClientProxyProtocol) async -> UserSessionProtocol {
|
private func buildUserSessionWithClient(_ clientProxy: ClientProxyProtocol, userSettings: UserSettings) async -> UserSessionProtocol {
|
||||||
let mediaProvider = MediaProvider(mediaLoader: clientProxy.mediaLoader,
|
let mediaProvider = MediaProvider(mediaLoader: clientProxy.mediaLoader,
|
||||||
imageCache: .onlyInMemory,
|
imageCache: .onlyInMemory,
|
||||||
homeserverReachabilityPublisher: clientProxy.homeserverReachabilityPublisher)
|
homeserverReachabilityPublisher: clientProxy.homeserverReachabilityPublisher)
|
||||||
@@ -115,16 +118,17 @@ class UserSessionStore: UserSessionStoreProtocol {
|
|||||||
|
|
||||||
let liveLocationManager = await MainActor.run {
|
let liveLocationManager = await MainActor.run {
|
||||||
LiveLocationManager(clientProxy: clientProxy,
|
LiveLocationManager(clientProxy: clientProxy,
|
||||||
appSettings: appSettings)
|
userSettings: userSettings)
|
||||||
}
|
}
|
||||||
|
|
||||||
return UserSession(clientProxy: clientProxy,
|
return UserSession(clientProxy: clientProxy,
|
||||||
|
userSettings: userSettings,
|
||||||
mediaProvider: mediaProvider,
|
mediaProvider: mediaProvider,
|
||||||
voiceMessageMediaManager: voiceMessageMediaManager,
|
voiceMessageMediaManager: voiceMessageMediaManager,
|
||||||
liveLocationManager: liveLocationManager)
|
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 {
|
guard credentials.restorationToken.sessionDirectories.isNonTransientUserDataValid() else {
|
||||||
MXLog.error("Failed restoring login, missing non-transient user data")
|
MXLog.error("Failed restoring login, missing non-transient user data")
|
||||||
return .failure(.failedRestoringLogin)
|
return .failure(.failedRestoringLogin)
|
||||||
@@ -142,7 +146,7 @@ class UserSessionStore: UserSessionStoreProtocol {
|
|||||||
|
|
||||||
Task(priority: .low) { await appHooks.remoteSettingsHook.updateCache(using: client) }
|
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) {
|
} catch UserSessionStoreError.failedSettingUpClientProxy(let error) {
|
||||||
// If this has failed, there is likely something wrong with the creation of the sync service
|
// 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
|
// 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 {
|
do {
|
||||||
return try await ClientProxy(client: client,
|
return try await ClientProxy(client: client,
|
||||||
networkMonitor: networkMonitor,
|
networkMonitor: networkMonitor,
|
||||||
appSettings: appSettings,
|
userSettings: userSettings,
|
||||||
analyticsService: analyticsService)
|
analyticsService: analyticsService)
|
||||||
} catch {
|
} catch {
|
||||||
throw UserSessionStoreError.failedSettingUpClientProxy(error)
|
throw UserSessionStoreError.failedSettingUpClientProxy(error)
|
||||||
|
|||||||
Reference in New Issue
Block a user