Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Where/WhereUI/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ Layering, localization, preview, and testing conventions live in the feature
Project Locations-card GPS points through the cache's shared
`RegionArtworkProjection`. Never project, simplify, or spatially reduce
artwork in a card's `body`.
- Keep the Locations background on the selected year’s complete visited-region set,
in catalog order independent of card rank. Omit the catch-all Other entry from
the print. Use the injected outline cache.
- Keep the Elsewhere summary count and artwork on the same secondary-region input.
Use its own stylesheet tokens and the shared outline renderers.
- Keep Locations-card points on `YearReportModel`'s loaded
Expand Down
13 changes: 13 additions & 0 deletions Where/WhereUI/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -427,6 +427,19 @@ interactive viewport. Flyover's appearance, device, Dynamic Type, contrast,
layout-direction, and bold-text choices are session-only and apply only to
registered content.

### Locations background

The Locations root screen uses a stationary monochrome pattern of all regions
visited in the selected year, including manually recorded visits and Elsewhere.
The catch-all Other entry is omitted because it has no geographic outline.
Catalog order keeps the pattern stable when card rankings change. Planned stays
do not contribute. The shared outline cache supplies the region paths. Full-view
rosette rings sit behind staggered region outlines with a half-cell row offset. Slender
regions receive a bounded size adjustment to balance their visual weight. The
monochrome ink uses faint strokes without a shadow or blur. Appearance belongs
to `WhereStylesheet.locationsBackground`; Reduce Transparency hides the ink.
Empty, initial-loading, and failed screens show only the full-view rosette texture.

## Testing

Swift Testing in [`Tests/`](Tests) (`WhereUITests`), hosted in `StuffTestHost`
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import SnapshotKitTesting
import Testing
@testable import WhereUI

struct LocationsBackgroundSnapshotTests {
@Test func locationsBackground() async {
await assertSnapshots(of: LocationsBackground.self)
}
}
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
133 changes: 133 additions & 0 deletions Where/WhereUI/Sources/Primary/LocationsBackground.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import BroadwayUI
import RegionKit
import SwiftUI

/// Stationary, noninteractive paper texture for the root Locations viewport.
struct LocationsBackground: View {
let regions: [Region]

@Environment(\.stylesheet) private var stylesheet
@Environment(\.regionOutlinePathCache) private var cache
@State private var artwork: LocationsBackgroundArtwork?

private var style: WhereStylesheet.LocationsBackgroundStyle {
stylesheet.locationsBackground
}

private var requestedRegions: [Region] {
style.showsInk ? regions.filter { $0 != .other } : []
}

var body: some View {
ZStack {
style.paper
if style.showsInk {
SecurityPrintRosette(
tint: style.ink,
wobble: style.rosette.wobble,
lineWidth: style.rosette.lineWidth,
primaryRingSpacing: style.rosette.primaryRingSpacing,
secondaryRingSpacing: style.rosette.secondaryRingSpacing,
primaryOpacity: style.rosetteOpacity,
secondaryOpacity: style.rosetteOpacity,
)
silhouettes
}
}
.clipped()
.allowsHitTesting(false)
.accessibilityHidden(true)
.task(id: requestedRegions) {

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe not for this PR, but I think we have this duplicated in a few spots, maybe a good candidate for a shared modifier of some sort?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Posted by an AI agent on @kyleve's behalf.

Extracted this in #325 (commit b9b63d4). The Locations background, Elsewhere card, region cards, welcome card, and forecast panel now share regionArtworkTask plus RegionArtworkModel. The shared lifecycle handles cache injection, cancellation, and stale results, while the region card retains compatible outlines during point refreshes. Fifteen focused tests and six existing snapshot suites passed without reference changes. Since #324 has merged, the follow-up targets main.

guard let cache else { return }
if let loaded = await LocationsBackgroundArtwork.load(
regions: requestedRegions,
cache: cache,
) {
guard !Task.isCancelled else { return }
artwork = loaded
}
}
}

private var silhouettes: some View {
GeometryReader { proxy in
let items = artwork?.items(for: requestedRegions) ?? []
let cells = LocationsBackgroundLayout.cells(
count: items.count,
in: proxy.size,
preferredCellSize: style.preferredCellSize,
)
ZStack(alignment: .topLeading) {
ForEach(cells) { cell in
let item = items[cell.artworkIndex]
RegionOutlineArtwork(
path: item.path,
tint: style.ink,
style: balancedArtwork(for: item.path),
)
.frame(width: cell.frame.width, height: cell.frame.height)
.position(x: cell.frame.midX, y: cell.frame.midY)
}
}
}
}

private func balancedArtwork(for path: Path) -> WhereStylesheet.CardStyle.RegionShape.Artwork {
var artwork = style.artwork
let bounds = path.boundingRect
let shortSide = min(bounds.width, bounds.height)
guard shortSide > 0 else { return artwork }
// Give slender regions more room without changing the shared geographic projection.
artwork.scale *= min(
style.maximumAspectScale,
sqrt(max(bounds.width, bounds.height) / shortSide),
)
return artwork
}
}

#if DEBUG
import SnapshotKit

extension LocationsBackground: SnapshotProviding {
static var snapshots: [SnapshotCase] {
artworkSnapshot(name: "Empty", regions: [])
artworkSnapshot(name: "One", regions: [.california])
artworkSnapshot(name: "Several", regions: [.california, .newYork, .canada, .other])
artworkSnapshot(name: "Many", regions: Array(Region.allCases.prefix(40)))
artworkSnapshot(
name: "ReduceTransparency",
regions: [.canada, .other],
reduceTransparency: true,
)
}

private static func artworkSnapshot(
name: String,
regions: [Region],
reduceTransparency: Bool = false,
) -> SnapshotCase {
let cache = RegionOutlinePathCache()
return whereSnapshot(
name: name,
configurations: .phoneLightDark,
measurementReadiness: .immediate,
onReadyToSnapshot: {
_ = await LocationsBackgroundArtwork.load(regions: regions, cache: cache)
},
) {
LocationsBackground(regions: regions)
.environment(\.regionOutlinePathCache, cache)
.bTraitOverrides { traits, overrides in
var accessibility = traits.accessibility
accessibility.isReduceTransparencyEnabled = reduceTransparency
overrides.accessibility = accessibility
}
}
}
}

#Preview {
LocationsBackground.snapshotPreviews
}
#endif
Loading
Loading