feat(apple): add the contacts feature model

MVVM model for the device list, its detail, and the two consent prompts.
Accepting an offer is the only path that returns a ticket, matching the
core: a declined offer hands over nothing.

Grant lifetime lives in preferences because the core holds it in memory
only, so it is pushed back on every start rather than silently reverting to
the default.
This commit is contained in:
2026-08-06 17:48:18 +02:00
parent 3c89c34c6e
commit 256ff89423
3 changed files with 449 additions and 1 deletions

View File

@@ -122,6 +122,8 @@ struct AppPreferences: Equatable {
var themeMode: ThemeMode
var diagnosticsInstallId: String
var relayConfiguration: RelayConfiguration
/// Idle lifetime applied to grants this device issues from now on.
var grantLifetime: GrantLifetimeOption
}
struct AppPreferencesDefaults {
@@ -145,6 +147,7 @@ final class AppPreferencesRepository: ObservableObject {
static let themeMode = "theme_mode"
static let diagnosticsInstallId = "diagnostics_install_id"
static let relayConfiguration = "relay_configuration"
static let grantLifetime = "grant_lifetime"
}
init(defaults: UserDefaults = .standard, fallback: AppPreferencesDefaults) {
@@ -158,12 +161,15 @@ final class AppPreferencesRepository: ObservableObject {
let folder = resolveReceiveFolder(defaults, fallback: fallback.receiveFolder)
let themeMode = defaults.string(forKey: Key.themeMode).flatMap(ThemeMode.init(rawValue:)) ?? fallback.themeMode
let installId = defaults.string(forKey: Key.diagnosticsInstallId) ?? ""
let grantLifetime = defaults.string(forKey: Key.grantLifetime)
.flatMap(GrantLifetimeOption.init(rawValue:)) ?? .days90
return AppPreferences(
username: username,
receiveFolder: folder,
themeMode: themeMode,
diagnosticsInstallId: installId,
relayConfiguration: resolveRelayConfiguration(defaults)
relayConfiguration: resolveRelayConfiguration(defaults),
grantLifetime: grantLifetime
)
}
@@ -209,6 +215,11 @@ final class AppPreferencesRepository: ObservableObject {
setReceiveFolder(fallback.receiveFolder)
}
func setGrantLifetime(_ lifetime: GrantLifetimeOption) {
defaults.set(lifetime.rawValue, forKey: Key.grantLifetime)
reload()
}
func setThemeMode(_ mode: ThemeMode) {
defaults.set(mode.rawValue, forKey: Key.themeMode)
reload()