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:
@@ -34,6 +34,8 @@ struct SettingsView: View {
|
|||||||
@State private var nameErrorMessage: String?
|
@State private var nameErrorMessage: String?
|
||||||
@State private var isSavingLanguage = false
|
@State private var isSavingLanguage = false
|
||||||
@State private var languageErrorMessage: String?
|
@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
|
/// Full Local Sync and cache-clearing both need a real connection to be
|
||||||
/// safe — clearing while offline (or letting Full Local Sync think it
|
/// safe — clearing while offline (or letting Full Local Sync think it
|
||||||
@@ -248,6 +250,12 @@ struct SettingsView: View {
|
|||||||
Divider()
|
Divider()
|
||||||
appearanceRow
|
appearanceRow
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if let preferencesErrorMessage {
|
||||||
|
Text(preferencesErrorMessage)
|
||||||
|
.font(.caption)
|
||||||
|
.foregroundStyle(.red)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.frame(maxWidth: 480, alignment: .leading)
|
.frame(maxWidth: 480, alignment: .leading)
|
||||||
.task { await refreshProfile() }
|
.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
|
// MARK: - Offline & Sync
|
||||||
|
|
||||||
private var offlineSyncDetail: some View {
|
private var offlineSyncDetail: some View {
|
||||||
|
|||||||
Reference in New Issue
Block a user