iOS: per-setting sync table with sync icons, dropdowns, colour resets
Generalise the two-tier SettingsSync into a server-driven flag map: each syncable key has an account-wide flag (fetched from the server); pull applies only on-flagged keys, push (read-modify-write) sends only on-flagged keys plus the flag map. Per-row link icon toggles a setting's sync; a global switch flips all. Enabling a flag adopts this device's current value as the shared one. - AppSettings: fix bg_color mapping (was background_color), add cache_months, month_view_paged, sync_flags - SettingsView: uniform rows (sync icon | name | value), ContrastSelector and the button pickers replaced by menu dropdowns, every colour gets a reset to a canonical default, global "sync all" toggle; account settings (privacy, group calendar, hide profile) kept in their own block; language and text/line contrast kept device-local (iOS-specific) - unify default line colour to #3A3A52 across views Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -15,11 +15,14 @@ struct AppSettings: Codable {
|
||||
var monthLabelColor: String = "#7090c0"
|
||||
var textColor: String = "#FFFFFF"
|
||||
var backgroundColor: String = "#000000"
|
||||
var lineColor: String = "#3A3A3C"
|
||||
var lineColor: String = "#3A3A52"
|
||||
var privateEventVisibility: String = "busy" // 'hidden' | 'busy'
|
||||
var groupVisibleCalendarId: Int? = nil
|
||||
var defaultReminderMinutes: Int? = nil // minutes before start; nil = off
|
||||
var defaultEventDurationMinutes: Int = 60 // applied to a new event's end time
|
||||
var cacheMonths: Int = 3 // preloaded month range (device-local by default)
|
||||
var monthViewPaged: Bool = false // month view swipe-paging (device-local by default)
|
||||
var syncFlags: [String: Bool]? = nil // server-resolved per-setting sync flags
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case defaultView = "default_view"
|
||||
@@ -35,21 +38,24 @@ struct AppSettings: Codable {
|
||||
case monthDividerColor = "month_divider_color"
|
||||
case monthLabelColor = "month_label_color"
|
||||
case textColor = "text_color"
|
||||
case backgroundColor = "background_color"
|
||||
case backgroundColor = "bg_color"
|
||||
case lineColor = "line_color"
|
||||
case privateEventVisibility = "private_event_visibility"
|
||||
case groupVisibleCalendarId = "group_visible_calendar_id"
|
||||
case defaultReminderMinutes = "default_reminder_minutes"
|
||||
case defaultEventDurationMinutes = "default_event_duration_minutes"
|
||||
case cacheMonths = "cache_months"
|
||||
case monthViewPaged = "month_view_paged"
|
||||
case syncFlags = "sync_flags"
|
||||
}
|
||||
|
||||
init() {}
|
||||
|
||||
/// Resilient decoding: the server only stores a subset of these fields
|
||||
/// (e.g. it has no `text_color`/`background_color`/`line_color`, which are
|
||||
/// iOS-only). Using `decodeIfPresent` with the property defaults means a
|
||||
/// missing key no longer aborts the whole decode — otherwise the entire
|
||||
/// settings sync silently breaks.
|
||||
/// Resilient decoding: a server may omit some keys. Using `decodeIfPresent`
|
||||
/// with the property defaults means a missing key no longer aborts the whole
|
||||
/// decode — otherwise the entire settings sync silently breaks. (Note:
|
||||
/// `background_color` was previously mis-mapped; the server column is
|
||||
/// `bg_color`, now corrected above.)
|
||||
init(from decoder: Decoder) throws {
|
||||
let c = try decoder.container(keyedBy: CodingKeys.self)
|
||||
let d = AppSettings()
|
||||
@@ -72,6 +78,9 @@ struct AppSettings: Codable {
|
||||
groupVisibleCalendarId = try c.decodeIfPresent(Int.self, forKey: .groupVisibleCalendarId)
|
||||
defaultReminderMinutes = try c.decodeIfPresent(Int.self, forKey: .defaultReminderMinutes)
|
||||
defaultEventDurationMinutes = try c.decodeIfPresent(Int.self, forKey: .defaultEventDurationMinutes) ?? d.defaultEventDurationMinutes
|
||||
cacheMonths = try c.decodeIfPresent(Int.self, forKey: .cacheMonths) ?? d.cacheMonths
|
||||
monthViewPaged = try c.decodeIfPresent(Bool.self, forKey: .monthViewPaged) ?? d.monthViewPaged
|
||||
syncFlags = try c.decodeIfPresent([String: Bool].self, forKey: .syncFlags)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -579,7 +579,7 @@ class CalendarStore {
|
||||
todayColorHex: defaults.string(forKey: "todayColor") ?? "#4285f4",
|
||||
textColorHex: defaults.string(forKey: "textColor") ?? "#FFFFFF",
|
||||
backgroundColorHex: defaults.string(forKey: "backgroundColor") ?? "#000000",
|
||||
lineColorHex: defaults.string(forKey: "lineColor") ?? "#3A3A3C",
|
||||
lineColorHex: defaults.string(forKey: "lineColor") ?? "#3A3A52",
|
||||
primaryColorHex: defaults.string(forKey: "primaryColor") ?? "#4285f4",
|
||||
accentColorHex: defaults.string(forKey: "accentColor") ?? "#ea4335",
|
||||
language: defaults.string(forKey: "appLanguage") ?? "system"
|
||||
|
||||
@@ -81,6 +81,13 @@ private let strings: [String: [String: String]] = [
|
||||
"settings.sync": "Einstellungen synchronisieren",
|
||||
"settings.sync.desc": "Darstellung mit dem Server abgleichen",
|
||||
"settings.sync.footer": "Wenn aktiv, werden Farben, Kontraste und Stundenhöhe mit dem Server abgeglichen (der Server hat Vorrang). Ansicht, erster Wochentag und das Ausgrauen vergangener Termine werden immer synchronisiert – auch wenn der Schalter aus ist.",
|
||||
"settings.sync_all": "Alle synchronisieren",
|
||||
"settings.sync_all.desc": "Diese Einstellungen zwischen deinen Geräten teilen",
|
||||
"settings.sync_this": "Zwischen Geräten synchronisieren",
|
||||
"settings.reset": "Zurücksetzen",
|
||||
"settings.appearance": "Ansicht",
|
||||
"settings.device": "Nur auf diesem Gerät",
|
||||
"settings.device.footer": "Diese Einstellungen gelten nur auf diesem Gerät und werden nicht synchronisiert.",
|
||||
|
||||
"settings.cache.header": "Vorladen",
|
||||
"settings.cache.title": "Vorladen",
|
||||
@@ -440,6 +447,13 @@ private let strings: [String: [String: String]] = [
|
||||
"settings.sync": "Sync settings",
|
||||
"settings.sync.desc": "Keep appearance in sync with the server",
|
||||
"settings.sync.footer": "When on, colors, contrasts and hour height sync with the server (the server wins). View, first weekday and dimming past events always sync – even when the switch is off.",
|
||||
"settings.sync_all": "Sync all",
|
||||
"settings.sync_all.desc": "Share these settings across your devices",
|
||||
"settings.sync_this": "Sync across devices",
|
||||
"settings.reset": "Reset",
|
||||
"settings.appearance": "View",
|
||||
"settings.device": "This device only",
|
||||
"settings.device.footer": "These settings apply to this device only and are not synced.",
|
||||
|
||||
"settings.cache.header": "Preloading",
|
||||
"settings.cache.title": "Preloading",
|
||||
|
||||
Reference in New Issue
Block a user