diff --git a/Where/WhereCore/README.md b/Where/WhereCore/README.md index b21d6298f..ea6a8dcbe 100644 --- a/Where/WhereCore/README.md +++ b/Where/WhereCore/README.md @@ -165,7 +165,7 @@ one it belongs to rather than to a god-object: `InstallationRecordingContextStoring` keeps the persistence adapter outside the domain value. - **`WherePreferences`** — persisted user intent (onboarding, - reminder / summary schedules, Locations-card GPS-dot and annual-forecast visibility) plus the + reminder / summary schedules, Locations-card GPS-dot and estimated-time/planning visibility) plus the year-keyed Location-card counts and Codable recording-warning generation used for presentation continuity, behind a `KeyValueStore`. `RecordingConfigurationWarningCondition` evaluates the live device authority, recording choice, and authorization tuple in Core. The store has no diff --git a/Where/WhereCore/Sources/Forecasting/LocationForecast.swift b/Where/WhereCore/Sources/Forecasting/LocationForecast.swift index 4cc615775..1effb0aea 100644 --- a/Where/WhereCore/Sources/Forecasting/LocationForecast.swift +++ b/Where/WhereCore/Sources/Forecasting/LocationForecast.swift @@ -45,11 +45,13 @@ public struct LocationForecast: Hashable, Sendable { let baselineRate = Double(yearToDateDays) / Double(elapsedDays) let tomorrow = today.adding(days: 1) - let matchingStay = plannedStay.flatMap { stay in - stay.region == region && stay.through >= today ? stay : nil + let activeStay = plannedStay.flatMap { stay in + stay.through >= tomorrow ? stay : nil } - let plannedEnd = matchingStay.map { min($0.through, lastDay) } - let plannedDays = plannedEnd.map { tomorrow.days(through: $0).count } ?? 0 + let plannedEnd = activeStay.map { min($0.through, lastDay) } + let plannedDays = activeStay?.region == region + ? plannedEnd.map { tomorrow.days(through: $0).count } ?? 0 + : 0 let projectionStart = plannedEnd?.adding(days: 1) ?? tomorrow let remainingDays = projectionStart.days(through: lastDay).count let projectedRemainingDays = baselineRate * Double(remainingDays) diff --git a/Where/WhereCore/Sources/Preferences/WherePreferences.swift b/Where/WhereCore/Sources/Preferences/WherePreferences.swift index dde6a0cab..723827e6d 100644 --- a/Where/WhereCore/Sources/Preferences/WherePreferences.swift +++ b/Where/WhereCore/Sources/Preferences/WherePreferences.swift @@ -40,10 +40,11 @@ public final class WherePreferences { set { store.set(newValue, forKey: Keys.showsRecordedLocationDots.rawValue) } } - /// Whether the annual-estimate summary appears on the Locations tab. + /// Whether annual estimates, stay planning, and their projections appear. /// Defaults to `true` so an existing or fresh install sees the feature until - /// the user explicitly turns it off. - public var showsLocationForecastsOnLocationsTab: Bool { + /// the user explicitly turns it off. The defaults key retains its original + /// Locations-only name so an existing choice survives the broader meaning. + public var showsEstimatedTimeAndPlanning: Bool { get { store.object(forKey: Keys.showsLocationForecastsOnLocationsTab.rawValue) as? Bool ?? true diff --git a/Where/WhereCore/Tests/LocationForecastTests.swift b/Where/WhereCore/Tests/LocationForecastTests.swift index ab423766d..a1b451935 100644 --- a/Where/WhereCore/Tests/LocationForecastTests.swift +++ b/Where/WhereCore/Tests/LocationForecastTests.swift @@ -102,25 +102,54 @@ struct LocationForecastTests { #expect(forecast.estimatedTotalDays == 274) } - @Test func anotherRegionsStayDoesNotChangeTheEstimate() throws { - let baseline = try #require(LocationForecast.estimate( + @Test func anotherRegionsStayReservesItsDaysFromTheProjection() throws { + let withCaliforniaStay = try #require(LocationForecast.estimate( region: .newYork, report: Self.report(), asOf: Self.date(7, 1), calendar: Self.calendar, - plannedStay: nil, + plannedStay: PlannedStay( + region: .california, + through: CalendarDay(year: 2026, month: 8, day: 1), + ), )) - let withCaliforniaStay = try #require(LocationForecast.estimate( + + #expect(withCaliforniaStay.plannedDays == 0) + #expect(withCaliforniaStay.projectedRemainingDays == 76) + #expect(withCaliforniaStay.estimatedTotalDays == 167) + } + + @Test func anotherRegionsCrossYearStayLeavesOnlyRecordedDays() throws { + let forecast = try #require(LocationForecast.estimate( region: .newYork, report: Self.report(), asOf: Self.date(7, 1), calendar: Self.calendar, plannedStay: PlannedStay( region: .california, - through: CalendarDay(year: 2026, month: 8, day: 1), + through: CalendarDay(year: 2027, month: 2, day: 1), + ), + )) + + #expect(forecast.plannedDays == 0) + #expect(forecast.projectedRemainingDays == 0) + #expect(forecast.estimatedTotalDays == 91) + } + + @Test func stayEndingTodayDoesNotReserveFutureDays() throws { + let forecast = try #require(LocationForecast.estimate( + region: .newYork, + report: Self.report(), + asOf: Self.date(7, 1), + calendar: Self.calendar, + plannedStay: PlannedStay( + region: .california, + through: CalendarDay(year: 2026, month: 7, day: 1), ), )) - #expect(withCaliforniaStay == baseline) + #expect(forecast.plannedDays == 0) + #expect(forecast.projectedRemainingDays == 91.5) + #expect(forecast.estimatedTotalDays == 183) } } diff --git a/Where/WhereCore/Tests/WherePreferencesTests.swift b/Where/WhereCore/Tests/WherePreferencesTests.swift index a1a4a136c..c053f7bd9 100644 --- a/Where/WhereCore/Tests/WherePreferencesTests.swift +++ b/Where/WhereCore/Tests/WherePreferencesTests.swift @@ -12,7 +12,7 @@ struct WherePreferencesTests { #expect(preferences.hasOnboarded == false) #expect(preferences.showsRecordedLocationDots) - #expect(preferences.showsLocationForecastsOnLocationsTab) + #expect(preferences.showsEstimatedTimeAndPlanning) #expect(preferences.remindersEnabled) #expect(preferences.reminderTime == .defaultEvening) #expect(preferences.summaryEnabled) @@ -38,6 +38,17 @@ struct WherePreferencesTests { #expect(preferences.lastSeenLocationDayCounts(in: 2026) == counts2026) } + @Test func estimatedTimeUsesTheLegacyLocationsVisibilityKey() { + let store = InMemoryKeyValueStore() + store.set(false, forKey: "where.showsLocationForecastsOnLocationsTab") + let preferences = WherePreferences(store: store) + + #expect(preferences.showsEstimatedTimeAndPlanning == false) + + preferences.showsEstimatedTimeAndPlanning = true + #expect(store.bool(forKey: "where.showsLocationForecastsOnLocationsTab")) + } + @Test func recordingWarningRegistrationRoundTrips() { let preferences = preferences() var registration = RecordingConfigurationWarningRegistration() @@ -53,7 +64,7 @@ struct WherePreferencesTests { let preferences = preferences() preferences.hasOnboarded = true preferences.showsRecordedLocationDots = false - preferences.showsLocationForecastsOnLocationsTab = false + preferences.showsEstimatedTimeAndPlanning = false preferences.remindersEnabled = false preferences.reminderTime = ReminderTime(hour: 9, minute: 15) preferences.summaryEnabled = false @@ -70,7 +81,7 @@ struct WherePreferencesTests { #expect(preferences.hasOnboarded == false) #expect(preferences.showsRecordedLocationDots) - #expect(preferences.showsLocationForecastsOnLocationsTab) + #expect(preferences.showsEstimatedTimeAndPlanning) #expect(preferences.remindersEnabled) #expect(preferences.reminderTime == .defaultEvening) #expect(preferences.summaryEnabled) diff --git a/Where/WhereUI/AGENTS.md b/Where/WhereUI/AGENTS.md index f80b76ecd..1148b3879 100644 --- a/Where/WhereUI/AGENTS.md +++ b/Where/WhereUI/AGENTS.md @@ -76,6 +76,8 @@ Layering, localization, preview, and testing conventions live in the feature `YearReportDetails`. - Keep planned-stay persistence and forecast math in WhereCore; `LocationForecastModel` only mirrors the active register and orchestrates intents for the Locations, calendar, and timeline surfaces. +- Hide every forecast and planned-stay visualization behind + `YearReportModel.showsEstimatedTimeAndPlanning`; persist Off only after clearing the synced plan. - Continuous/looping motion (repeat-forever pulses, `TimelineView(.animation)`, typewriter reveals) must consult the shared `@MotionIsStatic` helper ([`Sources/Shared/MotionIsStatic.swift`](Sources/Shared/MotionIsStatic.swift)) diff --git a/Where/WhereUI/README.md b/Where/WhereUI/README.md index a261cd4b9..1e7952a0b 100644 --- a/Where/WhereUI/README.md +++ b/Where/WhereUI/README.md @@ -30,7 +30,10 @@ the feature [`Where/AGENTS.md`](../AGENTS.md) and this module's widget family on miniature Home Screen and Lock Screen surfaces. A Share & Evidence walkthrough also reveals the system Share-sheet extension and links into the saved attachment archive. Insights & Accuracy introduces the - automatic issue detectors without running them merely to render the gallery. + automatic issue detectors without running them merely to render the gallery, + while Estimated Time & Planning explains the live annual projection with a + worked pace-and-plan calculation, planned stays, and why overlapping travel + days do not sum neatly to one year. These galleries use a shared marketing header, quiet patterned backdrop, and staged entrance that resolves immediately for Reduce Motion and snapshot capture. Once the selected report has 14 recorded days, the Siri, Spotlight, @@ -295,9 +298,10 @@ stroke; its containing Liquid Glass surface owns the subtle outer border so direct and production rendering do not diverge. After at least three months of the current year, Locations can reveal a collapsible annual estimate -from the recorded pace; Settings > Appearance can disable it entirely. A focused region calendar -places the estimate after the current month and renders “I’ll be here through…” future days with a -continuous hatched band, distinct from recorded presence. +from the recorded pace and plan a stay through one of its displayed regions. A focused region +calendar places the estimate after the current month and renders planned future days with a +continuous hatched band, distinct from recorded presence. Settings > Appearance disables every +estimate and planning visualization only after clearing the synced plan succeeds. DEBUG builds include Card Designer Studio under Settings → Appearance. It edits a versioned, persisted draft of the regular, compact, and shared card diff --git a/Where/WhereUI/SnapshotTests/EstimatedTimeFeaturesViewSnapshotTests.swift b/Where/WhereUI/SnapshotTests/EstimatedTimeFeaturesViewSnapshotTests.swift new file mode 100644 index 000000000..e778cacc7 --- /dev/null +++ b/Where/WhereUI/SnapshotTests/EstimatedTimeFeaturesViewSnapshotTests.swift @@ -0,0 +1,10 @@ +import SnapshotKitTesting +import Testing +@testable import WhereUI + +@MainActor +struct EstimatedTimeFeaturesViewSnapshotTests { + @Test func snapshots() async { + await assertSnapshots(of: EstimatedTimeFeaturesView.self) + } +} diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPad.png b/Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPad.png index eeb8fb1e0..327bd6081 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPad.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPad.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:23ac10dd726c4466d21e19c2767652a59bead5231e5e25ba0835685b2bd3a6c2 -size 262180 +oid sha256:f95e1150e12337d4136d73b79e2d8b4f439c13bae3b7da3d75eda4b09dcc67c3 +size 280118 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPad_accessibility.png b/Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPad_accessibility.png index 0f7a9a9b4..2dec35961 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPad_accessibility.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPad_accessibility.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e5343e77a020605226b10d1bc8250dffddba5fd33343c4ec9ac396c2c761d95a -size 584958 +oid sha256:7b4ba9305b9f81c8da786a9ce23577b4d9fc416da2c8827cba23e999243b50e9 +size 714938 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPad_ax5.png b/Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPad_ax5.png index be2dc963a..4cfaa4d4a 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPad_ax5.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPad_ax5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1f57e5a5fa8fd90e2a62ac2a33e2dd95bea2effeb469a84436c26520e3cf211d -size 411979 +oid sha256:b003bf0351dfa2d8f435afb5a8565f73501722815abf26a8d50f086d21d7cc68 +size 493340 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPad_contrast.png b/Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPad_contrast.png index 0fc62fc7f..47f0d1ee0 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPad_contrast.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPad_contrast.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d2cb9d9665c24423225b092f2d2193c3a9a116f15121fc605cbd7746ceb02b97 -size 264145 +oid sha256:437ab0e5385980621bcd1ef33cd2dced40a93cf9f5cf95e6fec933254bf822ce +size 281854 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPad_dark.png b/Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPad_dark.png index 08a395b2a..6fc85d94e 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPad_dark.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPad_dark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:817412d4d8cfa85ede65f64742c4b6fe8acfa25d34d6fa6eaec54955bf56785e -size 264189 +oid sha256:33d2b29c1196fa76938a75e643b8ffc9d3533a5b34853aa7db60ce59d14e893b +size 281680 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPhone.png b/Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPhone.png index b2ee0867b..35c0e2da6 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPhone.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPhone.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3cbc55698a38661561b944cf403be404d778225fc18174620fce26713eb6a37f -size 147640 +oid sha256:4cf3342c02be7451bae4c38783e086a3444cebfe095b399f28827d9f99fa11e6 +size 166348 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPhone_accessibility.png b/Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPhone_accessibility.png index 6aa2f6c8f..1fe997251 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPhone_accessibility.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPhone_accessibility.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f2371e75ced5ecb12efb0c3991f39da02377048030effc7f4d590f166414e8de -size 388800 +oid sha256:20004be8e8dc9de535db7e6f1f923b0ba4885e411a65889189739d09a9437fff +size 428018 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPhone_ax5.png b/Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPhone_ax5.png index f8028a5d2..85268c66d 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPhone_ax5.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPhone_ax5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d963dd326bb209c4f73a3d6d3b98632abd11811bf4f168960e5cb326de6ba5c8 -size 357285 +oid sha256:26278a3c5390c3d575ca63dd138acd65a6f17ae15ad1c3f1b202b2c884b2cff8 +size 488379 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPhone_contrast.png b/Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPhone_contrast.png index a48415e38..f3dc28095 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPhone_contrast.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPhone_contrast.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b477751714d13c6e000adba324c7696f90adf574ee14d2e45b15e86a84723f74 -size 149922 +oid sha256:61bad67299f21daae56d6a028aae7286d9f63b4ec85865dc640b8835d27ea165 +size 168602 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPhone_dark.png b/Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPhone_dark.png index cef787c64..e4a38802b 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPhone_dark.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/AppearanceSettingsViewSnapshotTests/appearance.Default_iPhone_dark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f4ffedc8f9f8c18c8315d7eebd57a676021f2df0544016beac0b07ec4d1bdfa1 -size 149787 +oid sha256:3e51eb4400f736d911f1859d3cb36cdc894a0ac1dbf9ec44db4d4e593b454e06 +size 168461 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.FocusedPlannedStay_iPhone.png b/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.FocusedPlannedStay_iPhone.png index 4755086d1..fd5cce11b 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.FocusedPlannedStay_iPhone.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.FocusedPlannedStay_iPhone.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:98d2e5d774b3b323f1c646c56c7446d950b59e721f0dad458c13050bd4a7489d -size 1070281 +oid sha256:cf96d7f6fae8f4d9b55b041d2bdb74cea7bec813acc44396d5b6609561696df6 +size 1070448 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.FocusedPlannedStay_iPhone_dark.png b/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.FocusedPlannedStay_iPhone_dark.png index 6b5d925f9..0515639b1 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.FocusedPlannedStay_iPhone_dark.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.FocusedPlannedStay_iPhone_dark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:360829e9463ebbeb2a38e350c1cf67bd08d3e9bb5807d192fabbd96eebef909e -size 975660 +oid sha256:d997af2043fb7db006bdfdca03f76705afa526a05d6fbc581182257fecf10798 +size 975849 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.Focused_iPhone.png b/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.Focused_iPhone.png index eb09988f5..6b377f84a 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.Focused_iPhone.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.Focused_iPhone.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d19e47f97f9af292cc55001ad879272cfea53da9a17aeed1afc86ace9afa6411 -size 880899 +oid sha256:53857329a74c4a880269f1f38c5c9e6af2c9cb51a07af2741c1752aede9ade66 +size 881058 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.Focused_iPhone_dark.png b/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.Focused_iPhone_dark.png index 841f6516b..7d08ea21f 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.Focused_iPhone_dark.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.Focused_iPhone_dark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5793809829b802062b73960e737df1c9b16d4c6eae83ca63e56ea66545b4437b -size 820542 +oid sha256:68d7fda8fc0780a7341d1fbb31dd73be900cfc36b4b87a2716b61ad589664074 +size 820434 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.InitialPosition_iPhone.png b/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.InitialPosition_iPhone.png new file mode 100644 index 000000000..8d03f7a0b --- /dev/null +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.InitialPosition_iPhone.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:0ad65ba5d9f26f6eb09e3ddfaf166ad49c5b7a2f7af9249b3187385a62f7dd80 +size 454676 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.InitialPosition_iPhone_dark.png b/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.InitialPosition_iPhone_dark.png new file mode 100644 index 000000000..b34808fc2 --- /dev/null +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.InitialPosition_iPhone_dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:64ebacc87feb0560ca6c72286776e9d40fe7ec6cd57e4b5a542cd255485b031f +size 323226 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.MultiRegionPlannedStay_iPhone.png b/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.MultiRegionPlannedStay_iPhone.png index 9f3b4d98a..039e60085 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.MultiRegionPlannedStay_iPhone.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.MultiRegionPlannedStay_iPhone.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5f6e02af5256e4593a1f4013f2fbe750137bd7dac063efe2c705fb52773eeeef -size 1194181 +oid sha256:801be5c3863bbd7e73ad7a3d874c0734ec9de79882abecf33e861cb2afbeaa5f +size 1193715 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.MultiRegionPlannedStay_iPhone_dark.png b/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.MultiRegionPlannedStay_iPhone_dark.png index f6a6c7f9f..257526285 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.MultiRegionPlannedStay_iPhone_dark.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.MultiRegionPlannedStay_iPhone_dark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:47514498dc692b11ef68f600e2c4adbbf7a2b84ae8dc46ec9d10498da1156d01 -size 1097921 +oid sha256:fd962598b3abd27bff9e57585604bcc5a1cabbf27afea85917da1b6b9fcaf78b +size 1097817 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.PlannedStayHidden_iPhone.png b/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.PlannedStayHidden_iPhone.png new file mode 100644 index 000000000..9ad106fd4 --- /dev/null +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.PlannedStayHidden_iPhone.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:13f330f99401d66edc7dd6354ee2b98379526f8b6a14ebdb64cb880557625b67 +size 790456 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.PlannedStayHidden_iPhone_dark.png b/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.PlannedStayHidden_iPhone_dark.png new file mode 100644 index 000000000..2f9e732ed --- /dev/null +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.PlannedStayHidden_iPhone_dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:a5930876b73443460b4363df3ff76a79e2bc438f05566f7da7ba859e49a28110 +size 768349 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.WithData_iPad.png b/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.WithData_iPad.png index c8fc20f8d..a8406593c 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.WithData_iPad.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.WithData_iPad.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e9d37bb7f52e53cc3c211b8b99118205121818e9258179ce70d88dcc82b94d06 -size 1411020 +oid sha256:92693d44b56a58fe080297a02d2a8f4bae2ecdfb727b991c942e0479b4bb4595 +size 1411221 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.WithData_iPad_ax5.png b/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.WithData_iPad_ax5.png index 346c3ac7c..b4f882a2b 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.WithData_iPad_ax5.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.WithData_iPad_ax5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:cc22338919a81220c4fc9f3457960e52d012c304d290771d66ea5af3368f7282 -size 2709699 +oid sha256:6675382866877b1d7043b169465e46403dc2462196bc5acbe9a44bdfcdb5fc55 +size 2719832 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.WithData_iPad_contrast.png b/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.WithData_iPad_contrast.png index 6c88ac657..e21943656 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.WithData_iPad_contrast.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.WithData_iPad_contrast.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ac3541d41088a47fd2f345a66004ce2186f80cf0c5314b05b67814a6816da1de -size 1508817 +oid sha256:cc19266845d5f80b4be76cc28fa3d4dbfe6ebbc2b3046e5272963e6db739a1e7 +size 1508816 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.WithData_iPad_dark.png b/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.WithData_iPad_dark.png index f8ea5bcad..48f87d928 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.WithData_iPad_dark.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.WithData_iPad_dark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5fe37c4b232ff2a770c71fa4dd5bbc731f5c4f22979becf37e9a13a590667f6f -size 1254338 +oid sha256:f8f095a4ef35602add20777ceea9f6b9e7d5be15f5ec5acc0bf37e6b1102eb46 +size 1253860 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.WithData_iPhone.png b/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.WithData_iPhone.png index fe4c6d4ac..35a492d66 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.WithData_iPhone.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.WithData_iPhone.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:50c8f82f7c90449d444395fb318b5329674415c2e3e0c10e779030e2dd0708df -size 1018609 +oid sha256:2879e1ca237420c5ec2aeaf64b02574cfae8c24c67242bb687f17f026f60f932 +size 1018829 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.WithData_iPhone_ax5.png b/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.WithData_iPhone_ax5.png index bf5c06bec..e21d9a676 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.WithData_iPhone_ax5.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.WithData_iPhone_ax5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b05963de230c9dceea9c3280c169982b56c3ec8470f498e22e55f80cae17b78f -size 1915970 +oid sha256:fc57e30b6c5cb813ce38afb0496f012c1904be72ef00dcec8733889678c91317 +size 1915311 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.WithData_iPhone_contrast.png b/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.WithData_iPhone_contrast.png index 39265afce..289b3837b 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.WithData_iPhone_contrast.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.WithData_iPhone_contrast.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5e9802dde610230b7498a44fbdedf42bf66f5218b031f13ca9c5f31afca82a82 -size 1064892 +oid sha256:074186f088347b9e212813975f1a8ccbeb71843ed2b1c2d8cdb06c1a5121b7d9 +size 1064882 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.WithData_iPhone_dark.png b/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.WithData_iPhone_dark.png index 0b4720026..2919bb057 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.WithData_iPhone_dark.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/CalendarContentViewSnapshotTests/calendarContent.WithData_iPhone_dark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7ec7a23681ee99d5eee06dd6c0fcc36f96ea73a67f7006a8f89c12013fae2a15 -size 926587 +oid sha256:41d4d7afed650f8c76bcc92dcb9eb609a2c545e9cfceb002b7aa2b68983a9bc3 +size 926381 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/EstimatedTimeFeaturesViewSnapshotTests/snapshots.Disabled_iPhone.png b/Where/WhereUI/SnapshotTests/__Snapshots__/EstimatedTimeFeaturesViewSnapshotTests/snapshots.Disabled_iPhone.png new file mode 100644 index 000000000..6cfc46c37 --- /dev/null +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/EstimatedTimeFeaturesViewSnapshotTests/snapshots.Disabled_iPhone.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:798166ea6b7a0e0e3693b26615063f86120a567219fac0a7c5317546195792d5 +size 743848 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/EstimatedTimeFeaturesViewSnapshotTests/snapshots.Disabled_iPhone_dark.png b/Where/WhereUI/SnapshotTests/__Snapshots__/EstimatedTimeFeaturesViewSnapshotTests/snapshots.Disabled_iPhone_dark.png new file mode 100644 index 000000000..4aaafc3b1 --- /dev/null +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/EstimatedTimeFeaturesViewSnapshotTests/snapshots.Disabled_iPhone_dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:cc528ac3b24cb233425e3120d06a21615a84c125d5d7f02e1090a494b8f5e502 +size 749400 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/EstimatedTimeFeaturesViewSnapshotTests/snapshots.Live_iPad.png b/Where/WhereUI/SnapshotTests/__Snapshots__/EstimatedTimeFeaturesViewSnapshotTests/snapshots.Live_iPad.png new file mode 100644 index 000000000..c3ff86c72 --- /dev/null +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/EstimatedTimeFeaturesViewSnapshotTests/snapshots.Live_iPad.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:566e8e13a277e43c181efb9ca6e99719f036af6ada95221ff369da80352648df +size 1395910 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/EstimatedTimeFeaturesViewSnapshotTests/snapshots.Live_iPad_accessibility.png b/Where/WhereUI/SnapshotTests/__Snapshots__/EstimatedTimeFeaturesViewSnapshotTests/snapshots.Live_iPad_accessibility.png new file mode 100644 index 000000000..f2d28a0f8 --- /dev/null +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/EstimatedTimeFeaturesViewSnapshotTests/snapshots.Live_iPad_accessibility.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3d63002414ef0b5cd99e2bd110d7623718e23b03acca2d60055791a86beeab60 +size 1853054 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/EstimatedTimeFeaturesViewSnapshotTests/snapshots.Live_iPad_ax5.png b/Where/WhereUI/SnapshotTests/__Snapshots__/EstimatedTimeFeaturesViewSnapshotTests/snapshots.Live_iPad_ax5.png new file mode 100644 index 000000000..3f0d327d7 --- /dev/null +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/EstimatedTimeFeaturesViewSnapshotTests/snapshots.Live_iPad_ax5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:eed86e34da9ffd79e3998b2ad724252f7cd8ac99341ad217489a6944cd8632ef +size 3781965 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/EstimatedTimeFeaturesViewSnapshotTests/snapshots.Live_iPad_contrast.png b/Where/WhereUI/SnapshotTests/__Snapshots__/EstimatedTimeFeaturesViewSnapshotTests/snapshots.Live_iPad_contrast.png new file mode 100644 index 000000000..a1b7bff90 --- /dev/null +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/EstimatedTimeFeaturesViewSnapshotTests/snapshots.Live_iPad_contrast.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:04e211390c7cd6c40085c57e29e194c3bb04da151158d0ee267b319b18f1c026 +size 1558416 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/EstimatedTimeFeaturesViewSnapshotTests/snapshots.Live_iPad_dark.png b/Where/WhereUI/SnapshotTests/__Snapshots__/EstimatedTimeFeaturesViewSnapshotTests/snapshots.Live_iPad_dark.png new file mode 100644 index 000000000..4489ced28 --- /dev/null +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/EstimatedTimeFeaturesViewSnapshotTests/snapshots.Live_iPad_dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:42c7d9aba1beecc3d781bef37cdf4bb0af8c27bef2d1cb1ecc0436fca5607192 +size 1337649 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/EstimatedTimeFeaturesViewSnapshotTests/snapshots.Live_iPhone.png b/Where/WhereUI/SnapshotTests/__Snapshots__/EstimatedTimeFeaturesViewSnapshotTests/snapshots.Live_iPhone.png new file mode 100644 index 000000000..864ed59ef --- /dev/null +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/EstimatedTimeFeaturesViewSnapshotTests/snapshots.Live_iPhone.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:944b1c5f8934f930545e6211babe82926e7b0ccdfffa5812f8117eb93f7b1677 +size 967643 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/EstimatedTimeFeaturesViewSnapshotTests/snapshots.Live_iPhone_accessibility.png b/Where/WhereUI/SnapshotTests/__Snapshots__/EstimatedTimeFeaturesViewSnapshotTests/snapshots.Live_iPhone_accessibility.png new file mode 100644 index 000000000..8f0be24dd --- /dev/null +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/EstimatedTimeFeaturesViewSnapshotTests/snapshots.Live_iPhone_accessibility.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:92499f85da5ea156e54350b35c61f5583c0c399400a8dcf2b4a2c7a950e36e04 +size 1480112 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/EstimatedTimeFeaturesViewSnapshotTests/snapshots.Live_iPhone_ax5.png b/Where/WhereUI/SnapshotTests/__Snapshots__/EstimatedTimeFeaturesViewSnapshotTests/snapshots.Live_iPhone_ax5.png new file mode 100644 index 000000000..fbb66538c --- /dev/null +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/EstimatedTimeFeaturesViewSnapshotTests/snapshots.Live_iPhone_ax5.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3f607da6830ac0fba179dc2f9ee60a8cf1de523c0133d5ae6329cae6d38078f5 +size 3491432 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/EstimatedTimeFeaturesViewSnapshotTests/snapshots.Live_iPhone_contrast.png b/Where/WhereUI/SnapshotTests/__Snapshots__/EstimatedTimeFeaturesViewSnapshotTests/snapshots.Live_iPhone_contrast.png new file mode 100644 index 000000000..49bcfd2b8 --- /dev/null +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/EstimatedTimeFeaturesViewSnapshotTests/snapshots.Live_iPhone_contrast.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1384b2776c4cf093e2c5060f353477d121d8a17f401abf02e635fe49f1b6e5ec +size 1054323 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/EstimatedTimeFeaturesViewSnapshotTests/snapshots.Live_iPhone_dark.png b/Where/WhereUI/SnapshotTests/__Snapshots__/EstimatedTimeFeaturesViewSnapshotTests/snapshots.Live_iPhone_dark.png new file mode 100644 index 000000000..d86dede21 --- /dev/null +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/EstimatedTimeFeaturesViewSnapshotTests/snapshots.Live_iPhone_dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:3df65c8c3038cbdc9c0ea341493e67cb02aed30d0888f656f1634c0199883195 +size 945837 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/EstimatedTimeFeaturesViewSnapshotTests/snapshots.Unavailable_iPhone.png b/Where/WhereUI/SnapshotTests/__Snapshots__/EstimatedTimeFeaturesViewSnapshotTests/snapshots.Unavailable_iPhone.png new file mode 100644 index 000000000..8b6ba8459 --- /dev/null +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/EstimatedTimeFeaturesViewSnapshotTests/snapshots.Unavailable_iPhone.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:b36f769a95e70ae1a5fd766af2bb090544ab8b1b826711612ffb82a15d586dd6 +size 743537 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/EstimatedTimeFeaturesViewSnapshotTests/snapshots.Unavailable_iPhone_dark.png b/Where/WhereUI/SnapshotTests/__Snapshots__/EstimatedTimeFeaturesViewSnapshotTests/snapshots.Unavailable_iPhone_dark.png new file mode 100644 index 000000000..132ffe8ee --- /dev/null +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/EstimatedTimeFeaturesViewSnapshotTests/snapshots.Unavailable_iPhone_dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:6d92bc601da16524b538dabea2fa62544594466cb69b2838776a473e45c4a0b9 +size 742730 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.DotsHidden_iPhone.png b/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.DotsHidden_iPhone.png index 2f3b51af0..a12c93c9d 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.DotsHidden_iPhone.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.DotsHidden_iPhone.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:532305f6ac03b0b3c2db6f57048e389f2a1d8e7bf16ea81923e51ce53a958ca3 -size 2224354 +oid sha256:f9729bb1ef7f4d12da41df615cede478a750985f0de1aba9572f2d9ce6696a21 +size 2348710 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.DotsHidden_iPhone_dark.png b/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.DotsHidden_iPhone_dark.png index b9b944275..d97be9fab 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.DotsHidden_iPhone_dark.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.DotsHidden_iPhone_dark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0a75c5ffd8d0ee120c0be544972adcad103fc98decb44ad3f4ca239c14485978 -size 2322967 +oid sha256:406d7de06e42b50ebcd9a222c672dc503cef816ff3c54d2deb1127a128e5ce72 +size 2456283 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.ForecastsHidden_iPhone.png b/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.ForecastsHidden_iPhone.png index 68cdb35d3..9f8bee274 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.ForecastsHidden_iPhone.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.ForecastsHidden_iPhone.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0900251620d9d375d28f86f9735704ee4cf1d07339fc855a5cfd01841c0f6525 -size 2142029 +oid sha256:e00724261d79f82073d650f1a2637b421ab288d97f300a85e9dd41935b5ee003 +size 2150658 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.ForecastsHidden_iPhone_dark.png b/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.ForecastsHidden_iPhone_dark.png index ab54c334b..08d474868 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.ForecastsHidden_iPhone_dark.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.ForecastsHidden_iPhone_dark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:fcfdb83f5e690da756086937af5c6e49a7b55deaa03712439530803891ac136d -size 2308445 +oid sha256:9aeea4889111ea10492600e1fbbd52c5660cb2a4eab2418463456d05f9f941b1 +size 2317590 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.Loaded_iPad.png b/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.Loaded_iPad.png index 92077a773..7ab612221 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.Loaded_iPad.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.Loaded_iPad.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:abb896c85dd06ea814afcc541a86213b3bdc9a10959ad4332f14f5e95a79bdae -size 3565897 +oid sha256:c84c373bf1f46f48336248d909bf55fdd0e523bf91e48df6776e41f78c838faf +size 3763002 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.Loaded_iPad_accessibility.png b/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.Loaded_iPad_accessibility.png index 836fb3ab2..99b694ce9 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.Loaded_iPad_accessibility.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.Loaded_iPad_accessibility.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e70c563890f576fe8021b0542e710cee82233ed0116b29645a03341328959c19 -size 2519638 +oid sha256:32503acf6c307848e0bb81e909aca185b89a75437840f002811f1fce10a6b7a2 +size 2651159 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.Loaded_iPad_ax5.png b/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.Loaded_iPad_ax5.png index 69383da7a..79c03ee5f 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.Loaded_iPad_ax5.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.Loaded_iPad_ax5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:90df92ec892544fd17e9ea6fa1c49d015800b8a49582ca3c8af1e291025f4cfb -size 3981615 +oid sha256:16a9045055098b09e7e356c7fdb6cb888efb076082db1f6c4f180300162e4fab +size 4683002 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.Loaded_iPad_contrast.png b/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.Loaded_iPad_contrast.png index d4cb98d53..b98976776 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.Loaded_iPad_contrast.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.Loaded_iPad_contrast.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0aef8f2a2c220bdde06a47aa70ff20af50b29366fbdf9b4da9131794916d7dc1 -size 3544558 +oid sha256:2004a56f0a0a5265214bc037da11c6530e9c6b3343a289ef3d9fe8e09ceea949 +size 3767042 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.Loaded_iPad_dark.png b/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.Loaded_iPad_dark.png index ef862ef72..086c3c979 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.Loaded_iPad_dark.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.Loaded_iPad_dark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e8d392d77bf09ff9c4e8a6218a5dc804ac1c747a9d183d99e087bb1b4842a704 -size 3841965 +oid sha256:0f26732da7b2c8ff77b2d3101d25311f2ca611c0cf82d4a22b6538ced8bbf821 +size 4043689 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.Loaded_iPhone.png b/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.Loaded_iPhone.png index b7d3928ab..af34e9936 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.Loaded_iPhone.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.Loaded_iPhone.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1f3adaece03ab0ba04ccd2c0281c277ed114a6175a780ce60372ba0d4ae4e64f -size 2232277 +oid sha256:eacb91e65e4f1cb6b910a426a4f4ffa59e8a714040f1db0889c6d109ba8a30bb +size 2357578 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.Loaded_iPhone_accessibility.png b/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.Loaded_iPhone_accessibility.png index 89040bfdb..3b712931e 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.Loaded_iPhone_accessibility.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.Loaded_iPhone_accessibility.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:13f0cc40c12c3f9f42e16ba6af1750a4489124d655b430b90cb980d13856c531 -size 1518040 +oid sha256:32eb1de099bde50850833ac7e8f14fbe043e1caa469ddb27dad2692e0003c8f3 +size 1624303 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.Loaded_iPhone_ax5.png b/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.Loaded_iPhone_ax5.png index 52c3c96b4..6db6b9be2 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.Loaded_iPhone_ax5.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.Loaded_iPhone_ax5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b0ce1da7ddf796d1fc2878e0a1947348a1daca566d8ef255fa5f67c84fdc6265 -size 2782536 +oid sha256:7698aa1917722384070b0d0616f814377059f95a54ced145f306149f737176e4 +size 3444208 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.Loaded_iPhone_contrast.png b/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.Loaded_iPhone_contrast.png index 54f1157e2..2568cb065 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.Loaded_iPhone_contrast.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.Loaded_iPhone_contrast.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:67695e63cabe1083249ef4e4e0a3a7d7187899da81cff936c713230370facd52 -size 2174136 +oid sha256:bf001507e79826fa2b223f299dd5253e9eea6a958cf288c7cab66ce8e23b3f58 +size 2287551 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.Loaded_iPhone_dark.png b/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.Loaded_iPhone_dark.png index 2cc02fe86..2c8c7e935 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.Loaded_iPhone_dark.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.Loaded_iPhone_dark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:feafa456720ca375c600a190d6474916c9e5792a806eb18ef2337ff4372e445a -size 2332239 +oid sha256:741ab8d9347109515475306ebb7fc7d66f8ff23a647c412f5e88b5fd30e74070 +size 2464465 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.MissingDays_iPhone.png b/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.MissingDays_iPhone.png index 6b6c9dc77..e4399666c 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.MissingDays_iPhone.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.MissingDays_iPhone.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:54ba5d103dd26ab338e47de146e6d2328a1c9c35b20758851f76659a3364ad64 -size 1199231 +oid sha256:bf8fcfbf31aade576fe6493508bb04dfee3786e9dfdf5eb4a36bbd4195c7ff06 +size 1197949 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.MissingDays_iPhone_dark.png b/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.MissingDays_iPhone_dark.png index 61f00dce5..0d657af35 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.MissingDays_iPhone_dark.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.MissingDays_iPhone_dark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ac355069ef83959b69e8a836d15cc17a0b456c4c8acc7a60a1f9ed73c0466430 -size 1246553 +oid sha256:c277809546ce9ed8ee2ddaa31cf612b57419728e9a8d45488390968e426a3e4b +size 1246977 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.PlannedStay_iPhone.png b/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.PlannedStay_iPhone.png index 64b65a8dd..068256764 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.PlannedStay_iPhone.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.PlannedStay_iPhone.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:be00ad30219fcbacf4921f50f618919dd5f7afff4d43e828b3ae5a29d2901a03 -size 2183444 +oid sha256:e9c55ba759c8063a797c507694b0562a2531e8e5e81a2e33f4eb51f3f4399b22 +size 2308698 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.PlannedStay_iPhone_dark.png b/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.PlannedStay_iPhone_dark.png index 961a9b104..dbb4c1eaf 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.PlannedStay_iPhone_dark.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/LocationsViewSnapshotTests/locations.PlannedStay_iPhone_dark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:261dac5247ea2392bbd91be11b6a88643d69053a6d58a2f9c96dea5a80884b7f -size 2262525 +oid sha256:671a6458dc5e5d5cbab106c6471c9a34bb3ca54ecf472f51c566789ed098b7c5 +size 2408213 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStayHidden_iPhone.png b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStayHidden_iPhone.png new file mode 100644 index 000000000..bc6ad8079 --- /dev/null +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStayHidden_iPhone.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:ece523d213dbdad54c8c6018c8ce0de9a3ecd98b7fa0003bcd8a02726b4c094c +size 164519 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStayHidden_iPhone_dark.png b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStayHidden_iPhone_dark.png new file mode 100644 index 000000000..d278f5b99 --- /dev/null +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStayHidden_iPhone_dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:d0ca90440d332c9967fb60b26409038d54609455d47c9748326edf2b72a75214 +size 169501 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.Default_iPad.png b/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.Default_iPad.png index a9f861ace..48f93e80c 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.Default_iPad.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.Default_iPad.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:505266373ef3da408af30e946928fef8d73f616da344b3ffb969a665a47db6cc -size 436679 +oid sha256:a24ee9715b1663ebe10f16f960f8afc0c59a9e911a517626c13f583f8b2a893d +size 459527 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.Default_iPad_accessibility.png b/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.Default_iPad_accessibility.png index 3f43c4305..482cdf9ac 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.Default_iPad_accessibility.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.Default_iPad_accessibility.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:11d9f7d0a357b64473afbdbd8cf4eed0a32c6b31d44293bd2ffe7964c5906367 -size 738035 +oid sha256:8589b71f0152232396f7f1c1f113aa6df43ea053b97231e0dc1a9d7deb74597e +size 777597 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.Default_iPad_ax5.png b/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.Default_iPad_ax5.png index 8868f4f24..722ade64c 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.Default_iPad_ax5.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.Default_iPad_ax5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:76aedc7bee72bba15fbe79456e53fa10c97dfcc3e05d5213c14f0d01ab750b51 -size 795940 +oid sha256:74a40c38bc9a04e483e32562581d26707c8496bb8d9bc9211b1210ca8f09202b +size 861449 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.Default_iPad_contrast.png b/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.Default_iPad_contrast.png index 7e599aa3d..f12bedc37 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.Default_iPad_contrast.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.Default_iPad_contrast.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f83896e94a6e3bfa0ecf13887d20455eb358f749d0460b9b0ba1de5b091ae037 -size 442963 +oid sha256:6051fcc8a1ad4ad97ef1798b038ff9410da651527861a80d83003e0ad9c80763 +size 465513 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.Default_iPad_dark.png b/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.Default_iPad_dark.png index 931a319b5..d2c831e11 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.Default_iPad_dark.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.Default_iPad_dark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:b12337a899d6d1ad4e387d9c707641dca52eb3930e9cb7dbc03822abec91b97e -size 450007 +oid sha256:13951516a6785a4cbd3a047caefee3c9ac58f12c882642ea4ac6a27239f40574 +size 474779 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.Default_iPhone.png b/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.Default_iPhone.png index 3b51c916b..ab3cb1b1c 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.Default_iPhone.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.Default_iPhone.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c163a8b8dc5b2b2d408b83e2c010919a248262674e7e52da89590638ae88083e -size 331123 +oid sha256:584366a080384f1029b79eb3da6010c6da50b4cdaa5d842cbb6e656af35f2bbb +size 349651 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.Default_iPhone_accessibility.png b/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.Default_iPhone_accessibility.png index 64b0d9ec4..2e5ab5337 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.Default_iPhone_accessibility.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.Default_iPhone_accessibility.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e65cf217685a68776de9916f67ea85221a2798100b97d01d13b455ab0ceb2889 -size 624766 +oid sha256:057db6ce1098d6396a3d324550d2668026552a3c60e52ca8140fe7f6a4f10f29 +size 656656 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.Default_iPhone_ax5.png b/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.Default_iPhone_ax5.png index 7e4ccd153..68006d5fd 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.Default_iPhone_ax5.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.Default_iPhone_ax5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:06ab31ec7b55a23ab3d9e182878cea5a421ca5ade91396386585083441a0f8c5 -size 774570 +oid sha256:4e8b758598ccef8ac8b5b7ecc0934be49beca752a645897969130013542e4a44 +size 794957 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.Default_iPhone_contrast.png b/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.Default_iPhone_contrast.png index 9728f1304..2e81264d1 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.Default_iPhone_contrast.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.Default_iPhone_contrast.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:09d597f7304ffe44dda5399adfbcc8c5b67bb20abf88bd629af83f98e584ff7e -size 338931 +oid sha256:4d87bb923d8bf835d92a737d21e41dae1a6262a3af65259349c86c7e75606c23 +size 356128 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.Default_iPhone_dark.png b/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.Default_iPhone_dark.png index 52352d5a0..dac7ebc74 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.Default_iPhone_dark.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.Default_iPhone_dark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e211e2bfd1958909270efb356210e4d9b649e56faa00326e9d1fb514b6fe6d37 -size 343004 +oid sha256:dc939783c3d6402e332efbf8336bcfa2e578b988a68d82ac59173d1bc8cf9a40 +size 363225 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.Default_iPhone_rtl.png b/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.Default_iPhone_rtl.png index abb113168..96538c9a8 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.Default_iPhone_rtl.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.Default_iPhone_rtl.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7c820cda798c1c31ae1188a6c4f88a3d14ae436407e3d5b6e9b5b1b81c51262a -size 332808 +oid sha256:145bd70abcc0af549bc171c0aa8f3aa9a59921e88599f867bcec44f76a2b8b0d +size 351273 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.DemoMode_iPhone.png b/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.DemoMode_iPhone.png index 0022540d1..3f639dd30 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.DemoMode_iPhone.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.DemoMode_iPhone.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f254c43003a0cc144da32e457b181c3228b3bc0cfa7c93191ae9a8a7ecc485a1 -size 311199 +oid sha256:b816d7be914340c94f04e14e04d09b23c75d13ebdf7206c842f1c407efa9cb53 +size 327406 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.DemoMode_iPhone_dark.png b/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.DemoMode_iPhone_dark.png index 74daa6186..a6d4f3ec4 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.DemoMode_iPhone_dark.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.DemoMode_iPhone_dark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5de02a9670db7c65dfe18f44680241a226214dbb89de0673212d0b6592167d19 -size 314385 +oid sha256:364bff5712a96fe4b6e5b9f8998a9ef2b147d274c4fdbd874ec8069768091c9d +size 330731 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.RecordingConfigurationWarning_iPhone.png b/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.RecordingConfigurationWarning_iPhone.png index ad4d509e1..380923822 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.RecordingConfigurationWarning_iPhone.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.RecordingConfigurationWarning_iPhone.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1d830bb679cbbd94f8f1261f7a637f25d2fb013d41e3b69cb8aae2df7e15061a -size 412163 +oid sha256:e9131145777c3a755e6371d8e867598bb5d312a2aaad66379f99db90b1b703fe +size 430889 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.RecordingConfigurationWarning_iPhone_dark.png b/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.RecordingConfigurationWarning_iPhone_dark.png index 5f8110a4b..85ae972e1 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.RecordingConfigurationWarning_iPhone_dark.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/SettingsViewSnapshotTests/settings.RecordingConfigurationWarning_iPhone_dark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:eabde64561a1d50fea7a99066a6c90bee287fe1eedc92f81196ac30196002965 -size 431764 +oid sha256:c743816d82cc22eec318346419fe2735b6c293d99565d6ebadedf6f491273d98 +size 450926 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/WhereFlyoverViewSnapshotTests/seededEntryState.WhereFlyover_iPad.png b/Where/WhereUI/SnapshotTests/__Snapshots__/WhereFlyoverViewSnapshotTests/seededEntryState.WhereFlyover_iPad.png index f04db519b..433ca8a57 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/WhereFlyoverViewSnapshotTests/seededEntryState.WhereFlyover_iPad.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/WhereFlyoverViewSnapshotTests/seededEntryState.WhereFlyover_iPad.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f1483d0f964015020e672d47e19f5c86ab39315f714f33524b701510a4107046 -size 9416258 +oid sha256:8e8a714f686d9bbc299a7742dff50035e84c3c56ab2008df170bd0ee63e3e928 +size 9645368 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/YearViewSnapshotTests/year.Loaded_iPad.png b/Where/WhereUI/SnapshotTests/__Snapshots__/YearViewSnapshotTests/year.Loaded_iPad.png index ec8296f17..08c11ead2 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/YearViewSnapshotTests/year.Loaded_iPad.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/YearViewSnapshotTests/year.Loaded_iPad.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c42459e99056739516e9924df429f7404da3c45205da23793d9a70fb601a185c -size 1517174 +oid sha256:2ed336cde746eec32773e5baf664ccb0b003aa241c4ae6f540886dea5be93d96 +size 1517458 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/YearViewSnapshotTests/year.Loaded_iPad_ax5.png b/Where/WhereUI/SnapshotTests/__Snapshots__/YearViewSnapshotTests/year.Loaded_iPad_ax5.png index 55bd45c4b..ca52004c6 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/YearViewSnapshotTests/year.Loaded_iPad_ax5.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/YearViewSnapshotTests/year.Loaded_iPad_ax5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c189f25e76ba2449388dc182c7d7fe495e43a70900262cff2c2a4974a3cc389a -size 2834697 +oid sha256:8f9f2478ace8bdcf9920ebc68dd37dfd5be1250ef59c6100575f6701921b1027 +size 2841515 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/YearViewSnapshotTests/year.Loaded_iPad_contrast.png b/Where/WhereUI/SnapshotTests/__Snapshots__/YearViewSnapshotTests/year.Loaded_iPad_contrast.png index 95e91bdc1..dc0944327 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/YearViewSnapshotTests/year.Loaded_iPad_contrast.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/YearViewSnapshotTests/year.Loaded_iPad_contrast.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:452b5c27715868f360cc2c93e80c9f6d9c27eaa94670d7988efc3cb1980e1b7c -size 1617327 +oid sha256:c68ce3ce2209bfa761e51201922bbb6d1932280870a95d23825ae45eac7b0b88 +size 1617655 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/YearViewSnapshotTests/year.Loaded_iPad_dark.png b/Where/WhereUI/SnapshotTests/__Snapshots__/YearViewSnapshotTests/year.Loaded_iPad_dark.png index 0d0f7de6b..9873fb485 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/YearViewSnapshotTests/year.Loaded_iPad_dark.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/YearViewSnapshotTests/year.Loaded_iPad_dark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ae4bc8c80881dc31f4c929a8d52a68a626f9da7cf529089123f8a8b9cf2af556 -size 1777351 +oid sha256:df2d14d0578d316b57c929e8bf7c2e05843c00a86ca0e64cbb6ca8c7687d175c +size 1754466 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/YearViewSnapshotTests/year.Loaded_iPhone.png b/Where/WhereUI/SnapshotTests/__Snapshots__/YearViewSnapshotTests/year.Loaded_iPhone.png index de46ff987..9010e4ce4 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/YearViewSnapshotTests/year.Loaded_iPhone.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/YearViewSnapshotTests/year.Loaded_iPhone.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0d73537d37f693fdf393dfc5efb0e6c0713d742242be23cd477dffb8ba978958 -size 1122106 +oid sha256:d9f6da2c44dd58244f2a7b2a78306d8b6b442c1d57ff3c5839d9bfa364b61d5b +size 1122401 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/YearViewSnapshotTests/year.Loaded_iPhone_ax5.png b/Where/WhereUI/SnapshotTests/__Snapshots__/YearViewSnapshotTests/year.Loaded_iPhone_ax5.png index 23b749128..491c42bdc 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/YearViewSnapshotTests/year.Loaded_iPhone_ax5.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/YearViewSnapshotTests/year.Loaded_iPhone_ax5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:a2b73a267a28afdd13f23e3cd2963d8b8dbac97be9d0c79681dfec803336f3e2 -size 1506478 +oid sha256:39255af926478685d0887d549f074dd7b98b43c78bac4e2635a141e3383ecb90 +size 1512861 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/YearViewSnapshotTests/year.Loaded_iPhone_contrast.png b/Where/WhereUI/SnapshotTests/__Snapshots__/YearViewSnapshotTests/year.Loaded_iPhone_contrast.png index 33446a774..46464bdd1 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/YearViewSnapshotTests/year.Loaded_iPhone_contrast.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/YearViewSnapshotTests/year.Loaded_iPhone_contrast.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9fe183d7f1520217d6827ee547f947cdcaba5b212b730f755cd84582ff9a8fec -size 1153686 +oid sha256:845ede73807c5f52c7121bbad4a8f2be981614499288e6da644b77d4e521c710 +size 1154623 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/YearViewSnapshotTests/year.Loaded_iPhone_dark.png b/Where/WhereUI/SnapshotTests/__Snapshots__/YearViewSnapshotTests/year.Loaded_iPhone_dark.png index 62a055597..0fec0ab4d 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/YearViewSnapshotTests/year.Loaded_iPhone_dark.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/YearViewSnapshotTests/year.Loaded_iPhone_dark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6750d0c0e4e9c331f3fdd6886a32e8cd719af15e7a935ea3b93eb871ebdd5e70 -size 1263594 +oid sha256:43a1dbd8028bc07e9b78d62a56fb6dbbc8e26681f580fdafe14927fb09e37309 +size 1239374 diff --git a/Where/WhereUI/Sources/Developer/Flyover/WhereFlyoverCatalog.swift b/Where/WhereUI/Sources/Developer/Flyover/WhereFlyoverCatalog.swift index 27efe1583..bd492ba01 100644 --- a/Where/WhereUI/Sources/Developer/Flyover/WhereFlyoverCatalog.swift +++ b/Where/WhereUI/Sources/Developer/Flyover/WhereFlyoverCatalog.swift @@ -97,6 +97,7 @@ SiriFeaturesView.flyoverData, WidgetFeaturesView.flyoverData, ShareEvidenceFeaturesView.flyoverData, + EstimatedTimeFeaturesView.flyoverData, InsightsAccuracyFeaturesView.flyoverData, PersonalizationFeaturesView.flyoverData, DataSettingsView.flyoverData, diff --git a/Where/WhereUI/Sources/Forecasting/LocationForecastDisclosureStyle.swift b/Where/WhereUI/Sources/Forecasting/LocationForecastDisclosureStyle.swift deleted file mode 100644 index 2140ac853..000000000 --- a/Where/WhereUI/Sources/Forecasting/LocationForecastDisclosureStyle.swift +++ /dev/null @@ -1,30 +0,0 @@ -import SFSafeSymbols -import SwiftUI - -/// A quiet disclosure treatment for the floating Locations forecast card. -struct LocationForecastDisclosureStyle: DisclosureGroupStyle { - let foregroundColor: Color - - func makeBody(configuration: Configuration) -> some View { - VStack(alignment: .leading, spacing: 0) { - Button { - configuration.isExpanded.toggle() - } label: { - HStack(alignment: .firstTextBaseline) { - configuration.label - Image(systemSymbol: .chevronRight) - .rotationEffect(.degrees(configuration.isExpanded ? 90 : 0)) - .accessibilityHidden(true) - } - .contentShape(Rectangle()) - } - .buttonStyle(.plain) - .foregroundStyle(foregroundColor) - - if configuration.isExpanded { - configuration.content - .foregroundStyle(.primary) - } - } - } -} diff --git a/Where/WhereUI/Sources/Forecasting/LocationForecastHeader.swift b/Where/WhereUI/Sources/Forecasting/LocationForecastHeader.swift new file mode 100644 index 000000000..bd40b7e55 --- /dev/null +++ b/Where/WhereUI/Sources/Forecasting/LocationForecastHeader.swift @@ -0,0 +1,80 @@ +import SFSafeSymbols +import SwiftUI + +/// Forecast heading that optionally expands the compact Locations card. +struct LocationForecastHeader: View { + let elapsedDays: Int? + let isExpanded: Bool + var expansionAction: (() -> Void)? + + @Environment(\.stylesheet) private var stylesheet + + var body: some View { + ViewThatFits(in: .horizontal) { + HStack(alignment: .center, spacing: stylesheet.spacing.xSmall) { + title + Spacer(minLength: stylesheet.spacing.large) + elapsed + } + + VStack(alignment: .leading, spacing: stylesheet.locationForecast.estimateSpacing) { + title + elapsed + .frame(maxWidth: .infinity, alignment: .trailing) + } + } + } + + @ViewBuilder + private var title: some View { + if let expansionAction { + Button(action: expansionAction) { + Label { + HStack(spacing: stylesheet.spacing.xSmall) { + Text(String(localized: .locationForecastTitle)) + Image(systemSymbol: .chevronRight) + .rotationEffect(.degrees(isExpanded ? 90 : 0)) + .accessibilityHidden(true) + } + } icon: { + Image(systemSymbol: .chartLineUptrendXyaxis) + } + .contentShape(Rectangle()) + } + .buttonStyle(.plain) + .foregroundStyle(stylesheet.locationForecast.collapsedLabelColor) + .frame(minHeight: 44) + .accessibilityValue(String(localized: isExpanded + ? .locationForecastExpanded + : .locationForecastCollapsed)) + } else { + Label( + String(localized: .locationForecastTitle), + systemSymbol: .chartLineUptrendXyaxis, + ) + } + } + + @ViewBuilder + private var elapsed: some View { + if let elapsedDays { + Text(WhereFormat.locationForecastElapsed(days: elapsedDays)) + .font(.footnote) + .foregroundStyle(.secondary) + .monospacedDigit() + .fixedSize(horizontal: true, vertical: false) + } + } +} + +#if DEBUG + #Preview { + LocationForecastHeader( + elapsedDays: 224, + isExpanded: false, + expansionAction: {}, + ) + .padding() + .whereBroadwayRoot() + } +#endif diff --git a/Where/WhereUI/Sources/Forecasting/LocationForecastPanel.swift b/Where/WhereUI/Sources/Forecasting/LocationForecastPanel.swift index 783752f65..f5e54af2f 100644 --- a/Where/WhereUI/Sources/Forecasting/LocationForecastPanel.swift +++ b/Where/WhereUI/Sources/Forecasting/LocationForecastPanel.swift @@ -1,5 +1,4 @@ import RegionKit -import SFSafeSymbols import SwiftUI import WhereCore @@ -24,21 +23,21 @@ struct LocationForecastPanel: View { var body: some View { VStack(alignment: .leading, spacing: style.rowSpacing) { if isCollapsible { - DisclosureGroup(isExpanded: $isExpanded) { - VStack(alignment: .leading, spacing: style.rowSpacing) { - forecastContent - } - .padding(.top, style.rowSpacing) - } - label: { - forecastHeader - } - .disclosureGroupStyle(LocationForecastDisclosureStyle( - foregroundColor: style.collapsedLabelColor, - )) + LocationForecastHeader( + elapsedDays: forecasts.first?.elapsedDays, + isExpanded: isExpanded, + expansionAction: toggleExpansion, + ) } else { - forecastHeader + LocationForecastHeader( + elapsedDays: forecasts.first?.elapsedDays, + isExpanded: true, + ) + } + + if !isCollapsible || isExpanded { forecastContent + .transition(.move(edge: .top).combined(with: .opacity)) } } .padding(style.padding) @@ -60,49 +59,6 @@ struct LocationForecastPanel: View { Color.clear.glassEffect(.regular, in: shape) } } - .animation(style.expansionAnimation, value: isExpanded) - } - - @ViewBuilder - private var forecastHeader: some View { - if let elapsedDays = forecasts.first?.elapsedDays { - ViewThatFits(in: .horizontal) { - HStack(alignment: .firstTextBaseline) { - Image(systemSymbol: .chartLineUptrendXyaxis) - .accessibilityHidden(true) - Text(String(localized: .locationForecastTitle)) - .fixedSize(horizontal: true, vertical: false) - Spacer(minLength: stylesheet.spacing.large) - Text(WhereFormat.locationForecastElapsed(days: elapsedDays)) - .font(.footnote) - .foregroundStyle(.secondary) - .monospacedDigit() - .fixedSize(horizontal: true, vertical: false) - } - - VStack(alignment: .leading, spacing: style.estimateSpacing) { - HStack(alignment: .firstTextBaseline) { - Image(systemSymbol: .chartLineUptrendXyaxis) - .accessibilityHidden(true) - Text(String(localized: .locationForecastTitle)) - } - Text(WhereFormat.locationForecastElapsed(days: elapsedDays)) - .font(.footnote) - .foregroundStyle(.secondary) - .monospacedDigit() - .multilineTextAlignment(.trailing) - .frame(maxWidth: .infinity, alignment: .trailing) - } - } - .font(.headline) - } else { - HStack(alignment: .firstTextBaseline) { - Image(systemSymbol: .chartLineUptrendXyaxis) - .accessibilityHidden(true) - Text(String(localized: .locationForecastTitle)) - } - .font(.headline) - } } @ViewBuilder @@ -123,6 +79,12 @@ struct LocationForecastPanel: View { ) } } + + private func toggleExpansion() { + withAnimation(style.expansionAnimation) { + isExpanded.toggle() + } + } } private struct LocationForecastRow: View { diff --git a/Where/WhereUI/Sources/Model/YearReportModel.swift b/Where/WhereUI/Sources/Model/YearReportModel.swift index af2250388..08e6a20d5 100644 --- a/Where/WhereUI/Sources/Model/YearReportModel.swift +++ b/Where/WhereUI/Sources/Model/YearReportModel.swift @@ -150,17 +150,26 @@ public final class YearReportModel { /// model publishes this value to both the Appearance toggle and Locations. private var showsRecordedLocationDotsStorage: Bool - /// Observed mirror of the Locations tab's forecast-visibility preference. - /// `WherePreferences` is intentionally not observable, so Settings writes - /// through this property to update the mounted Locations tab immediately. - public var showsLocationForecastsOnLocationsTab: Bool { + /// Observed mirror of the estimated-time and planning visibility preference. + /// `WherePreferences` is intentionally not observable, so the async intent + /// below updates every mounted forecast/planning surface immediately. + public private(set) var showsEstimatedTimeAndPlanning: Bool { didSet { - guard oldValue != showsLocationForecastsOnLocationsTab else { return } - preferences.showsLocationForecastsOnLocationsTab = - showsLocationForecastsOnLocationsTab + guard oldValue != showsEstimatedTimeAndPlanning else { return } + preferences.showsEstimatedTimeAndPlanning = showsEstimatedTimeAndPlanning } } + /// Enable immediately, or clear the synced plan before hiding every + /// estimated-time surface. A failed clear leaves the preference and UI on. + func setEstimatedTimeAndPlanningEnabled(_ isEnabled: Bool) async throws { + guard isEnabled != showsEstimatedTimeAndPlanning else { return } + if !isEnabled { + try await forecasts.clear() + } + showsEstimatedTimeAndPlanning = isEnabled + } + /// Whether Locations cards render their recorded GPS constellations. /// Writes persist synchronously and update the visible cards immediately. var showsRecordedLocationDots: Bool { @@ -263,8 +272,7 @@ public final class YearReportModel { driftThresholdStorage = DriftThreshold(rawValue: preferences.driftThresholdMeters) ?? .default showsRecordedLocationDotsStorage = preferences.showsRecordedLocationDots - showsLocationForecastsOnLocationsTab = - preferences.showsLocationForecastsOnLocationsTab + showsEstimatedTimeAndPlanning = preferences.showsEstimatedTimeAndPlanning var calendar = Calendar(identifier: .gregorian) calendar.timeZone = .current self.calendar = calendar diff --git a/Where/WhereUI/Sources/Preview/PreviewSupport.swift b/Where/WhereUI/Sources/Preview/PreviewSupport.swift index 5cc8c6230..ce4bfc293 100644 --- a/Where/WhereUI/Sources/Preview/PreviewSupport.swift +++ b/Where/WhereUI/Sources/Preview/PreviewSupport.swift @@ -320,7 +320,9 @@ /// A report stopped at the pinned "today" with a deterministic future /// New York stay, for forecast and planned-calendar previews. @MainActor - public static func plannedStayYearReportModel() -> YearReportModel { + public static func plannedStayYearReportModel( + showsEstimatedTimeAndPlanning: Bool = true, + ) -> YearReportModel { let completeReport = sampleReport() var calendar = Calendar(identifier: .gregorian) calendar.timeZone = TimeZone(identifier: "America/Los_Angeles")! @@ -332,6 +334,8 @@ recordedTotals[region, default: 0] += 1 } } + let preferences = previewPreferences() + preferences.showsEstimatedTimeAndPlanning = showsEstimatedTimeAndPlanning let model = YearReportModel( services: previewServices(), details: YearReportDetails( @@ -343,7 +347,7 @@ primaryRegionLocations: sampleRegionLocations(), ), selectedYear: year, - preferences: previewPreferences(), + preferences: preferences, now: { referenceNow }, ) model.forecasts.setActivePlannedStay(PlannedStay( @@ -371,6 +375,20 @@ return model } + /// The loaded report fixture with every estimated-time presentation off. + @MainActor + public static func loadedYearReportModelWithEstimatedTimeHidden() -> YearReportModel { + let preferences = previewPreferences() + preferences.showsEstimatedTimeAndPlanning = false + return YearReportModel( + services: previewServices(), + details: sampleYearReportDetails(), + selectedYear: year, + preferences: preferences, + now: { referenceNow }, + ) + } + /// Deterministic point clouds for the Locations card constellations. /// Dates fall inside the corresponding region's block in `sampleReport`. private static func sampleRegionLocations() -> [Region: [RegionDayLocations]] { diff --git a/Where/WhereUI/Sources/Primary/CalendarContentView.swift b/Where/WhereUI/Sources/Primary/CalendarContentView.swift index 491183bfe..0771c2441 100644 --- a/Where/WhereUI/Sources/Primary/CalendarContentView.swift +++ b/Where/WhereUI/Sources/Primary/CalendarContentView.swift @@ -21,6 +21,8 @@ struct CalendarContentView: View { @Environment(\.stylesheet) private var stylesheet @State private var monthsLoad: Result<[CalendarMonth], Error>? @State private var plannedStayEditorTarget: PlannedStayEditorTarget? + @State private var initiallyPositionedYear: Int? + @State private var scrollPosition = ScrollPosition(idType: String.self) private static let logger = WhereLog.session(CalendarViewLog.self) @@ -128,19 +130,24 @@ struct CalendarContentView: View { } private func calendarContent(months: [CalendarMonth]) -> some View { - ScrollView { + let visibleMonths = shownMonths(months) + let initialMonthID = visibleMonths.first(where: \.isCurrentMonth)?.id + ?? visibleMonths.last?.id + + return ScrollView { LazyVStack(spacing: stylesheet.calendar.monthSpacing) { - ForEach(shownMonths(months)) { month in + ForEach(visibleMonths) { month in VStack(spacing: stylesheet.calendar.monthSpacing) { MonthGridView( month: month, focusedRegion: focusedRegion, dateCalendar: report.calendar, - plannedRegion: report.forecasts.plannedRegion(on:), + plannedRegion: displayedPlannedRegion(on:), ) + // In chronological flow, the estimate belongs immediately // after the month whose recorded pace it is projecting from. - if month.isCurrentMonth, !calendarForecasts.isEmpty { + if showsForecast(after: month) { LocationForecastPanel( forecasts: calendarForecasts, plannedStay: report.forecasts.activePlannedStay, @@ -154,11 +161,25 @@ struct CalendarContentView: View { ) } } + .id(month.id) } } + .scrollTargetLayout() .padding() } - .defaultScrollAnchor(.bottom, for: .initialOffset) + .scrollPosition($scrollPosition) + .task(id: report.selectedYear) { + guard initiallyPositionedYear != report.selectedYear else { return } + guard let initialMonthID else { return } + scrollPosition.scrollTo(id: initialMonthID, anchor: .bottom) + initiallyPositionedYear = report.selectedYear + } + } + + private func showsForecast(after month: CalendarMonth) -> Bool { + report.showsEstimatedTimeAndPlanning + && month.isCurrentMonth + && !calendarForecasts.isEmpty } private var calendarForecasts: [LocationForecast] { @@ -183,12 +204,18 @@ struct CalendarContentView: View { /// A plan belongs on the selected year's calendar and, when this is a /// region-focused calendar, only on that region's destination. private var displayedPlannedStay: PlannedStay? { + guard report.showsEstimatedTimeAndPlanning else { return nil } guard let year = report.report?.year else { return nil } guard let stay = report.forecasts.plannedStay(intersecting: year) else { return nil } guard focusedRegion == nil || focusedRegion == stay.region else { return nil } return stay } + private func displayedPlannedRegion(on day: CalendarDay) -> Region? { + guard report.showsEstimatedTimeAndPlanning else { return nil } + return report.forecasts.plannedRegion(on: day) + } + /// The months to show in chronological order. Future months are omitted /// unless a planned stay reaches into them; a past year shows the full year. private func shownMonths(_ months: [CalendarMonth]) -> [CalendarMonth] { @@ -595,6 +622,11 @@ private struct DayCell: View { CalendarContentView(report: PreviewSupport.loadedYearReportModel()) } } + whereSnapshot(name: "InitialPosition", configurations: .phoneLightDark) { + NavigationStack { + CalendarContentView(report: PreviewSupport.loadedYearReportModel()) + } + } whereSnapshot(name: "Empty", configurations: .fullContentPhoneLightDark) { NavigationStack { CalendarContentView(report: PreviewSupport.emptyYearReportModel()) @@ -630,6 +662,16 @@ private struct DayCell: View { CalendarContentView(report: PreviewSupport.plannedStayYearReportModel()) } } + whereSnapshot( + name: "PlannedStayHidden", + configurations: .fullContentPhoneLightDark, + ) { + NavigationStack { + CalendarContentView(report: PreviewSupport.plannedStayYearReportModel( + showsEstimatedTimeAndPlanning: false, + )) + } + } } } diff --git a/Where/WhereUI/Sources/Primary/LocationsView.swift b/Where/WhereUI/Sources/Primary/LocationsView.swift index f9968fc3e..e079ad7c4 100644 --- a/Where/WhereUI/Sources/Primary/LocationsView.swift +++ b/Where/WhereUI/Sources/Primary/LocationsView.swift @@ -13,6 +13,7 @@ struct LocationsView: View { let report: YearReportModel @State private var showingResolution = false + @State private var plannedStayEditorTarget: PlannedStayEditorTarget? @State private var isCardSurfaceVisible = false @State private var dayCountPresentation: LocationDayCountPresentationModel @@ -72,6 +73,9 @@ struct LocationsView: View { .sheet(isPresented: $showingResolution) { ResolutionView(report: report) } + .sheet(item: $plannedStayEditorTarget) { target in + PlannedStayEditor(region: target.region, model: report.forecasts) + } // Log View Mode: reveal an inspect badge for the year-report events // backing this screen. A no-op in release. .debugLogInspectable(WhereLog.session(YearReportModelLog.self)) @@ -123,6 +127,7 @@ struct LocationsView: View { regionDays: presentedItem, interactive: true, yearLength: report.daysInSelectedYear, + estimatedDays: estimatedDays(for: item.region), year: report.selectedYear, tilt: tilt, recordedPoints: report.primaryRegionLocations? @@ -185,10 +190,13 @@ struct LocationsView: View { .scrollBounceBehavior(.basedOnSize) .accessibilityIdentifier("where_root_title") .safeAreaInset(edge: .bottom) { - if report.showsLocationForecastsOnLocationsTab, !topForecasts.isEmpty { + if report.showsEstimatedTimeAndPlanning, !topForecasts.isEmpty { LocationForecastPanel( forecasts: topForecasts, plannedStay: report.forecasts.activePlannedStay, + editableRegions: topForecasts.map(\.region), + editAction: editPlannedStay, + clearAction: report.forecasts.clear, isCollapsible: true, ) .padding(.horizontal) @@ -228,6 +236,23 @@ struct LocationsView: View { report.forecasts.leadingForecasts(report: report.report) } + private func estimatedDays(for region: Region) -> Int? { + guard report.showsEstimatedTimeAndPlanning else { return nil } + return report.forecasts.forecast(for: region, report: report.report)?.estimatedTotalDays + } + + private func editPlannedStay(_ region: Region) { + plannedStayEditorTarget = PlannedStayEditorTarget(region: region) + } + + private struct PlannedStayEditorTarget: Identifiable { + let region: Region + + var id: Region { + region + } + } + /// The region's calendar, pushed as a nested view. It's the zoom /// destination: the tapped card expands into it via matched geometry, and the /// stack's back gesture collapses it again. @@ -352,9 +377,7 @@ private struct ResolveToolbarLabel: View { } private static func forecastsHiddenReport() -> YearReportModel { - let report = PreviewSupport.loadedYearReportModel() - report.showsLocationForecastsOnLocationsTab = false - return report + PreviewSupport.loadedYearReportModelWithEstimatedTimeHidden() } } diff --git a/Where/WhereUI/Sources/Primary/PresenceTimelineList.swift b/Where/WhereUI/Sources/Primary/PresenceTimelineList.swift index a42672ada..69aeaaad4 100644 --- a/Where/WhereUI/Sources/Primary/PresenceTimelineList.swift +++ b/Where/WhereUI/Sources/Primary/PresenceTimelineList.swift @@ -19,7 +19,9 @@ struct PresenceTimelineList: View { var body: some View { let yearReport = report.report let stints = yearReport.map { PresenceTimeline.stints(from: $0) } ?? [] - let plannedInterval = report.forecasts.plannedInterval(intersecting: report.selectedYear) + let plannedInterval = report.showsEstimatedTimeAndPlanning + ? report.forecasts.plannedInterval(intersecting: report.selectedYear) + : nil if stints.isEmpty, plannedInterval == nil { ContentUnavailableView { @@ -148,6 +150,17 @@ struct PresenceTimelineList: View { PresenceTimelineList(report: PreviewSupport.plannedStayYearReportModel()) } }, + whereSnapshot( + name: "PlannedStayHidden", + configurations: .fullContentPhoneLightDark, + measurementReadiness: .immediate, + ) { + NavigationStack { + PresenceTimelineList(report: PreviewSupport.plannedStayYearReportModel( + showsEstimatedTimeAndPlanning: false, + )) + } + }, ] } } diff --git a/Where/WhereUI/Sources/Primary/RegionSummaryCard.swift b/Where/WhereUI/Sources/Primary/RegionSummaryCard.swift index f2668b997..0c6af4096 100644 --- a/Where/WhereUI/Sources/Primary/RegionSummaryCard.swift +++ b/Where/WhereUI/Sources/Primary/RegionSummaryCard.swift @@ -30,6 +30,10 @@ struct RegionSummaryCard: View { /// (`YearReportModel.daysInSelectedYear`); the default is only for previews. var yearLength = 365 + /// The forecasted total rendered behind recorded progress. Locations cards + /// supply it when Estimated Time & Planning is visible; other cards omit it. + var estimatedDays: Int? + /// The calendar year being summarized, inked onto the entry stamp. Callers /// pass `WhereSession.selectedYear`; the default is only for previews. var year = WhereModel.currentYear @@ -92,6 +96,14 @@ struct RegionSummaryCard: View { styleOverride ?? regionStyles.style(for: regionDays.region) } + private var recordedFraction: Double { + fraction(for: regionDays.days) + } + + private var estimatedFraction: Double? { + estimatedDays.map(fraction) + } + /// Region ink on light cards; a pale derivative on dark cards that remains /// distinct while interactive Liquid Glass illuminates nearby surfaces. private var securityPrintTint: Color { @@ -102,15 +114,15 @@ struct RegionSummaryCard: View { RoundedRectangle(cornerRadius: card.cornerRadius, style: .continuous) } - private var fraction: Double { - guard yearLength > 0 else { return 0 } - return min(1, Double(regionDays.days) / Double(yearLength)) - } - private var barHeight: CGFloat { card.progressBarHeight } + private func fraction(for days: Int) -> Double { + guard yearLength > 0 else { return 0 } + return min(1, max(0, Double(days) / Double(yearLength))) + } + private var regionArtworkLoadID: RegionArtworkLoadID { RegionArtworkLoadID( region: regionDays.region, @@ -127,6 +139,20 @@ struct RegionSummaryCard: View { cardStyles.dayCount } + private var accessibilityLabel: String { + guard let estimatedDays else { + return WhereFormat.regionDaysAccessibility( + region: regionDays.region.localizedName, + days: regionDays.days, + ) + } + return WhereFormat.regionDaysEstimatedAccessibility( + region: regionDays.region.localizedName, + recordedDays: regionDays.days, + estimatedDays: estimatedDays, + ) + } + /// A circular rubber-stamp "entry" impression: the region glyph and year /// ringed by the region name, tilted as if pressed onto the page. The arc /// lettering is dropped on the small compact cards where it can't be read. @@ -294,23 +320,44 @@ struct RegionSummaryCard: View { .foregroundStyle(.secondary) } - Capsule() - .fill(.quaternary) - .frame(height: barHeight) - .overlay(alignment: .leading) { - GeometryReader { proxy in - Capsule() - .fill(style.tint) - .frame(width: proxy.size.width * fraction) - } + VStack(alignment: .trailing, spacing: stylesheet.spacing.xxSmall) { + if let estimatedDays { + Text(WhereFormat.locationCardEstimatedDays(estimatedDays)) + .font(.caption.weight(.semibold)) + .textCase(.uppercase) + .tracking(0.8) + .foregroundStyle(securityPrintTint) + .contentTransition(dayCount.transition(days: estimatedDays)) } - .frame(height: barHeight) - .accessibilityHidden(true) + + Capsule() + .fill(.quaternary) + .frame(height: barHeight) + .overlay(alignment: .leading) { + GeometryReader { proxy in + Capsule() + .fill(style.tint) + .frame(width: proxy.size.width * recordedFraction) + .background(alignment: .leading) { + if let estimatedFraction { + Capsule() + .fill(securityPrintTint.opacity( + cardStyles.estimatedProgressOpacity, + )) + .frame(width: proxy.size.width * estimatedFraction) + } + } + } + } + .frame(height: barHeight) + } + .accessibilityHidden(true) } // What makes the count's `.contentTransition` run at all — one morphs // only inside an animation transaction — and it sweeps the ambient bar, // which reads the same count, in the same beat. .animation(dayCount.animation, value: regionDays.days) + .animation(dayCount.animation, value: estimatedDays) .padding(card.padding) .frame(maxWidth: .infinity, alignment: .leading) .background { stampPaper } @@ -342,12 +389,7 @@ struct RegionSummaryCard: View { y: card.lift.offsetY, ) .accessibilityElement(children: .combine) - .accessibilityLabel( - WhereFormat.regionDaysAccessibility( - region: regionDays.region.localizedName, - days: regionDays.days, - ), - ) + .accessibilityLabel(accessibilityLabel) .task(id: regionArtworkLoadID, loadRegionOutlines) } } diff --git a/Where/WhereUI/Sources/Resources/Localizable.xcstrings b/Where/WhereUI/Sources/Resources/Localizable.xcstrings index 65db4ca7e..d9dc30997 100644 --- a/Where/WhereUI/Sources/Resources/Localizable.xcstrings +++ b/Where/WhereUI/Sources/Resources/Localizable.xcstrings @@ -2311,6 +2311,18 @@ } } }, + "common.regionDaysEstimated.accessibility" : { + "comment" : "VoiceOver summary for a location card showing recorded and estimated annual day counts. The first placeholder is the region name; the second and third are localized day counts.", + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "%1$@: %2$@ recorded, estimated %3$@" + } + } + } + }, "common.retry" : { "extractionState" : "manual", "localizations" : { @@ -3079,6 +3091,18 @@ } } }, + "locationCard.estimatedDays" : { + "comment" : "Compact label above a location card progress bar. The placeholder is an already-localized day count, for example '184 days'.", + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Estimated · %@" + } + } + } + }, "locationForecast.basis" : { "comment" : "Short explanation below a location forecast. The placeholder is the region's year-to-date day count.", "extractionState" : "manual", @@ -3113,6 +3137,17 @@ } } }, + "locationForecast.collapsed" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Collapsed" + } + } + } + }, "locationForecast.editStay" : { "extractionState" : "manual", "localizations" : { @@ -3181,6 +3216,17 @@ } } }, + "locationForecast.expanded" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Expanded" + } + } + } + }, "locationForecast.plan" : { "comment" : "Explains the planned-stay input included in an annual location forecast.", "extractionState" : "manual", @@ -5454,6 +5500,17 @@ } } }, + "settings.appearance.locationForecasts.disableError.title" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Couldn’t turn off Estimated Time & Planning" + } + } + } + }, "settings.appearance.locationForecasts.footer" : { "comment" : "Explains the scope of the annual location forecast visibility toggle.", "extractionState" : "manual", @@ -5461,7 +5518,7 @@ "en" : { "stringUnit" : { "state" : "new", - "value" : "Show or hide the annual estimated-time summary in Locations." + "value" : "Show or hide annual estimates, stay planning, and planned-day visuals across Locations, Calendar, and Timeline. Turning this off also clears your current plan." } } } @@ -5473,7 +5530,7 @@ "en" : { "stringUnit" : { "state" : "new", - "value" : "Estimated time" + "value" : "Estimated time & planning" } } } @@ -6047,6 +6104,281 @@ } } }, + "settings.explore.estimatedTime.calculation.elapsed" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "182 elapsed calendar days" + } + } + } + }, + "settings.explore.estimatedTime.calculation.intro" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Suppose July 1 is day 182. New York has counted on 91 of those days—a 50% pace—and you plan to stay there through July 10." + } + } + } + }, + "settings.explore.estimatedTime.calculation.otherRegions" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Those 9 planned days belong only to New York. Every other region adds no planned days and resumes its own pace on July 11." + } + } + } + }, + "settings.explore.estimatedTime.calculation.pace" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "91 recorded ÷ 182 elapsed = 50% pace" + } + } + } + }, + "settings.explore.estimatedTime.calculation.planned" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "9 days reserved for New York" + } + } + } + }, + "settings.explore.estimatedTime.calculation.projection" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "174 unreserved days × 50% = 87 projected" + } + } + } + }, + "settings.explore.estimatedTime.calculation.remaining" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "174 days left after the plan" + } + } + } + }, + "settings.explore.estimatedTime.calculation.result" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "91 recorded + 9 planned + 87 projected = 187 days" + } + } + } + }, + "settings.explore.estimatedTime.calculation.timeline" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "A 365-day timeline with 182 elapsed days, 9 planned days, and 174 unreserved days remaining." + } + } + } + }, + "settings.explore.estimatedTime.calculation.title" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "A simple example" + } + } + } + }, + "settings.explore.estimatedTime.disabled.description" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Estimated time and planning are hidden. You can turn them on from Appearance." + } + } + } + }, + "settings.explore.estimatedTime.disabled.title" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Estimated time is off" + } + } + } + }, + "settings.explore.estimatedTime.manage" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Manage Estimated Time & Planning" + } + } + } + }, + "settings.explore.estimatedTime.overview" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Your estimated time" + } + } + } + }, + "settings.explore.estimatedTime.pace.description" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "After three complete months, Where divides each region’s year-to-date days by all elapsed calendar days. It applies that pace to unreserved days left in the year, then rounds the total to the nearest day." + } + } + } + }, + "settings.explore.estimatedTime.pace.title" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Your pace" + } + } + } + }, + "settings.explore.estimatedTime.plan.description" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "A plan reserves tomorrow through the selected end date, inclusive. Those days count for the chosen region; every other region’s projection pauses, then each region resumes its own historical pace." + } + } + } + }, + "settings.explore.estimatedTime.plan.title" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Plan ahead" + } + } + } + }, + "settings.explore.estimatedTime.row" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Estimated Time & Planning" + } + } + } + }, + "settings.explore.estimatedTime.tagline" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "See where this year’s pace—and your plans—could take you." + } + } + } + }, + "settings.explore.estimatedTime.title" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Estimated Time & Planning" + } + } + } + }, + "settings.explore.estimatedTime.totals.description" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Each region is calculated independently. A recorded travel day can count toward more than one region, and summaries may omit smaller regions, so displayed estimates may not total 365 days." + } + } + } + }, + "settings.explore.estimatedTime.totals.title" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Why totals vary" + } + } + } + }, + "settings.explore.estimatedTime.unavailable.description" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Estimates appear for the current year after three complete months and enough recorded history to establish a pace." + } + } + } + }, + "settings.explore.estimatedTime.unavailable.title" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "Building your estimate" + } + } + } + }, "settings.explore.evidence.archive.title" : { "extractionState" : "manual", "localizations" : { @@ -7177,6 +7509,17 @@ } } }, + "settings.keywords.estimatedTimeFeatures" : { + "extractionState" : "manual", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "new", + "value" : "estimate, estimated time, forecast, pace, planned stay, planning, overlap, travel days, annual" + } + } + } + }, "settings.keywords.evidenceFeatures" : { "extractionState" : "manual", "localizations" : { diff --git a/Where/WhereUI/Sources/Settings/AppearanceSettingsView.swift b/Where/WhereUI/Sources/Settings/AppearanceSettingsView.swift index 51584c82a..86ea285c1 100644 --- a/Where/WhereUI/Sources/Settings/AppearanceSettingsView.swift +++ b/Where/WhereUI/Sources/Settings/AppearanceSettingsView.swift @@ -10,12 +10,22 @@ struct AppearanceSettingsView: View { var focus: SettingsFocus? @State private var showAppIcon = false + @State private var estimatedTimeSettings: EstimatedTimeAndPlanningSettingsModel #if DEBUG @Environment(\.cardDesignerModel) private var cardDesignerModel #endif + init(report: YearReportModel, focus: SettingsFocus? = nil) { + self.report = report + self.focus = focus + _estimatedTimeSettings = State(initialValue: EstimatedTimeAndPlanningSettingsModel( + report: report, + )) + } + var body: some View { @Bindable var report = report + @Bindable var estimatedTimeSettings = estimatedTimeSettings SettingsFocusScope(focus: focus) { Form { Section { @@ -31,12 +41,19 @@ struct AppearanceSettingsView: View { } Section { - Toggle(isOn: $report.showsLocationForecastsOnLocationsTab) { - Label( - String(localized: .settingsAppearanceLocationForecastsToggle), - systemSymbol: .chartLineUptrendXyaxis, - ) + Toggle(isOn: $estimatedTimeSettings.isEnabled) { + HStack { + Label( + String(localized: .settingsAppearanceLocationForecastsToggle), + systemSymbol: .chartLineUptrendXyaxis, + ) + if estimatedTimeSettings.isUpdating { + ProgressView() + .controlSize(.small) + } + } } + .disabled(estimatedTimeSettings.isUpdating) .settingsRow(Item.locationForecasts) } footer: { Text(String(localized: .settingsAppearanceLocationForecastsFooter)) @@ -82,6 +99,15 @@ struct AppearanceSettingsView: View { .sheet(isPresented: $showAppIcon) { AppIconView() } + .alert( + String(localized: .settingsAppearanceLocationForecastsDisableErrorTitle), + isPresented: $estimatedTimeSettings.isShowingError, + presenting: estimatedTimeSettings.presentedFailure, + ) { _ in + Button(String(localized: .commonOk), role: .cancel) {} + } message: { message in + Text(message) + } } } diff --git a/Where/WhereUI/Sources/Settings/EstimatedTimeAndPlanningSettingsModel.swift b/Where/WhereUI/Sources/Settings/EstimatedTimeAndPlanningSettingsModel.swift new file mode 100644 index 000000000..998b9a695 --- /dev/null +++ b/Where/WhereUI/Sources/Settings/EstimatedTimeAndPlanningSettingsModel.swift @@ -0,0 +1,63 @@ +import Observation + +/// Transactional presentation state for the Appearance forecast toggle. +/// Disabling stays visually on until the synced planned stay is cleared. +@MainActor +@Observable +final class EstimatedTimeAndPlanningSettingsModel { + private enum OperationState: Equatable { + case idle + case updating + case failed(String) + } + + private let report: YearReportModel + private var enabledStorage: Bool + private var operationState: OperationState = .idle + + init(report: YearReportModel) { + self.report = report + enabledStorage = report.showsEstimatedTimeAndPlanning + } + + var isEnabled: Bool { + get { enabledStorage } + set { request(newValue) } + } + + var isUpdating: Bool { + operationState == .updating + } + + var presentedFailure: String? { + guard case let .failed(message) = operationState else { return nil } + return message + } + + var isShowingError: Bool { + get { presentedFailure != nil } + set { + if !newValue, presentedFailure != nil { + operationState = .idle + } + } + } + + private func request(_ isEnabled: Bool) { + guard isEnabled != enabledStorage, !isUpdating else { return } + Task { await setEnabled(isEnabled) } + } + + func setEnabled(_ isEnabled: Bool) async { + guard isEnabled != enabledStorage, !isUpdating else { return } + operationState = .updating + do { + try await report.setEstimatedTimeAndPlanningEnabled(isEnabled) + enabledStorage = report.showsEstimatedTimeAndPlanning + operationState = .idle + } catch { + enabledStorage = report.showsEstimatedTimeAndPlanning + operationState = .failed(error.localizedDescription) + } + } +} diff --git a/Where/WhereUI/Sources/Settings/FeaturePreviews/EstimatedTime/EstimatedTimeFeaturesView.swift b/Where/WhereUI/Sources/Settings/FeaturePreviews/EstimatedTime/EstimatedTimeFeaturesView.swift new file mode 100644 index 000000000..854eb9514 --- /dev/null +++ b/Where/WhereUI/Sources/Settings/FeaturePreviews/EstimatedTime/EstimatedTimeFeaturesView.swift @@ -0,0 +1,222 @@ +import SFSafeSymbols +import SnapshotKit +import SwiftUI +import WhereCore + +/// Explains Where's annual projection and the planned-stay adjustment using +/// the user's live report whenever it is eligible. +struct EstimatedTimeFeaturesView: View { + let report: YearReportModel + let focus: SettingsFocus? + + var body: some View { + StaggeredRevealScope { + SettingsFocusScope(focus: focus) { + EstimatedTimeFeaturesContent(report: report) + } + } + .navigationTitle("") + .navigationBarTitleDisplayMode(.inline) + } +} + +/// Keeps the large concrete `Form` type behind a small nominal view so the +/// focus scope does not copy the form's full value onto the stack while a +/// navigation push is preparing its destination. +private struct EstimatedTimeFeaturesContent: View { + let report: YearReportModel + + @Environment(\.stylesheet) private var stylesheet + + var body: some View { + Form { + FeatureMarketingHeader( + title: String(localized: .settingsExploreEstimatedTimeTitle), + tagline: String(localized: .settingsExploreEstimatedTimeTagline), + systemSymbol: SettingsDestination.estimatedTime.systemSymbol, + tint: SettingsDestination.estimatedTime.iconColor, + ) + .listRowInsets(.init()) + .listRowBackground(Color.clear) + .listRowSeparator(.hidden) + .staggeredReveal(order: 0) + + Section { + preview + .featureMarketingRow(order: 1) + .settingsRow(EstimatedTimeFeaturesView.Item.overview, restingBackground: .clear) + } + + Section { + explanation( + title: String(localized: .settingsExploreEstimatedTimePaceTitle), + body: String(localized: .settingsExploreEstimatedTimePaceDescription), + systemSymbol: .chartLineUptrendXyaxis, + ) + .featureMarketingRow(order: 2) + .settingsRow(EstimatedTimeFeaturesView.Item.pace, restingBackground: .clear) + + explanation( + title: String(localized: .settingsExploreEstimatedTimePlanTitle), + body: String(localized: .settingsExploreEstimatedTimePlanDescription), + systemSymbol: .calendarBadgeClock, + ) + .featureMarketingRow(order: 3) + .settingsRow(EstimatedTimeFeaturesView.Item.planning, restingBackground: .clear) + } + + Section { + FeatureEstimatedTimeCalculationExample() + .featureMarketingRow(order: 4) + .settingsRow( + EstimatedTimeFeaturesView.Item.calculation, + restingBackground: .clear, + ) + } + + Section { + explanation( + title: String(localized: .settingsExploreEstimatedTimeTotalsTitle), + body: String(localized: .settingsExploreEstimatedTimeTotalsDescription), + systemSymbol: .airplane, + ) + .featureMarketingRow(order: 5) + .settingsRow(EstimatedTimeFeaturesView.Item.totals, restingBackground: .clear) + } + + Section { + FeatureMarketingPanel { + NavigationLink(value: SettingsRoute(.appearance)) { + Label { + Text(String(localized: .settingsExploreEstimatedTimeManage)) + .foregroundStyle(.primary) + } icon: { + Image(systemSymbol: .paintbrushFill) + .foregroundStyle(SettingsDestination.estimatedTime.iconColor) + } + } + .frame(maxWidth: .infinity, alignment: .leading) + } + .featureMarketingRow(order: 6) + } footer: { + FeatureDiscoveryDataFooter() + .staggeredReveal(order: 7) + } + } + .scrollContentBackground(.hidden) + .background(FeatureDiscoveryBackground()) + } + + @ViewBuilder + private var preview: some View { + if !report.showsEstimatedTimeAndPlanning { + FeatureEstimatedTimeStatusPreview(state: .disabled) + } else if forecasts.isEmpty { + FeatureEstimatedTimeStatusPreview(state: .unavailable) + } else { + LocationForecastPanel( + forecasts: forecasts, + plannedStay: report.forecasts.activePlannedStay, + ) + } + } + + private var forecasts: [LocationForecast] { + report.forecasts.leadingForecasts(report: report.report) + } + + private func explanation( + title: String, + body: String, + systemSymbol: SFSymbol, + ) -> some View { + FeatureMarketingPanel { + VStack(alignment: .leading, spacing: stylesheet.spacing.small) { + Label(title, systemSymbol: systemSymbol) + .font(.headline) + Text(body) + .font(.subheadline) + .foregroundStyle(.secondary) + } + } + } +} + +extension EstimatedTimeFeaturesView: SettingsSection { + static var destination: SettingsDestination { + .estimatedTime + } + + enum Item: SettingsItem { + case overview + case pace + case planning + case calculation + case totals + + var title: String { + switch self { + case .overview: String(localized: .settingsExploreEstimatedTimeOverview) + case .pace: String(localized: .settingsExploreEstimatedTimePaceTitle) + case .planning: String(localized: .settingsExploreEstimatedTimePlanTitle) + case .calculation: + String(localized: .settingsExploreEstimatedTimeCalculationTitle) + case .totals: String(localized: .settingsExploreEstimatedTimeTotalsTitle) + } + } + + var keywords: [String] { + splitKeywords(String(localized: .settingsKeywordsEstimatedTimeFeatures)) + } + } +} + +#if DEBUG + extension EstimatedTimeFeaturesView: SnapshotProviding { + static var snapshots: [SnapshotCase] { + whereSnapshot( + name: "Live", + configurations: .fullContentScreenDefaults, + measurementReadiness: .immediate, + ) { + EstimatedTimeFeaturesView( + report: PreviewSupport.plannedStayYearReportModel(), + focus: nil, + ) + } + whereSnapshot( + name: "Unavailable", + configurations: .fullContentPhoneLightDark, + measurementReadiness: .immediate, + ) { + EstimatedTimeFeaturesView( + report: PreviewSupport.emptyYearReportModel(), + focus: nil, + ) + } + whereSnapshot( + name: "Disabled", + configurations: .fullContentPhoneLightDark, + measurementReadiness: .immediate, + ) { + EstimatedTimeFeaturesView( + report: PreviewSupport.loadedYearReportModelWithEstimatedTimeHidden(), + focus: nil, + ) + } + } + } + + #Preview { + NavigationStack { EstimatedTimeFeaturesView.snapshotPreviews } + .whereBroadwayRoot() + } + + extension EstimatedTimeFeaturesView: WhereFlyoverProviding { + static let flyoverData = WhereFlyoverData.snapshots( + EstimatedTimeFeaturesView.self, + title: "Estimated Time & Planning", + routes: [.push(to: AppearanceSettingsView.flyoverID)], + ) + } +#endif diff --git a/Where/WhereUI/Sources/Settings/FeaturePreviews/EstimatedTime/FeatureEstimatedTimeCalculationExample.swift b/Where/WhereUI/Sources/Settings/FeaturePreviews/EstimatedTime/FeatureEstimatedTimeCalculationExample.swift new file mode 100644 index 000000000..9d71b3a5b --- /dev/null +++ b/Where/WhereUI/Sources/Settings/FeaturePreviews/EstimatedTime/FeatureEstimatedTimeCalculationExample.swift @@ -0,0 +1,101 @@ +import SwiftUI + +/// A concrete, fixed-year example that makes the forecast's three inputs and +/// arithmetic visible without fabricating a forecast from the user's data. +struct FeatureEstimatedTimeCalculationExample: View { + @Environment(\.stylesheet) private var stylesheet + + var body: some View { + let style = stylesheet.featureDiscovery.estimatedTime + + FeatureMarketingPanel { + VStack(alignment: .leading, spacing: stylesheet.spacing.large) { + Text(String(localized: .settingsExploreEstimatedTimeCalculationTitle)) + .font(.headline) + + Text(String(localized: .settingsExploreEstimatedTimeCalculationIntro)) + .font(.subheadline) + .foregroundStyle(.secondary) + + GeometryReader { geometry in + let segmentWidth = max( + 0, + geometry.size.width - style.timelineSpacing * 2, + ) + + HStack(spacing: style.timelineSpacing) { + RoundedRectangle(cornerRadius: style.segmentCornerRadius) + .fill(.secondary.opacity(0.35)) + .frame(width: segmentWidth * 182 / 365) + RoundedRectangle(cornerRadius: style.segmentCornerRadius) + .fill(.blue) + .frame(width: segmentWidth * 9 / 365) + RoundedRectangle(cornerRadius: style.segmentCornerRadius) + .fill(.blue.opacity(0.24)) + .frame(width: segmentWidth * 174 / 365) + } + .accessibilityElement(children: .ignore) + .accessibilityLabel(String( + localized: .settingsExploreEstimatedTimeCalculationTimeline, + )) + } + .frame(height: style.timelineHeight) + + VStack(alignment: .leading, spacing: stylesheet.spacing.small) { + Label { + Text(String( + localized: .settingsExploreEstimatedTimeCalculationElapsed, + )) + } icon: { + Circle() + .fill(Color.secondary.opacity(0.35)) + .frame(width: style.legendDotSize, height: style.legendDotSize) + } + Label { + Text(String( + localized: .settingsExploreEstimatedTimeCalculationPlanned, + )) + } icon: { + Circle() + .fill(.blue) + .frame(width: style.legendDotSize, height: style.legendDotSize) + } + Label { + Text(String( + localized: .settingsExploreEstimatedTimeCalculationRemaining, + )) + } icon: { + Circle() + .fill(.blue.opacity(0.24)) + .frame(width: style.legendDotSize, height: style.legendDotSize) + } + } + .font(.footnote) + + Divider() + + VStack(alignment: .leading, spacing: style.calculationSpacing) { + Text(String(localized: .settingsExploreEstimatedTimeCalculationPace)) + Text(String(localized: .settingsExploreEstimatedTimeCalculationProjection)) + Text(String(localized: .settingsExploreEstimatedTimeCalculationResult)) + .bold() + } + .font(.subheadline) + .monospacedDigit() + .fixedSize(horizontal: false, vertical: true) + + Text(String(localized: .settingsExploreEstimatedTimeCalculationOtherRegions)) + .font(.footnote) + .foregroundStyle(.secondary) + } + } + } +} + +#if DEBUG + #Preview { + FeatureEstimatedTimeCalculationExample() + .padding() + .whereBroadwayRoot() + } +#endif diff --git a/Where/WhereUI/Sources/Settings/FeaturePreviews/EstimatedTime/FeatureEstimatedTimeStatusPreview.swift b/Where/WhereUI/Sources/Settings/FeaturePreviews/EstimatedTime/FeatureEstimatedTimeStatusPreview.swift new file mode 100644 index 000000000..4cf7da0ac --- /dev/null +++ b/Where/WhereUI/Sources/Settings/FeaturePreviews/EstimatedTime/FeatureEstimatedTimeStatusPreview.swift @@ -0,0 +1,50 @@ +import SFSafeSymbols +import SwiftUI + +/// Educational fallback when a live estimated-time panel cannot render. +struct FeatureEstimatedTimeStatusPreview: View { + enum State { + case disabled + case unavailable + } + + let state: State + + var body: some View { + FeatureMarketingPanel { + VStack(alignment: .leading, spacing: 8) { + Label(title, systemSymbol: .chartLineUptrendXyaxis) + .font(.headline) + Text(message) + .font(.subheadline) + .foregroundStyle(.secondary) + } + } + } + + private var title: String { + switch state { + case .disabled: + String(localized: .settingsExploreEstimatedTimeDisabledTitle) + case .unavailable: + String(localized: .settingsExploreEstimatedTimeUnavailableTitle) + } + } + + private var message: String { + switch state { + case .disabled: + String(localized: .settingsExploreEstimatedTimeDisabledDescription) + case .unavailable: + String(localized: .settingsExploreEstimatedTimeUnavailableDescription) + } + } +} + +#if DEBUG + #Preview { + FeatureEstimatedTimeStatusPreview(state: .unavailable) + .padding() + .whereBroadwayRoot() + } +#endif diff --git a/Where/WhereUI/Sources/Settings/SettingsSearch.swift b/Where/WhereUI/Sources/Settings/SettingsSearch.swift index ed83a627a..9473e80d1 100644 --- a/Where/WhereUI/Sources/Settings/SettingsSearch.swift +++ b/Where/WhereUI/Sources/Settings/SettingsSearch.swift @@ -16,6 +16,7 @@ enum SettingsDestination: Hashable, CaseIterable { case siri case widgets case shareEvidence + case estimatedTime case insightsAccuracy case personalization case data @@ -35,6 +36,7 @@ enum SettingsDestination: Hashable, CaseIterable { case .siri: String(localized: .settingsExploreSiriRow) case .widgets: String(localized: .settingsExploreWidgetsRow) case .shareEvidence: String(localized: .settingsExploreEvidenceRow) + case .estimatedTime: String(localized: .settingsExploreEstimatedTimeRow) case .insightsAccuracy: String(localized: .settingsExploreInsightsRow) case .personalization: String(localized: .settingsExplorePersonalizationRow) case .data: String(localized: .settingsDataHeader) @@ -55,6 +57,7 @@ enum SettingsDestination: Hashable, CaseIterable { case .siri: .waveform case .widgets: .widgetSmall case .shareEvidence: .squareAndArrowDownFill + case .estimatedTime: .chartLineUptrendXyaxis case .insightsAccuracy: .sparkles case .personalization: .paintpaletteFill case .data: .externaldriveFill @@ -77,6 +80,7 @@ enum SettingsDestination: Hashable, CaseIterable { case .siri: .pink case .widgets: .cyan case .shareEvidence: .indigo + case .estimatedTime: .blue case .insightsAccuracy: .orange case .personalization: .purple case .data: .teal @@ -94,7 +98,7 @@ enum SettingsDestination: Hashable, CaseIterable { switch self { case .data, .appearance: false case .attachments, .loggedDays, .devices, .regions, .alerts, .year, .siri, .widgets, - .shareEvidence, .insightsAccuracy, .personalization, .about: + .shareEvidence, .estimatedTime, .insightsAccuracy, .personalization, .about: true } } @@ -106,7 +110,7 @@ enum SettingsDestination: Hashable, CaseIterable { switch self { case .regions: true case .attachments, .loggedDays, .devices, .alerts, .appearance, .year, .siri, .widgets, - .shareEvidence, .insightsAccuracy, .personalization, .data, .about: + .shareEvidence, .estimatedTime, .insightsAccuracy, .personalization, .data, .about: false } } @@ -134,7 +138,14 @@ enum SettingsListSection: CaseIterable { case .notifications: [.alerts] case .display: [.appearance, .year] case .exploreFeatures: - [.siri, .widgets, .shareEvidence, .insightsAccuracy, .personalization] + [ + .siri, + .widgets, + .shareEvidence, + .estimatedTime, + .insightsAccuracy, + .personalization, + ] case .storage: [.data] case .about: [.about] } @@ -251,6 +262,7 @@ enum SettingsCatalog { + SiriFeaturesView.searchResults + WidgetFeaturesView.searchResults + ShareEvidenceFeaturesView.searchResults + + EstimatedTimeFeaturesView.searchResults + InsightsAccuracyFeaturesView.searchResults + PersonalizationFeaturesView.searchResults + DataSettingsView.searchResults diff --git a/Where/WhereUI/Sources/Settings/SettingsView.swift b/Where/WhereUI/Sources/Settings/SettingsView.swift index 920e2261e..ed2cd5c7a 100644 --- a/Where/WhereUI/Sources/Settings/SettingsView.swift +++ b/Where/WhereUI/Sources/Settings/SettingsView.swift @@ -196,7 +196,8 @@ struct SettingsView: View { case .regions: showRegions = true case .attachments, .loggedDays, .devices, .alerts, .appearance, .year, .siri, - .widgets, .shareEvidence, .insightsAccuracy, .personalization, .data, .about: + .widgets, .shareEvidence, .estimatedTime, .insightsAccuracy, .personalization, + .data, .about: assertionFailure("\(destination) is a push destination, not a sheet") } } @@ -230,7 +231,7 @@ struct SettingsView: View { case .year: report.selectedYear.formatted(.number.grouping(.never)) case .attachments, .loggedDays, .regions, .alerts, .appearance, .siri, .widgets, - .shareEvidence, .insightsAccuracy, .personalization, .data, .about: + .shareEvidence, .estimatedTime, .insightsAccuracy, .personalization, .data, .about: nil } } @@ -288,6 +289,8 @@ struct SettingsView: View { focus: route.focus, presentation: featureDiscoveryPresentation, ) + case .estimatedTime: + EstimatedTimeFeaturesView(report: report, focus: route.focus) case .insightsAccuracy: InsightsAccuracyFeaturesView( report: report, @@ -388,6 +391,7 @@ struct SettingsView: View { .push(to: SiriFeaturesView.flyoverID), .push(to: WidgetFeaturesView.flyoverID), .push(to: ShareEvidenceFeaturesView.flyoverID), + .push(to: EstimatedTimeFeaturesView.flyoverID), .push(to: InsightsAccuracyFeaturesView.flyoverID), .push(to: PersonalizationFeaturesView.flyoverID), .push(to: DataSettingsView.flyoverID), diff --git a/Where/WhereUI/Sources/Shared/WhereFormat.swift b/Where/WhereUI/Sources/Shared/WhereFormat.swift index 4bb57ebec..a3c0dad0a 100644 --- a/Where/WhereUI/Sources/Shared/WhereFormat.swift +++ b/Where/WhereUI/Sources/Shared/WhereFormat.swift @@ -84,6 +84,10 @@ enum WhereFormat { )) } + static func locationCardEstimatedDays(_ days: Int) -> String { + String(localized: .locationCardEstimatedDays(dayCount(days))) + } + static func settingsBackupImportedMessage( samples: Int, evidence: Int, @@ -220,6 +224,18 @@ enum WhereFormat { String(localized: .commonRegionDaysAccessibility(region, dayCount(days))) } + static func regionDaysEstimatedAccessibility( + region: String, + recordedDays: Int, + estimatedDays: Int, + ) -> String { + String(localized: .commonRegionDaysEstimatedAccessibility( + region, + dayCount(recordedDays), + dayCount(estimatedDays), + )) + } + static func calendarMonthAccessibility( regionTotals: [RegionDayTally], regionCombinationTotals: [RegionCombinationDayTally], diff --git a/Where/WhereUI/Sources/Shared/WhereStylesheet.swift b/Where/WhereUI/Sources/Shared/WhereStylesheet.swift index d74e095f6..20f0f15e7 100644 --- a/Where/WhereUI/Sources/Shared/WhereStylesheet.swift +++ b/Where/WhereUI/Sources/Shared/WhereStylesheet.swift @@ -569,6 +569,8 @@ extension WhereStylesheet { var glassTintOpacity: Double /// Opacity of the region-name header. var nameOpacity: Double + /// Opacity of the estimated progress rendered behind recorded days. + var estimatedProgressOpacity: Double = 0.3 /// Fill opacities of the two security-print rosettes. var rosetteFill: RosetteFill /// How the region tint is prepared for decorative security printing. @@ -1507,6 +1509,7 @@ extension WhereStylesheet { var marketingHeader: MarketingHeader var marketingPanel: MarketingPanel var backgroundPattern: BackgroundPattern + var estimatedTime: EstimatedTime var siri: Siri var widgets: Widgets @@ -1539,6 +1542,14 @@ extension WhereStylesheet { var opacity: Double } + struct EstimatedTime: Equatable { + var timelineHeight: CGFloat + var timelineSpacing: CGFloat + var calculationSpacing: CGFloat + var segmentCornerRadius: CGFloat + var legendDotSize: CGFloat + } + struct Siri: Equatable { var card: Card var bubble: Bubble @@ -1632,6 +1643,13 @@ extension WhereStylesheet { lineWidth: 0.9, opacity: 0.12, ), + estimatedTime: EstimatedTime( + timelineHeight: 18, + timelineSpacing: 3, + calculationSpacing: 8, + segmentCornerRadius: 5, + legendDotSize: 10, + ), siri: Siri( card: Siri.Card( cornerRadius: 20, diff --git a/Where/WhereUI/Tests/EstimatedTimeAndPlanningSettingsModelTests.swift b/Where/WhereUI/Tests/EstimatedTimeAndPlanningSettingsModelTests.swift new file mode 100644 index 000000000..d6a04a093 --- /dev/null +++ b/Where/WhereUI/Tests/EstimatedTimeAndPlanningSettingsModelTests.swift @@ -0,0 +1,74 @@ +import Foundation +import Testing +@_spi(Testing) import WhereCore +@testable import WhereUI + +@MainActor +struct EstimatedTimeAndPlanningSettingsModelTests { + @Test func disablingClearsThePlanBeforePersistingOff() async throws { + let store = try TestStore() + let preferences = makePreferences() + let report = makeReport(store: store, preferences: preferences) + try await report.forecasts.set( + region: .california, + through: CalendarDay(year: 2027, month: 1, day: 1).startOfDay(in: report.calendar), + ) + let model = EstimatedTimeAndPlanningSettingsModel(report: report) + + await model.setEnabled(false) + + #expect(model.isEnabled == false) + #expect(preferences.showsEstimatedTimeAndPlanning == false) + #expect(report.forecasts.activePlannedStay == nil) + #expect(try await report.services.plannedStays.active() == nil) + } + + @Test func failedClearLeavesTheFeatureOnAndPresentsTheFailure() async throws { + let store = try TestStore() + let preferences = makePreferences() + let report = makeReport(store: store, preferences: preferences) + try await report.forecasts.set( + region: .california, + through: CalendarDay(year: 2027, month: 1, day: 1).startOfDay(in: report.calendar), + ) + await store.failPlannedStays() + let model = EstimatedTimeAndPlanningSettingsModel(report: report) + + await model.setEnabled(false) + + #expect(model.isEnabled) + #expect(preferences.showsEstimatedTimeAndPlanning) + #expect(report.forecasts.activePlannedStay?.region == .california) + #expect(model.presentedFailure != nil) + } + + @Test func enablingDoesNotCreateAPlan() async throws { + let store = try TestStore() + let preferences = makePreferences() + preferences.showsEstimatedTimeAndPlanning = false + let report = makeReport(store: store, preferences: preferences) + let model = EstimatedTimeAndPlanningSettingsModel(report: report) + + await model.setEnabled(true) + + #expect(model.isEnabled) + #expect(preferences.showsEstimatedTimeAndPlanning) + #expect(try await report.services.plannedStays.active() == nil) + } + + private func makeReport( + store: TestStore, + preferences: WherePreferences, + ) -> YearReportModel { + YearReportModel( + services: WhereServices( + store: store, + locationSource: ScriptedLocationSource(), + reminderScheduler: NoopLoggingReminderScheduler(), + widgetRefresher: NoopWidgetTimelineRefresher(), + ), + selectedYear: 2026, + preferences: preferences, + ) + } +} diff --git a/Where/WhereUI/Tests/SettingsSearchTests.swift b/Where/WhereUI/Tests/SettingsSearchTests.swift index 19ee38f51..eb9a0c595 100644 --- a/Where/WhereUI/Tests/SettingsSearchTests.swift +++ b/Where/WhereUI/Tests/SettingsSearchTests.swift @@ -72,11 +72,21 @@ struct SettingsSearchTests { let boardingPass = SettingsCatalog.results(matching: "boarding pass") let drift = SettingsCatalog.results(matching: "drift") let emoji = SettingsCatalog.results(matching: "emoji") + let pace = SettingsCatalog.results(matching: "pace") #expect(automation.contains { $0.destination == .siri }) #expect(accessory.contains { $0.destination == .widgets }) #expect(boardingPass.contains { $0.destination == .shareEvidence }) #expect(drift.contains { $0.destination == .insightsAccuracy }) #expect(emoji.contains { $0.destination == .personalization }) + #expect(pace.contains { $0.destination == .estimatedTime }) + } + + @Test func estimatedTimePrecedesInsightsAndAccuracy() throws { + let destinations = SettingsListSection.exploreFeatures.destinations + let estimatedTime = try #require(destinations.firstIndex(of: .estimatedTime)) + let insights = try #require(destinations.firstIndex(of: .insightsAccuracy)) + + #expect(estimatedTime < insights) } @Test func focusedRouteCarriesTheResultsDestinationAndFocus() throws { diff --git a/Where/WhereUI/Tests/WhereFormatTests.swift b/Where/WhereUI/Tests/WhereFormatTests.swift index e8a21644b..7cbb97ecf 100644 --- a/Where/WhereUI/Tests/WhereFormatTests.swift +++ b/Where/WhereUI/Tests/WhereFormatTests.swift @@ -59,6 +59,17 @@ struct WhereFormatTests { ) } + @Test func locationCardEstimateCopyComposesLocalizedDayCounts() { + #expect(WhereFormat.locationCardEstimatedDays(184) == "Estimated · 184 days") + #expect( + WhereFormat.regionDaysEstimatedAccessibility( + region: "California", + recordedDays: 148, + estimatedDays: 184, + ) == "California: 148 days recorded, estimated 184 days", + ) + } + /// The one string that agrees grammatically via automatic inflection /// (`^[%lld region](inflect: true)`) rather than an explicit plural /// variation, so both forms are worth pinning. diff --git a/Where/WhereUI/Tests/WhereStylesheetTests.swift b/Where/WhereUI/Tests/WhereStylesheetTests.swift index cc484a089..af3a72b41 100644 --- a/Where/WhereUI/Tests/WhereStylesheetTests.swift +++ b/Where/WhereUI/Tests/WhereStylesheetTests.swift @@ -27,6 +27,7 @@ struct WhereStylesheetTests { @Test func regularCardStyle() { let card = style.card.regular + #expect(style.card.estimatedProgressOpacity == 0.3) #expect(card.cornerRadius == 28) #expect(card.padding == 22) #expect(card.contentSpacing == 16) @@ -484,6 +485,13 @@ struct WhereStylesheetTests { lineWidth: 0.9, opacity: 0.12, )) + #expect(featureDiscovery.estimatedTime == .init( + timelineHeight: 18, + timelineSpacing: 3, + calculationSpacing: 8, + segmentCornerRadius: 5, + legendDotSize: 10, + )) #expect(featureDiscovery.siri == .init( card: .init( cornerRadius: 20, diff --git a/Where/WhereUI/Tests/YearReportModelTests.swift b/Where/WhereUI/Tests/YearReportModelTests.swift index 3756cf066..cbb74df44 100644 --- a/Where/WhereUI/Tests/YearReportModelTests.swift +++ b/Where/WhereUI/Tests/YearReportModelTests.swift @@ -279,19 +279,19 @@ struct YearReportModelTests { #expect(preferences.driftThresholdMeters == DriftThreshold.km25.rawValue) } - @Test func locationForecastVisibilityMirrorsAndPersists() throws { + @Test func estimatedTimeVisibilityMirrorsAndPersists() async throws { let preferences = makePreferences() - preferences.showsLocationForecastsOnLocationsTab = false + preferences.showsEstimatedTimeAndPlanning = false let report = try YearReportModel( services: makeServices(), selectedYear: 2026, preferences: preferences, ) - #expect(report.showsLocationForecastsOnLocationsTab == false) + #expect(report.showsEstimatedTimeAndPlanning == false) - report.showsLocationForecastsOnLocationsTab = true - #expect(preferences.showsLocationForecastsOnLocationsTab) + try await report.setEstimatedTimeAndPlanningEnabled(true) + #expect(preferences.showsEstimatedTimeAndPlanning) } /// The Resolve list keys its scan `.task(id:)` on `dataIssueScanInputs`, so a