Add Where multi-theme foundation - #244
Conversation
688d736 to
ff051b1
Compare
| get { | ||
| guard | ||
| let rawValue = store.object(forKey: Keys.theme.rawValue) as? String, | ||
| let theme = WhereTheme(rawValue: rawValue) |
There was a problem hiding this comment.
To check: Is this expensive? If so, we may want a read cache of these.
There was a problem hiding this comment.
Posted by an AI agent on kyleve's behalf.
Checked this: UserDefaults serves these reads from its in-process representation, and WhereModel.theme is already the observable runtime cache used by SwiftUI, so I left the preference getter uncached.
| // empty map rather than failing (the widget then uses default looks). | ||
| appearances = try container | ||
| .decodeIfPresent([Region: RegionAppearance].self, forKey: .appearances) ?? [:] | ||
| theme = try container.decodeIfPresent(WhereTheme.self, forKey: .theme) ?? .standard |
There was a problem hiding this comment.
IMO this should be written and read separately; I think the data JSON and the theme info should be separate so changing one doesn't require rewriting the other.
There was a problem hiding this comment.
Posted by an AI agent on kyleve's behalf.
Addressed in f18d16f. Widget presentation now lives in a separate widget-presentation.json; WidgetSnapshot is data-only again, and a theme change writes only the small presentation value.
| public func configureTheme(_ theme: WhereTheme) { | ||
| guard theme != self.theme else { return } | ||
| self.theme = theme | ||
| presentationRevision &+= 1 |
There was a problem hiding this comment.
This feels like a smell to me; I don't know why we'd need it if we're just poking data on change.
There was a problem hiding this comment.
Posted by an AI agent on kyleve's behalf.
Addressed in f18d16f. Theme changes no longer invalidate or rebuild the data snapshot. A dedicated WidgetPresentationPublisher writes the separate theme file and reloads WidgetKit.
| /// Serialize writes through a task chain. A theme change cancels a queued | ||
| /// superseded write; a write already in flight finishes before its | ||
| /// replacement, so the newest selection is always the final external state. | ||
| private func enqueue(_ snapshot: WidgetSnapshot, revision: UInt64) async { |
There was a problem hiding this comment.
This feels a bit complicated; shouldn't we just finish in progress writes? Also, I think if we split presentation info from widget data it's easier? Maybe?
There was a problem hiding this comment.
Posted by an AI agent on kyleve's behalf.
Addressed in f18d16f. Removing presentation from WidgetSnapshotPublisher eliminated its revision/task-chain machinery. Actor-serialized presentation writes finish in order, and the model's cancellable task suppresses superseded queued selections.
| } | ||
|
|
||
| /// Preview a theme without writing device preferences. | ||
| public func previewTheme(_ newTheme: WhereTheme) { |
There was a problem hiding this comment.
To check: How does this actually kick the theme I the app root to update it? And if it doesn't, what is the value of this method?
There was a problem hiding this comment.
Posted by an AI agent on kyleve's behalf.
Added the requested inline comment in previewTheme(_:) documenting the observation path: RootView reads the observable theme property into whereBroadwayRoot, so the assignment immediately re-resolves the live presentation tree without persisting the preview. Commit e5407c0.
| } | ||
|
|
||
| /// A compact rendering of the current Liquid Glass location-card language. | ||
| private struct WhereThemePreviewArtwork: View { |
There was a problem hiding this comment.
Hmm, I don't love rebuilding the card; could we make the card more responsive to smaller rendering sizes perhaps? Eg, hiding content based on some passed in render size/purpose.
(Another option would be to always show the previews vertically stacked, and always include a full size card; which might be nice given the card is the primary UI you'll change!)
There was a problem hiding this comment.
Posted by an AI agent on kyleve's behalf.
Removed the duplicate WhereThemePreviewArtwork implementation. Each option now renders the production RegionSummaryCard under its own theme, using the existing compact card size plus a theme-specimen purpose that only omits the provenance stamp at narrow width. This retains the real rosette, material, typography, count, and progress behavior; the full light/dark/contrast/RTL/Dynamic Type snapshot matrix was re-recorded and reviewed. Commit e4af1ce.
…ndation # Conflicts: # Where/WhereCore/README.md # Where/WhereCore/Sources/Preferences/WherePreferences.swift # Where/WhereCore/Tests/WherePreferencesTests.swift # Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPad.png # Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPad_accessibility.png # Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPad_ax5.png # Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPad_contrast.png # Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPad_dark.png # Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPhone.png # Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPhone_accessibility.png # Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPhone_ax5.png # Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPhone_contrast.png # Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPhone_dark.png # Where/WhereUI/Sources/Resources/Localizable.xcstrings
…ndation # Conflicts: # Where/WhereCore/README.md # Where/WhereCore/Sources/Preferences/WherePreferences.swift # Where/WhereCore/Tests/WherePreferencesTests.swift # Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPad.png # Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPad_accessibility.png # Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPad_ax5.png # Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPad_contrast.png # Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPad_dark.png # Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPhone.png # Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPhone_accessibility.png # Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPhone_ax5.png # Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPhone_contrast.png # Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPhone_dark.png # Where/WhereUI/Sources/Settings/AppearanceSettingsView.swift
Summary
standardandalternatetheme identities backed by device-local preferences and BroadwayThe two themes are intentionally identical scaffolding in this PR. Standard is the default and preserves the current app, widget, and snippet presentation. Theme-only widget changes write a small
widget-presentation.jsonvalue and reload timelines without rebuilding the annual data snapshot.Testing
./swiftformat --lint./sf-symbols --lint./xcstrings --lint./test WhereCoreTests(551 tests passed)./test WhereUITests(420 tests passed)./test WhereIntentsTests(30 tests passed)./test --snapshots(42 snapshot tests passed)./test --everything(1,850 unit tests and 42 snapshot tests passed)Stack
mainand should merge first.