feat(settings): shared save path for preference toggles

savePreference(_:) reads the current OutlineUserPreferences, flips one
field, sends the whole object via updateUserPreferences. Every Behavior/
Display toggle added next reuses this instead of its own copy of the
same read-modify-write.
This commit is contained in:
2026-08-15 20:30:31 +01:00
parent a4c09f28a0
commit 49b00d6c80
@@ -34,6 +34,8 @@ struct SettingsView: View {
@State private var nameErrorMessage: String?
@State private var isSavingLanguage = false
@State private var languageErrorMessage: String?
@State private var isSavingPreferences = false
@State private var preferencesErrorMessage: String?
/// Full Local Sync and cache-clearing both need a real connection to be
/// safe clearing while offline (or letting Full Local Sync think it
@@ -248,6 +250,12 @@ struct SettingsView: View {
Divider()
appearanceRow
}
if let preferencesErrorMessage {
Text(preferencesErrorMessage)
.font(.caption)
.foregroundStyle(.red)
}
}
.frame(maxWidth: 480, alignment: .leading)
.task { await refreshProfile() }
@@ -338,6 +346,46 @@ struct SettingsView: View {
}
}
/// Every toggle in the Preferences page shares this: read
/// `session.userPreferences` (or an empty object if nothing's been set
/// yet), flip the one field the caller cares about, send the *whole*
/// object back see `UpdateUserPreferencesRequest`'s own doc comment
/// for why the full object rather than a partial diff.
private func savePreference(_ update: (inout OutlineUserPreferences) -> Void) async {
guard let apiClient = session.apiClient, let userId = session.userId else { return }
var preferences = session.userPreferences ?? OutlineUserPreferences()
update(&preferences)
isSavingPreferences = true
defer { isSavingPreferences = false }
do {
let updated = try await apiClient.updateUserPreferences(
UpdateUserPreferencesRequest(id: userId, preferences: preferences)
)
session.applyUpdatedProfile(updated)
preferencesErrorMessage = nil
} catch {
preferencesErrorMessage = outlineErrorMessage(error, fallback: "Couldn't update your preferences.")
}
}
private func preferenceToggleRow(
_ title: String,
description: String,
isOn: Bool,
onChange: @escaping (Bool) -> Void
) -> some View {
VStack(alignment: .leading, spacing: 2) {
Toggle(isOn: Binding(get: { isOn }, set: onChange)) {
VStack(alignment: .leading, spacing: 2) {
Text(title)
Text(description)
.font(.caption)
.foregroundStyle(.secondary)
}
}
}
}
// MARK: - Offline & Sync
private var offlineSyncDetail: some View {