From 9424de7530b5a0c714acac0ef23e894f2a2c184a Mon Sep 17 00:00:00 2001 From: Kyle Van Essen Date: Fri, 4 Sep 2026 15:54:32 -0700 Subject: [PATCH 1/5] Merge continuous planned Timeline stays --- ...ne.PlannedStayAfterRecordingGap_iPhone.png | 3 ++ ...annedStayAfterRecordingGap_iPhone_dark.png | 3 ++ ...line.PlannedStayDifferentRegion_iPhone.png | 3 ++ ...PlannedStayDifferentRegion_iPhone_dark.png | 3 ++ .../presenceTimeline.PlannedStay_iPhone.png | 4 +- ...eline.PlannedStay_iPhone_accessibility.png | 4 +- ...esenceTimeline.PlannedStay_iPhone_dark.png | 4 +- .../Sources/Preview/PreviewSupport.swift | 6 ++- .../Primary/PlannedPresenceJourneyRow.swift | 6 ++- .../Primary/PresenceJourneyCardPosition.swift | 46 +++++++++++++++++++ .../Sources/Primary/PresenceJourneyRow.swift | 17 +++---- .../Primary/PresenceTimelineList.swift | 34 ++++++++++++++ 12 files changed, 115 insertions(+), 18 deletions(-) create mode 100644 Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStayAfterRecordingGap_iPhone.png create mode 100644 Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStayAfterRecordingGap_iPhone_dark.png create mode 100644 Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStayDifferentRegion_iPhone.png create mode 100644 Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStayDifferentRegion_iPhone_dark.png create mode 100644 Where/WhereUI/Sources/Primary/PresenceJourneyCardPosition.swift diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStayAfterRecordingGap_iPhone.png b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStayAfterRecordingGap_iPhone.png new file mode 100644 index 000000000..acacece6b --- /dev/null +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStayAfterRecordingGap_iPhone.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:c55303771d25079f965e744bb7d21f3e84fa06fed865a27f74b93e076efb5423 +size 217572 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStayAfterRecordingGap_iPhone_dark.png b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStayAfterRecordingGap_iPhone_dark.png new file mode 100644 index 000000000..be755e9a3 --- /dev/null +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStayAfterRecordingGap_iPhone_dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:7bd573f565dc870df6c296b2a9703b577a3d6bd25e6e913df65b0f83d1ed4758 +size 224168 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStayDifferentRegion_iPhone.png b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStayDifferentRegion_iPhone.png new file mode 100644 index 000000000..5155207ec --- /dev/null +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStayDifferentRegion_iPhone.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:1a942accfd3fcef5c76b89b4c1bd4dc92b6d7515da8648e1f4a3019ec310c429 +size 221505 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStayDifferentRegion_iPhone_dark.png b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStayDifferentRegion_iPhone_dark.png new file mode 100644 index 000000000..ee44efbd3 --- /dev/null +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStayDifferentRegion_iPhone_dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:f64cfea81569f945b537467a0afea4f83ba4b034c6bb699e14c9fdf35abeb96c +size 225708 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone.png b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone.png index dcf23a1aa..5742f3afe 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:de920a261347f2cdbb47eed2de584583dfcd27e01ccdc6d01cf3074c02e6372f -size 218548 +oid sha256:54374b960bfb62d5adc347b0c9be5b0388fd33cbf2c8e50fd858ab30c7c77c7b +size 213185 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone_accessibility.png b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone_accessibility.png index a1d8480bf..d78517b66 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone_accessibility.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone_accessibility.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:e7c3f596af11216a4814a07498157b33d1bafa2efcb912c8aaa9c13e66aaf643 -size 332590 +oid sha256:47f2206004508839555566b7fd3a695829c775b58aa89e39e35652b2eec5801b +size 327774 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone_dark.png b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone_dark.png index 2af91afee..6412e7f14 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone_dark.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone_dark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f68728fcf011d370c27443cbe735251aed48ceab10fd13e57a450cb49d4a447f -size 224959 +oid sha256:ba2590ff520c20eafc9b205886070c5f10926ec8b1c8b2cfdd99f9c0d4701090 +size 218379 diff --git a/Where/WhereUI/Sources/Preview/PreviewSupport.swift b/Where/WhereUI/Sources/Preview/PreviewSupport.swift index cf56ec64c..38f61f708 100644 --- a/Where/WhereUI/Sources/Preview/PreviewSupport.swift +++ b/Where/WhereUI/Sources/Preview/PreviewSupport.swift @@ -341,12 +341,14 @@ @MainActor public static func plannedStayYearReportModel( showsEstimatedTimeAndPlanning: Bool = true, + plannedRegion: Region = .newYork, + recordedThroughDay: CalendarDay? = nil, ) -> YearReportModel { let completeReport = sampleReport() var calendar = Calendar(identifier: .gregorian) calendar.timeZone = TimeZone(identifier: "America/Los_Angeles")! let today = CalendarDay(from: referenceNow, in: calendar) - let recordedDays = completeReport.days.filter { $0.day <= today } + let recordedDays = completeReport.days.filter { $0.day <= recordedThroughDay ?? today } var recordedTotals: [Region: Int] = [:] for day in recordedDays { for region in day.regions { @@ -370,7 +372,7 @@ now: { referenceNow }, ) model.forecasts.setActivePlannedStay(PlannedStay( - region: .newYork, + region: plannedRegion, through: CalendarDay(year: year, month: 8, day: 15), )) return model diff --git a/Where/WhereUI/Sources/Primary/PlannedPresenceJourneyRow.swift b/Where/WhereUI/Sources/Primary/PlannedPresenceJourneyRow.swift index af94573c7..419143f5f 100644 --- a/Where/WhereUI/Sources/Primary/PlannedPresenceJourneyRow.swift +++ b/Where/WhereUI/Sources/Primary/PlannedPresenceJourneyRow.swift @@ -8,6 +8,7 @@ struct PlannedPresenceJourneyRow: View { let calendar: Calendar let daysInYear: Int let isFirst: Bool + let cardPosition: PresenceJourneyCardPosition @Environment(\.stylesheet) private var stylesheet @Environment(\.regionStyles) private var regionStyles @@ -30,7 +31,7 @@ struct PlannedPresenceJourneyRow: View { : AnyLayout(HStackLayout(alignment: .center, spacing: row.spacing)) let proportionalHeight = row.baseHeight + row.yearScaleHeight * CGFloat(interval.dayCount) / CGFloat(daysInYear) - let cardShape = RoundedRectangle(cornerRadius: row.cornerRadius) + let cardShape = cardPosition.shape(cornerRadius: row.cornerRadius) HStack(spacing: rail.toCardSpacing) { Color.clear @@ -84,7 +85,7 @@ struct PlannedPresenceJourneyRow: View { ) } } - .padding(.vertical, row.gap / 2) + .padding(cardPosition.gapEdges, row.gap / 2) .background(alignment: .leading) { PresenceJourneyRail( tint: style.tint.opacity(planned.labelOpacity), @@ -115,6 +116,7 @@ struct PlannedPresenceJourneyRow: View { calendar: Calendar(identifier: .gregorian), daysInYear: 365, isFirst: false, + cardPosition: .standalone, ) .padding() .whereBroadwayRoot() diff --git a/Where/WhereUI/Sources/Primary/PresenceJourneyCardPosition.swift b/Where/WhereUI/Sources/Primary/PresenceJourneyCardPosition.swift new file mode 100644 index 000000000..7aa6083b7 --- /dev/null +++ b/Where/WhereUI/Sources/Primary/PresenceJourneyCardPosition.swift @@ -0,0 +1,46 @@ +import SwiftUI + +/// Which outer corners and list gaps a journey-card segment owns. +enum PresenceJourneyCardPosition { + case standalone + case top + case bottom + + func shape(cornerRadius: CGFloat) -> UnevenRoundedRectangle { + UnevenRoundedRectangle( + topLeadingRadius: roundsTop ? cornerRadius : 0, + bottomLeadingRadius: roundsBottom ? cornerRadius : 0, + bottomTrailingRadius: roundsBottom ? cornerRadius : 0, + topTrailingRadius: roundsTop ? cornerRadius : 0, + ) + } + + var gapEdges: Edge.Set { + switch self { + case .standalone: + [.top, .bottom] + case .top: + .top + case .bottom: + .bottom + } + } + + private var roundsTop: Bool { + switch self { + case .standalone, .top: + true + case .bottom: + false + } + } + + private var roundsBottom: Bool { + switch self { + case .standalone, .bottom: + true + case .top: + false + } + } +} diff --git a/Where/WhereUI/Sources/Primary/PresenceJourneyRow.swift b/Where/WhereUI/Sources/Primary/PresenceJourneyRow.swift index 7d73a8ae9..aa4dbe17e 100644 --- a/Where/WhereUI/Sources/Primary/PresenceJourneyRow.swift +++ b/Where/WhereUI/Sources/Primary/PresenceJourneyRow.swift @@ -8,6 +8,7 @@ struct PresenceJourneyRow: View { let daysInYear: Int let isFirst: Bool let isLast: Bool + let cardPosition: PresenceJourneyCardPosition @Environment(\.stylesheet) private var stylesheet @Environment(\.regionStyles) private var regionStyles @@ -27,6 +28,7 @@ struct PresenceJourneyRow: View { : AnyLayout(HStackLayout(alignment: .center, spacing: row.spacing)) let proportionalHeight = row.baseHeight + row.yearScaleHeight * CGFloat(stint.dayCount) / CGFloat(daysInYear) + let cardShape = cardPosition.shape(cornerRadius: row.cornerRadius) HStack(spacing: rail.toCardSpacing) { Color.clear @@ -56,18 +58,16 @@ struct PresenceJourneyRow: View { .padding(.horizontal, row.horizontalPadding) .padding(.vertical, row.verticalPadding) .background { - RoundedRectangle(cornerRadius: row.cornerRadius) - .fill(style.tint.opacity(row.fillOpacity)) + cardShape.fill(style.tint.opacity(row.fillOpacity)) } .overlay { - RoundedRectangle(cornerRadius: row.cornerRadius) - .stroke( - style.tint.opacity(row.borderOpacity), - lineWidth: row.borderWidth, - ) + cardShape.stroke( + style.tint.opacity(row.borderOpacity), + lineWidth: row.borderWidth, + ) } } - .padding(.vertical, row.gap / 2) + .padding(cardPosition.gapEdges, row.gap / 2) .background(alignment: .leading) { PresenceJourneyRail( tint: style.tint, @@ -99,6 +99,7 @@ struct PresenceJourneyRow: View { daysInYear: report.daysInSelectedYear, isFirst: true, isLast: false, + cardPosition: .standalone, ) .padding() .whereBroadwayRoot() diff --git a/Where/WhereUI/Sources/Primary/PresenceTimelineList.swift b/Where/WhereUI/Sources/Primary/PresenceTimelineList.swift index 69aeaaad4..4cb3fa8ec 100644 --- a/Where/WhereUI/Sources/Primary/PresenceTimelineList.swift +++ b/Where/WhereUI/Sources/Primary/PresenceTimelineList.swift @@ -22,6 +22,13 @@ struct PresenceTimelineList: View { let plannedInterval = report.showsEstimatedTimeAndPlanning ? report.forecasts.plannedInterval(intersecting: report.selectedYear) : nil + let joinsPlannedStay = if let plannedInterval, let currentStint = stints.last { + plannedInterval.region == currentStint.region + && CalendarDay(from: currentStint.end, in: report.calendar).adding(days: 1) + == plannedInterval.start + } else { + false + } if stints.isEmpty, plannedInterval == nil { ContentUnavailableView { @@ -54,6 +61,10 @@ struct PresenceTimelineList: View { isFirst: index == stints.startIndex, isLast: plannedInterval == nil && index == stints.index(before: stints.endIndex), + cardPosition: joinsPlannedStay + && index == stints.index(before: stints.endIndex) + ? .top + : .standalone, ) } @@ -63,6 +74,7 @@ struct PresenceTimelineList: View { calendar: report.calendar, daysInYear: report.daysInSelectedYear, isFirst: stints.isEmpty, + cardPosition: joinsPlannedStay ? .bottom : .standalone, ) } } @@ -161,6 +173,28 @@ struct PresenceTimelineList: View { )) } }, + whereSnapshot( + name: "PlannedStayDifferentRegion", + configurations: .fullContentPhoneLightDark, + measurementReadiness: .immediate, + ) { + NavigationStack { + PresenceTimelineList(report: PreviewSupport.plannedStayYearReportModel( + plannedRegion: .california, + )) + } + }, + whereSnapshot( + name: "PlannedStayAfterRecordingGap", + configurations: .fullContentPhoneLightDark, + measurementReadiness: .immediate, + ) { + NavigationStack { + PresenceTimelineList(report: PreviewSupport.plannedStayYearReportModel( + recordedThroughDay: CalendarDay(year: 2026, month: 7, day: 12), + )) + } + }, ] } } From e476a498344ddae76ce7b19c44b1d8e15158e031 Mon Sep 17 00:00:00 2001 From: Kyle Van Essen Date: Fri, 4 Sep 2026 16:07:07 -0700 Subject: [PATCH 2/5] Remove the joined Timeline card seam --- .../presenceTimeline.PlannedStay_iPhone.png | 4 +- ...esenceTimeline.PlannedStay_iPhone_dark.png | 4 +- .../Primary/PlannedPresenceJourneyRow.swift | 6 ++- .../Primary/PresenceJourneyCardBorder.swift | 37 +++++++++++++++++++ .../Primary/PresenceJourneyCardPosition.swift | 11 ++++++ .../Sources/Primary/PresenceJourneyRow.swift | 6 ++- 6 files changed, 60 insertions(+), 8 deletions(-) create mode 100644 Where/WhereUI/Sources/Primary/PresenceJourneyCardBorder.swift diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone.png b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone.png index 5742f3afe..8cabb7a83 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:54374b960bfb62d5adc347b0c9be5b0388fd33cbf2c8e50fd858ab30c7c77c7b -size 213185 +oid sha256:03c8d78908c2afccbfabf348a685b7e016f0202c84c2af515b512bb5b61880fa +size 211820 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone_dark.png b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone_dark.png index 6412e7f14..deff3ff45 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone_dark.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone_dark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:ba2590ff520c20eafc9b205886070c5f10926ec8b1c8b2cfdd99f9c0d4701090 -size 218379 +oid sha256:6e0be8330bf7917956a8782dcf8ffb13226eabf9b61ea715b312c7e36d6f2158 +size 216920 diff --git a/Where/WhereUI/Sources/Primary/PlannedPresenceJourneyRow.swift b/Where/WhereUI/Sources/Primary/PlannedPresenceJourneyRow.swift index 419143f5f..b273f37bf 100644 --- a/Where/WhereUI/Sources/Primary/PlannedPresenceJourneyRow.swift +++ b/Where/WhereUI/Sources/Primary/PlannedPresenceJourneyRow.swift @@ -79,8 +79,10 @@ struct PlannedPresenceJourneyRow: View { } } .overlay { - cardShape.stroke( - style.tint.opacity(planned.borderOpacity), + PresenceJourneyCardBorder( + position: cardPosition, + cornerRadius: row.cornerRadius, + color: style.tint.opacity(planned.borderOpacity), lineWidth: row.borderWidth, ) } diff --git a/Where/WhereUI/Sources/Primary/PresenceJourneyCardBorder.swift b/Where/WhereUI/Sources/Primary/PresenceJourneyCardBorder.swift new file mode 100644 index 000000000..0dc00449a --- /dev/null +++ b/Where/WhereUI/Sources/Primary/PresenceJourneyCardBorder.swift @@ -0,0 +1,37 @@ +import SwiftUI + +/// Draws only the outer stroke of a standalone or joined journey-card segment. +struct PresenceJourneyCardBorder: View { + let position: PresenceJourneyCardPosition + let cornerRadius: CGFloat + let color: Color + let lineWidth: CGFloat + + var body: some View { + switch position { + case .standalone: + position.shape(cornerRadius: cornerRadius) + .stroke(color, lineWidth: lineWidth) + case .top, .bottom: + position.shape(cornerRadius: cornerRadius) + .stroke(color, lineWidth: lineWidth) + .mask { + Rectangle() + .padding(position.joinedEdge, lineWidth) + } + } + } +} + +#if DEBUG + #Preview { + PresenceJourneyCardBorder( + position: .standalone, + cornerRadius: 18, + color: .blue, + lineWidth: 1, + ) + .frame(width: 240, height: 120) + .padding() + } +#endif diff --git a/Where/WhereUI/Sources/Primary/PresenceJourneyCardPosition.swift b/Where/WhereUI/Sources/Primary/PresenceJourneyCardPosition.swift index 7aa6083b7..e9e3757dc 100644 --- a/Where/WhereUI/Sources/Primary/PresenceJourneyCardPosition.swift +++ b/Where/WhereUI/Sources/Primary/PresenceJourneyCardPosition.swift @@ -26,6 +26,17 @@ enum PresenceJourneyCardPosition { } } + var joinedEdge: Edge.Set { + switch self { + case .standalone: + [] + case .top: + .bottom + case .bottom: + .top + } + } + private var roundsTop: Bool { switch self { case .standalone, .top: diff --git a/Where/WhereUI/Sources/Primary/PresenceJourneyRow.swift b/Where/WhereUI/Sources/Primary/PresenceJourneyRow.swift index aa4dbe17e..1d8c2b6bf 100644 --- a/Where/WhereUI/Sources/Primary/PresenceJourneyRow.swift +++ b/Where/WhereUI/Sources/Primary/PresenceJourneyRow.swift @@ -61,8 +61,10 @@ struct PresenceJourneyRow: View { cardShape.fill(style.tint.opacity(row.fillOpacity)) } .overlay { - cardShape.stroke( - style.tint.opacity(row.borderOpacity), + PresenceJourneyCardBorder( + position: cardPosition, + cornerRadius: row.cornerRadius, + color: style.tint.opacity(row.borderOpacity), lineWidth: row.borderWidth, ) } From 2c68ec7c66b7b21ab00b00b033b3a407a9626df0 Mon Sep 17 00:00:00 2001 From: Kyle Van Essen Date: Fri, 4 Sep 2026 16:16:36 -0700 Subject: [PATCH 3/5] Fade the joined Timeline card transition --- .../presenceTimeline.PlannedStay_iPhone.png | 4 +- ...eline.PlannedStay_iPhone_accessibility.png | 4 +- ...esenceTimeline.PlannedStay_iPhone_dark.png | 4 +- .../PlannedPresenceJourneyBackground.swift | 71 +++++++++++++++++++ .../Primary/PlannedPresenceJourneyRow.swift | 17 ++--- .../Primary/PresenceJourneyCardPosition.swift | 9 +++ .../Sources/Shared/WhereStylesheet.swift | 2 + .../WhereUI/Tests/WhereStylesheetTests.swift | 1 + 8 files changed, 93 insertions(+), 19 deletions(-) create mode 100644 Where/WhereUI/Sources/Primary/PlannedPresenceJourneyBackground.swift diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone.png b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone.png index 8cabb7a83..fd29dcdb7 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:03c8d78908c2afccbfabf348a685b7e016f0202c84c2af515b512bb5b61880fa -size 211820 +oid sha256:0874f85ca8b0e0f986afc1e557898fa87d4f0807aeea1c007972766c7bb05540 +size 218866 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone_accessibility.png b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone_accessibility.png index d78517b66..156789460 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone_accessibility.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone_accessibility.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:47f2206004508839555566b7fd3a695829c775b58aa89e39e35652b2eec5801b -size 327774 +oid sha256:1dc24e887b5afa7506132087f5bbb9192bdf055f3a18457e359c5891686cf6a3 +size 335727 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone_dark.png b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone_dark.png index deff3ff45..90efce509 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone_dark.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone_dark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6e0be8330bf7917956a8782dcf8ffb13226eabf9b61ea715b312c7e36d6f2158 -size 216920 +oid sha256:17edbe357c0f846da52e48d789bf8a20e92b9be3ccd5fc7e9d89e80b60607c8b +size 221619 diff --git a/Where/WhereUI/Sources/Primary/PlannedPresenceJourneyBackground.swift b/Where/WhereUI/Sources/Primary/PlannedPresenceJourneyBackground.swift new file mode 100644 index 000000000..9d44eefc4 --- /dev/null +++ b/Where/WhereUI/Sources/Primary/PlannedPresenceJourneyBackground.swift @@ -0,0 +1,71 @@ +import SwiftUI + +/// Draws the lighter planned treatment, fading it in after a joined recorded card. +struct PlannedPresenceJourneyBackground: View { + let position: PresenceJourneyCardPosition + let tint: Color + + @Environment(\.stylesheet) private var stylesheet + + var body: some View { + let timeline = stylesheet.timeline + let row = timeline.row + let planned = timeline.planned + let shape = position.shape(cornerRadius: row.cornerRadius) + + ZStack { + if position.fadesFromRecorded { + VStack(spacing: 0) { + LinearGradient( + colors: [ + tint.opacity(row.fillOpacity), + tint.opacity(planned.fillOpacity), + ], + startPoint: .top, + endPoint: .bottom, + ) + .frame(height: planned.transitionHeight) + + tint.opacity(planned.fillOpacity) + } + } else { + shape + .fill(tint.opacity(planned.fillOpacity)) + } + + PlannedStayHatch( + color: tint, + spacing: planned.hatchSpacing, + lineWidth: planned.hatchLineWidth, + gridOriginX: 0, + ) + .opacity(planned.hatchOpacity) + .mask { + if position.fadesFromRecorded { + VStack(spacing: 0) { + LinearGradient( + colors: [.clear, .white], + startPoint: .top, + endPoint: .bottom, + ) + .frame(height: planned.transitionHeight) + + Color.white + } + } else { + Color.white + } + } + } + .clipShape(shape) + } +} + +#if DEBUG + #Preview { + PlannedPresenceJourneyBackground(position: .bottom, tint: .blue) + .frame(width: 240, height: 120) + .padding() + .whereBroadwayRoot() + } +#endif diff --git a/Where/WhereUI/Sources/Primary/PlannedPresenceJourneyRow.swift b/Where/WhereUI/Sources/Primary/PlannedPresenceJourneyRow.swift index b273f37bf..a7b3caae2 100644 --- a/Where/WhereUI/Sources/Primary/PlannedPresenceJourneyRow.swift +++ b/Where/WhereUI/Sources/Primary/PlannedPresenceJourneyRow.swift @@ -31,7 +31,6 @@ struct PlannedPresenceJourneyRow: View { : AnyLayout(HStackLayout(alignment: .center, spacing: row.spacing)) let proportionalHeight = row.baseHeight + row.yearScaleHeight * CGFloat(interval.dayCount) / CGFloat(daysInYear) - let cardShape = cardPosition.shape(cornerRadius: row.cornerRadius) HStack(spacing: rail.toCardSpacing) { Color.clear @@ -65,18 +64,10 @@ struct PlannedPresenceJourneyRow: View { .padding(.horizontal, row.horizontalPadding) .padding(.vertical, row.verticalPadding) .background { - ZStack { - cardShape - .fill(style.tint.opacity(planned.fillOpacity)) - PlannedStayHatch( - color: style.tint, - spacing: planned.hatchSpacing, - lineWidth: planned.hatchLineWidth, - gridOriginX: 0, - ) - .opacity(planned.hatchOpacity) - .clipShape(cardShape) - } + PlannedPresenceJourneyBackground( + position: cardPosition, + tint: style.tint, + ) } .overlay { PresenceJourneyCardBorder( diff --git a/Where/WhereUI/Sources/Primary/PresenceJourneyCardPosition.swift b/Where/WhereUI/Sources/Primary/PresenceJourneyCardPosition.swift index e9e3757dc..fe52a16db 100644 --- a/Where/WhereUI/Sources/Primary/PresenceJourneyCardPosition.swift +++ b/Where/WhereUI/Sources/Primary/PresenceJourneyCardPosition.swift @@ -37,6 +37,15 @@ enum PresenceJourneyCardPosition { } } + var fadesFromRecorded: Bool { + switch self { + case .standalone, .top: + false + case .bottom: + true + } + } + private var roundsTop: Bool { switch self { case .standalone, .top: diff --git a/Where/WhereUI/Sources/Shared/WhereStylesheet.swift b/Where/WhereUI/Sources/Shared/WhereStylesheet.swift index 7ea0efabe..a03f41828 100644 --- a/Where/WhereUI/Sources/Shared/WhereStylesheet.swift +++ b/Where/WhereUI/Sources/Shared/WhereStylesheet.swift @@ -1494,6 +1494,7 @@ extension WhereStylesheet { var hatchSpacing: CGFloat var hatchLineWidth: CGFloat var labelOpacity: Double + var transitionHeight: CGFloat } static let standard = TimelineStyle( @@ -1549,6 +1550,7 @@ extension WhereStylesheet { hatchSpacing: 8, hatchLineWidth: 1, labelOpacity: 0.7, + transitionHeight: 16, ), ) } diff --git a/Where/WhereUI/Tests/WhereStylesheetTests.swift b/Where/WhereUI/Tests/WhereStylesheetTests.swift index 4150b8fe1..84cec2784 100644 --- a/Where/WhereUI/Tests/WhereStylesheetTests.swift +++ b/Where/WhereUI/Tests/WhereStylesheetTests.swift @@ -443,6 +443,7 @@ struct WhereStylesheetTests { #expect(planned.hatchSpacing == 8) #expect(planned.hatchLineWidth == 1) #expect(planned.labelOpacity == 0.7) + #expect(planned.transitionHeight == 16) } @Test func regionMapStyle() { From 54702787130a1997549578b2d8aab5e2e1ac6ee8 Mon Sep 17 00:00:00 2001 From: Kyle Van Essen Date: Fri, 4 Sep 2026 16:41:28 -0700 Subject: [PATCH 4/5] Compact joined Timeline plan rows --- .../presenceTimeline.PlannedStay_iPhone.png | 4 +- ...eline.PlannedStay_iPhone_accessibility.png | 4 +- ...esenceTimeline.PlannedStay_iPhone_dark.png | 4 +- ...esenceTimeline.ShortPlannedStay_iPhone.png | 3 + ...eTimeline.ShortPlannedStay_iPhone_dark.png | 3 + .../Sources/Preview/PreviewSupport.swift | 3 +- .../PlannedPresenceJourneyBackground.swift | 4 +- .../PlannedPresenceJourneyCardContent.swift | 119 ++++++++++++++++++ .../Primary/PlannedPresenceJourneyRow.swift | 76 +++++------ .../Primary/PresenceJourneyCardPosition.swift | 2 +- .../Primary/PresenceTimelineList.swift | 11 ++ .../Sources/Shared/WhereStylesheet.swift | 10 ++ .../WhereUI/Tests/WhereStylesheetTests.swift | 5 + 13 files changed, 194 insertions(+), 54 deletions(-) create mode 100644 Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.ShortPlannedStay_iPhone.png create mode 100644 Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.ShortPlannedStay_iPhone_dark.png create mode 100644 Where/WhereUI/Sources/Primary/PlannedPresenceJourneyCardContent.swift diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone.png b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone.png index fd29dcdb7..0c0b15e34 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0874f85ca8b0e0f986afc1e557898fa87d4f0807aeea1c007972766c7bb05540 -size 218866 +oid sha256:0a932f25197ac1eddaf62554d834b4a656566812840dbd84eef011dbc4a7c38d +size 205865 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone_accessibility.png b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone_accessibility.png index 156789460..20fc4f87a 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone_accessibility.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone_accessibility.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1dc24e887b5afa7506132087f5bbb9192bdf055f3a18457e359c5891686cf6a3 -size 335727 +oid sha256:30f88ab05efab31e8f854a16b7f03eec357349176192e8c3f24e9ce189a5950d +size 323572 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone_dark.png b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone_dark.png index 90efce509..d2db0bb5f 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone_dark.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone_dark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:17edbe357c0f846da52e48d789bf8a20e92b9be3ccd5fc7e9d89e80b60607c8b -size 221619 +oid sha256:9b4cabcefcc50c1b474672b35b76ee5f916aca79b5ab09dceeb9ed00cafd7372 +size 209011 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.ShortPlannedStay_iPhone.png b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.ShortPlannedStay_iPhone.png new file mode 100644 index 000000000..56c6ec7dc --- /dev/null +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.ShortPlannedStay_iPhone.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:4a6c35c78d06d90afb52fe052b2dae68c302012cfd7d122361e4c1dd08e0e775 +size 201954 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.ShortPlannedStay_iPhone_dark.png b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.ShortPlannedStay_iPhone_dark.png new file mode 100644 index 000000000..7b846a30d --- /dev/null +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.ShortPlannedStay_iPhone_dark.png @@ -0,0 +1,3 @@ +version https://git-lfs.github.com/spec/v1 +oid sha256:17de7f7c92f21776bc522a7e547cb7b08d3b928c34674d2971aa7517e8223a22 +size 205045 diff --git a/Where/WhereUI/Sources/Preview/PreviewSupport.swift b/Where/WhereUI/Sources/Preview/PreviewSupport.swift index 38f61f708..8c56af8de 100644 --- a/Where/WhereUI/Sources/Preview/PreviewSupport.swift +++ b/Where/WhereUI/Sources/Preview/PreviewSupport.swift @@ -343,6 +343,7 @@ showsEstimatedTimeAndPlanning: Bool = true, plannedRegion: Region = .newYork, recordedThroughDay: CalendarDay? = nil, + plannedThroughDay: CalendarDay = CalendarDay(year: year, month: 8, day: 15), ) -> YearReportModel { let completeReport = sampleReport() var calendar = Calendar(identifier: .gregorian) @@ -373,7 +374,7 @@ ) model.forecasts.setActivePlannedStay(PlannedStay( region: plannedRegion, - through: CalendarDay(year: year, month: 8, day: 15), + through: plannedThroughDay, )) return model } diff --git a/Where/WhereUI/Sources/Primary/PlannedPresenceJourneyBackground.swift b/Where/WhereUI/Sources/Primary/PlannedPresenceJourneyBackground.swift index 9d44eefc4..d8dab8e4a 100644 --- a/Where/WhereUI/Sources/Primary/PlannedPresenceJourneyBackground.swift +++ b/Where/WhereUI/Sources/Primary/PlannedPresenceJourneyBackground.swift @@ -14,7 +14,7 @@ struct PlannedPresenceJourneyBackground: View { let shape = position.shape(cornerRadius: row.cornerRadius) ZStack { - if position.fadesFromRecorded { + if position.isJoinedContinuation { VStack(spacing: 0) { LinearGradient( colors: [ @@ -41,7 +41,7 @@ struct PlannedPresenceJourneyBackground: View { ) .opacity(planned.hatchOpacity) .mask { - if position.fadesFromRecorded { + if position.isJoinedContinuation { VStack(spacing: 0) { LinearGradient( colors: [.clear, .white], diff --git a/Where/WhereUI/Sources/Primary/PlannedPresenceJourneyCardContent.swift b/Where/WhereUI/Sources/Primary/PlannedPresenceJourneyCardContent.swift new file mode 100644 index 000000000..429cfd6e5 --- /dev/null +++ b/Where/WhereUI/Sources/Primary/PlannedPresenceJourneyCardContent.swift @@ -0,0 +1,119 @@ +import SwiftUI + +/// Lays out a planned stay as a full row or a compact joined continuation. +struct PlannedPresenceJourneyCardContent: View { + let regionName: String + let dateRange: String + let dayCount: Int + let daysInYear: Int + let position: PresenceJourneyCardPosition + let tint: Color + + @Environment(\.stylesheet) private var stylesheet + + var body: some View { + let timeline = stylesheet.timeline + let row = timeline.row + let planned = timeline.planned + let countLayout = row.stacksDayCount + ? AnyLayout(VStackLayout(alignment: .leading, spacing: row.labelSpacing)) + : AnyLayout(HStackLayout(alignment: .center, spacing: row.spacing)) + let baseHeight = position.isJoinedContinuation + ? planned.joinedBaseHeight + : row.baseHeight + let proportionalHeight = baseHeight + + row.yearScaleHeight * CGFloat(dayCount) / CGFloat(daysInYear) + + countLayout { + if position.isJoinedContinuation { + if row.stacksDayCount { + VStack(alignment: .leading, spacing: row.labelSpacing) { + Text(regionName) + .font(.subheadline.bold()) + Text(dateRange) + .font(.subheadline) + .foregroundStyle(.secondary) + } + .frame(maxWidth: .infinity, alignment: .leading) + } else { + HStack(spacing: planned.joinedLabelSpacing) { + Text(regionName) + .font(.subheadline.bold()) + .layoutPriority(1) + Text(dateRange) + .font(.subheadline) + .foregroundStyle(.secondary) + .lineLimit(1) + } + .frame(maxWidth: .infinity, alignment: .leading) + } + + Text(WhereFormat.dayCount(dayCount)) + .font(.caption.bold()) + .monospacedDigit() + .padding(.horizontal, planned.joinedCountHorizontalPadding) + .padding(.vertical, planned.joinedCountVerticalPadding) + .background { + Capsule() + .fill(tint.opacity(row.countFillOpacity / 2)) + } + } else { + VStack(alignment: .leading, spacing: row.labelSpacing) { + Text(regionName) + .font(.headline) + Text(dateRange) + .font(.subheadline) + .foregroundStyle(.secondary) + Text(String(localized: .timelinePlannedStay)) + .font(.caption) + .foregroundStyle(.secondary) + .opacity(planned.labelOpacity) + } + .frame(maxWidth: .infinity, alignment: .leading) + + Text(WhereFormat.dayCount(dayCount)) + .font(.subheadline.bold()) + .monospacedDigit() + .padding(.horizontal, row.countHorizontalPadding) + .padding(.vertical, row.countVerticalPadding) + .background { + Capsule() + .fill(tint.opacity(row.countFillOpacity / 2)) + } + } + } + .frame(minHeight: proportionalHeight) + .padding(.horizontal, row.horizontalPadding) + .padding( + .vertical, + position.isJoinedContinuation + ? planned.joinedVerticalPadding + : row.verticalPadding, + ) + } +} + +#if DEBUG + #Preview { + VStack(spacing: 0) { + PlannedPresenceJourneyCardContent( + regionName: "New York", + dateRange: "Jul 16 – Aug 15", + dayCount: 31, + daysInYear: 365, + position: .bottom, + tint: .blue, + ) + PlannedPresenceJourneyCardContent( + regionName: "New York", + dateRange: "Jul 16 – Aug 15", + dayCount: 31, + daysInYear: 365, + position: .standalone, + tint: .blue, + ) + } + .padding() + .whereBroadwayRoot() + } +#endif diff --git a/Where/WhereUI/Sources/Primary/PlannedPresenceJourneyRow.swift b/Where/WhereUI/Sources/Primary/PlannedPresenceJourneyRow.swift index a7b3caae2..1e8424357 100644 --- a/Where/WhereUI/Sources/Primary/PlannedPresenceJourneyRow.swift +++ b/Where/WhereUI/Sources/Primary/PlannedPresenceJourneyRow.swift @@ -26,43 +26,18 @@ struct PlannedPresenceJourneyRow: View { end: end, calendar: calendar, ) - let countLayout = row.stacksDayCount - ? AnyLayout(VStackLayout(alignment: .leading, spacing: row.labelSpacing)) - : AnyLayout(HStackLayout(alignment: .center, spacing: row.spacing)) - let proportionalHeight = row.baseHeight - + row.yearScaleHeight * CGFloat(interval.dayCount) / CGFloat(daysInYear) - HStack(spacing: rail.toCardSpacing) { Color.clear .frame(width: rail.nodeSize) - countLayout { - VStack(alignment: .leading, spacing: row.labelSpacing) { - Text(interval.region.localizedName) - .font(.headline) - Text(dateRange) - .font(.subheadline) - .foregroundStyle(.secondary) - Text(String(localized: .timelinePlannedStay)) - .font(.caption) - .foregroundStyle(.secondary) - .opacity(planned.labelOpacity) - } - .frame(maxWidth: .infinity, alignment: .leading) - - Text(WhereFormat.dayCount(interval.dayCount)) - .font(.subheadline.bold()) - .monospacedDigit() - .padding(.horizontal, row.countHorizontalPadding) - .padding(.vertical, row.countVerticalPadding) - .background { - Capsule() - .fill(style.tint.opacity(row.countFillOpacity / 2)) - } - } - .frame(minHeight: proportionalHeight) - .padding(.horizontal, row.horizontalPadding) - .padding(.vertical, row.verticalPadding) + PlannedPresenceJourneyCardContent( + regionName: interval.region.localizedName, + dateRange: dateRange, + dayCount: interval.dayCount, + daysInYear: daysInYear, + position: cardPosition, + tint: style.tint, + ) .background { PlannedPresenceJourneyBackground( position: cardPosition, @@ -100,17 +75,30 @@ struct PlannedPresenceJourneyRow: View { #if DEBUG #Preview { - PlannedPresenceJourneyRow( - interval: LocationForecastModel.PlannedInterval( - region: .newYork, - start: CalendarDay(year: 2026, month: 7, day: 16), - end: CalendarDay(year: 2026, month: 8, day: 15), - ), - calendar: Calendar(identifier: .gregorian), - daysInYear: 365, - isFirst: false, - cardPosition: .standalone, - ) + VStack(spacing: 0) { + PlannedPresenceJourneyRow( + interval: LocationForecastModel.PlannedInterval( + region: .newYork, + start: CalendarDay(year: 2026, month: 7, day: 16), + end: CalendarDay(year: 2026, month: 8, day: 15), + ), + calendar: Calendar(identifier: .gregorian), + daysInYear: 365, + isFirst: false, + cardPosition: .standalone, + ) + PlannedPresenceJourneyRow( + interval: LocationForecastModel.PlannedInterval( + region: .newYork, + start: CalendarDay(year: 2026, month: 7, day: 16), + end: CalendarDay(year: 2026, month: 7, day: 24), + ), + calendar: Calendar(identifier: .gregorian), + daysInYear: 365, + isFirst: false, + cardPosition: .bottom, + ) + } .padding() .whereBroadwayRoot() } diff --git a/Where/WhereUI/Sources/Primary/PresenceJourneyCardPosition.swift b/Where/WhereUI/Sources/Primary/PresenceJourneyCardPosition.swift index fe52a16db..886416397 100644 --- a/Where/WhereUI/Sources/Primary/PresenceJourneyCardPosition.swift +++ b/Where/WhereUI/Sources/Primary/PresenceJourneyCardPosition.swift @@ -37,7 +37,7 @@ enum PresenceJourneyCardPosition { } } - var fadesFromRecorded: Bool { + var isJoinedContinuation: Bool { switch self { case .standalone, .top: false diff --git a/Where/WhereUI/Sources/Primary/PresenceTimelineList.swift b/Where/WhereUI/Sources/Primary/PresenceTimelineList.swift index 4cb3fa8ec..990b5daa1 100644 --- a/Where/WhereUI/Sources/Primary/PresenceTimelineList.swift +++ b/Where/WhereUI/Sources/Primary/PresenceTimelineList.swift @@ -162,6 +162,17 @@ struct PresenceTimelineList: View { PresenceTimelineList(report: PreviewSupport.plannedStayYearReportModel()) } }, + whereSnapshot( + name: "ShortPlannedStay", + configurations: .fullContentPhoneLightDark, + measurementReadiness: .immediate, + ) { + NavigationStack { + PresenceTimelineList(report: PreviewSupport.plannedStayYearReportModel( + plannedThroughDay: CalendarDay(year: 2026, month: 7, day: 24), + )) + } + }, whereSnapshot( name: "PlannedStayHidden", configurations: .fullContentPhoneLightDark, diff --git a/Where/WhereUI/Sources/Shared/WhereStylesheet.swift b/Where/WhereUI/Sources/Shared/WhereStylesheet.swift index 00515a32b..b6e674fd8 100644 --- a/Where/WhereUI/Sources/Shared/WhereStylesheet.swift +++ b/Where/WhereUI/Sources/Shared/WhereStylesheet.swift @@ -1528,6 +1528,11 @@ extension WhereStylesheet { var hatchLineWidth: CGFloat var labelOpacity: Double var transitionHeight: CGFloat + var joinedBaseHeight: CGFloat + var joinedVerticalPadding: CGFloat + var joinedLabelSpacing: CGFloat + var joinedCountHorizontalPadding: CGFloat + var joinedCountVerticalPadding: CGFloat } static let standard = TimelineStyle( @@ -1584,6 +1589,11 @@ extension WhereStylesheet { hatchLineWidth: 1, labelOpacity: 0.7, transitionHeight: 16, + joinedBaseHeight: 32, + joinedVerticalPadding: 8, + joinedLabelSpacing: 5, + joinedCountHorizontalPadding: 8, + joinedCountVerticalPadding: 4, ), ) } diff --git a/Where/WhereUI/Tests/WhereStylesheetTests.swift b/Where/WhereUI/Tests/WhereStylesheetTests.swift index d89996545..a7d75d2be 100644 --- a/Where/WhereUI/Tests/WhereStylesheetTests.swift +++ b/Where/WhereUI/Tests/WhereStylesheetTests.swift @@ -444,6 +444,11 @@ struct WhereStylesheetTests { #expect(planned.hatchLineWidth == 1) #expect(planned.labelOpacity == 0.7) #expect(planned.transitionHeight == 16) + #expect(planned.joinedBaseHeight == 32) + #expect(planned.joinedVerticalPadding == 8) + #expect(planned.joinedLabelSpacing == 5) + #expect(planned.joinedCountHorizontalPadding == 8) + #expect(planned.joinedCountVerticalPadding == 4) } @Test func regionMapStyle() { From 99ab916f07d6bd21cac3ff5037518311859c85fe Mon Sep 17 00:00:00 2001 From: Kyle Van Essen Date: Fri, 4 Sep 2026 16:57:59 -0700 Subject: [PATCH 5/5] Show estimated time below Timeline --- ...ntiateWithoutColorInitialBottom_iPhone.png | 4 +- ...eWithoutColorInitialBottom_iPhone_dark.png | 4 +- ...eline.DifferentiateWithoutColor_iPhone.png | 4 +- ....DifferentiateWithoutColor_iPhone_dark.png | 4 +- .../presenceTimeline.InitialBottom_iPad.png | 4 +- ...eline.InitialBottom_iPad_accessibility.png | 4 +- ...resenceTimeline.InitialBottom_iPad_ax5.png | 4 +- ...ceTimeline.InitialBottom_iPad_contrast.png | 4 +- ...esenceTimeline.InitialBottom_iPad_dark.png | 4 +- .../presenceTimeline.InitialBottom_iPhone.png | 4 +- ...ine.InitialBottom_iPhone_accessibility.png | 4 +- ...senceTimeline.InitialBottom_iPhone_ax5.png | 4 +- ...Timeline.InitialBottom_iPhone_contrast.png | 4 +- ...enceTimeline.InitialBottom_iPhone_dark.png | 4 +- ...ne.PlannedStayAfterRecordingGap_iPhone.png | 4 +- ...annedStayAfterRecordingGap_iPhone_dark.png | 4 +- ...line.PlannedStayDifferentRegion_iPhone.png | 4 +- ...PlannedStayDifferentRegion_iPhone_dark.png | 4 +- .../presenceTimeline.PlannedStay_iPhone.png | 4 +- ...eline.PlannedStay_iPhone_accessibility.png | 4 +- ...esenceTimeline.PlannedStay_iPhone_dark.png | 4 +- ...esenceTimeline.ShortPlannedStay_iPhone.png | 4 +- ...eTimeline.ShortPlannedStay_iPhone_dark.png | 4 +- .../presenceTimeline.WithData_iPad.png | 4 +- ...ceTimeline.WithData_iPad_accessibility.png | 4 +- .../presenceTimeline.WithData_iPad_ax5.png | 4 +- ...resenceTimeline.WithData_iPad_contrast.png | 4 +- .../presenceTimeline.WithData_iPad_dark.png | 4 +- .../presenceTimeline.WithData_iPhone.png | 4 +- ...Timeline.WithData_iPhone_accessibility.png | 4 +- .../presenceTimeline.WithData_iPhone_ax5.png | 4 +- ...senceTimeline.WithData_iPhone_contrast.png | 4 +- .../presenceTimeline.WithData_iPhone_dark.png | 4 +- .../Primary/PresenceTimelineList.swift | 159 +++++++++++------- 34 files changed, 167 insertions(+), 124 deletions(-) diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.DifferentiateWithoutColorInitialBottom_iPhone.png b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.DifferentiateWithoutColorInitialBottom_iPhone.png index 06a9f76b9..8619751ad 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.DifferentiateWithoutColorInitialBottom_iPhone.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.DifferentiateWithoutColorInitialBottom_iPhone.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:d39ed2caa6762fe0942b037798074e4f9a53f1f52eb694633f4e82f1cc1f0210 -size 399590 +oid sha256:fd7b9da0fb992c7730eba5667063e74d55d273da28aaf4e3963477993741cd1f +size 1384837 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.DifferentiateWithoutColorInitialBottom_iPhone_dark.png b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.DifferentiateWithoutColorInitialBottom_iPhone_dark.png index bd1b3b989..036c15b72 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.DifferentiateWithoutColorInitialBottom_iPhone_dark.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.DifferentiateWithoutColorInitialBottom_iPhone_dark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6b2eebf9ed7bce1ff183885b096021f4a66f5761964b30ddf3b404ffd2f18ab7 -size 374555 +oid sha256:f54009022eefc4d91491cba2f9a4c9cfea4c690ad164e296f021bb7c24ab67e0 +size 798090 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.DifferentiateWithoutColor_iPhone.png b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.DifferentiateWithoutColor_iPhone.png index 305039a70..72bbdd9bc 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.DifferentiateWithoutColor_iPhone.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.DifferentiateWithoutColor_iPhone.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:517afb07d387c965478225bd3683f53a54bc54822951f8da825bbbbb4e6b27b2 -size 346943 +oid sha256:247ae89fe129fc1c9d7b46b03a211cb83ffa370436388845ef64ca1024640ace +size 1406763 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.DifferentiateWithoutColor_iPhone_dark.png b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.DifferentiateWithoutColor_iPhone_dark.png index 955f3863b..6396c258d 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.DifferentiateWithoutColor_iPhone_dark.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.DifferentiateWithoutColor_iPhone_dark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5d96311b839ae7c7dc5f4c2dac48a107548972fd098f47dcd3b295703173446a -size 348712 +oid sha256:3498a5f67d66b48d6bce2d1041faa0b2283908a240cf440b598fd44d0283cb5a +size 861573 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.InitialBottom_iPad.png b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.InitialBottom_iPad.png index 7f55da383..31bde52a7 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.InitialBottom_iPad.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.InitialBottom_iPad.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:20272851632b4dfaa4b5d11604028f6a3dfbe7a1526357fc130617951fe1345b -size 402883 +oid sha256:48f09d340616476f7d89b06b90f10148d600eab69196a9b001220ba1dfd54440 +size 2543177 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.InitialBottom_iPad_accessibility.png b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.InitialBottom_iPad_accessibility.png index f02f22da9..da8819ebc 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.InitialBottom_iPad_accessibility.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.InitialBottom_iPad_accessibility.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2bd088c1545bb52bdc03d7ca9d89c9bde5a78c830f3f2768213d43c97a1bd619 -size 562991 +oid sha256:6721345fd31a423e1bb1bc445676e98654eecf3826ee17d6bf735fcb23deec20 +size 2489637 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.InitialBottom_iPad_ax5.png b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.InitialBottom_iPad_ax5.png index 3523875fe..1c468d6ff 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.InitialBottom_iPad_ax5.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.InitialBottom_iPad_ax5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:6a6c5706ad0f1c1952e456c668dcf9f39a6ebaee0015d2bacd88eb652f74b4a5 -size 939746 +oid sha256:b63f40c719062eb87532e0e9d66e4c996b6f7adc3f6dadb16768a2f1dc00d84a +size 4807580 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.InitialBottom_iPad_contrast.png b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.InitialBottom_iPad_contrast.png index 6c7dcc91d..10bec6141 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.InitialBottom_iPad_contrast.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.InitialBottom_iPad_contrast.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1c5572f2ae67984cbf853ce869d88df5535786a6b9c20989d93aea6ad027d08a -size 406490 +oid sha256:7bd4705a23ae4abd762b304c9f85c3719b053518d0a080ecaa5950ae9c6c7775 +size 2574913 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.InitialBottom_iPad_dark.png b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.InitialBottom_iPad_dark.png index bbb71c54d..f96768d36 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.InitialBottom_iPad_dark.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.InitialBottom_iPad_dark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:067cd0279c28d20cb9f2463ef1efe11d16bc6ba2fada5ea8c88cfb6df0176b27 -size 409025 +oid sha256:a931ad65526703cdc5ef297b635b099752a7ba75e6953fa71d29a2a09616bfbf +size 1501243 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.InitialBottom_iPhone.png b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.InitialBottom_iPhone.png index 9dc44221b..a58266d38 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.InitialBottom_iPhone.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.InitialBottom_iPhone.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4f631b9315c6dba94fabd35a1efe543bd0ccfd376e2e3bc435e9e0306064af21 -size 399905 +oid sha256:d0f3b22e2d4aa4d5649952dd0477b5d60282bfcee514e25310d6a2c66541b7c8 +size 1384692 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.InitialBottom_iPhone_accessibility.png b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.InitialBottom_iPhone_accessibility.png index 75527e7c3..aee922348 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.InitialBottom_iPhone_accessibility.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.InitialBottom_iPhone_accessibility.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:98b0cd455331a3f02076d9deb91c632ae8a781b0772171738bef0884568fed7f -size 417005 +oid sha256:26c38a62ed489965a2293401536336b0dbf530f05fc879efc52ce9cd6bc73d6e +size 1390052 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.InitialBottom_iPhone_ax5.png b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.InitialBottom_iPhone_ax5.png index c1ad4f0b8..d60256d24 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.InitialBottom_iPhone_ax5.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.InitialBottom_iPhone_ax5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:88e8afa38895b9c45fee312ba62ac94c678e3c0c9a25cbdef0814f57dec209cf -size 551604 +oid sha256:78db2fb145e8b1f8e6eede3aff92e464dfbf0980579558df6b7f4947fb0bf85f +size 2236104 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.InitialBottom_iPhone_contrast.png b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.InitialBottom_iPhone_contrast.png index 18b970896..8ece7c4e2 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.InitialBottom_iPhone_contrast.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.InitialBottom_iPhone_contrast.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:dce28bee69c834257064c4261eb41c92572f45d5c2fe7a61579c15436b8249e5 -size 413613 +oid sha256:6fdcff2d2464debadf896845101014e4e13d4cc99a437454940542ee82f45a1e +size 1375655 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.InitialBottom_iPhone_dark.png b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.InitialBottom_iPhone_dark.png index cc9e668e5..5f6912681 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.InitialBottom_iPhone_dark.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.InitialBottom_iPhone_dark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c0f6663a48f7672cdbc03d293383a4f51ec2da38cdc3ccd9f775af175e9f1c72 -size 373395 +oid sha256:fa46c564ab87a53ad2441bd69ebaf6d9d2114ae235806e358d9d7253a979e972 +size 793500 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStayAfterRecordingGap_iPhone.png b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStayAfterRecordingGap_iPhone.png index acacece6b..ade00f1fd 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStayAfterRecordingGap_iPhone.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStayAfterRecordingGap_iPhone.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:c55303771d25079f965e744bb7d21f3e84fa06fed865a27f74b93e076efb5423 -size 217572 +oid sha256:2493064b98771b3086d329c329c1042f57711dfeb163a2aa632010d53e31a79f +size 1343431 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStayAfterRecordingGap_iPhone_dark.png b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStayAfterRecordingGap_iPhone_dark.png index be755e9a3..2f8fadbcc 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStayAfterRecordingGap_iPhone_dark.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStayAfterRecordingGap_iPhone_dark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:7bd573f565dc870df6c296b2a9703b577a3d6bd25e6e913df65b0f83d1ed4758 -size 224168 +oid sha256:3df384db63f680c999c99e8bf38a25e1db59867adc8d090e5f66d50767708135 +size 764302 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStayDifferentRegion_iPhone.png b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStayDifferentRegion_iPhone.png index 5155207ec..bda28e4ec 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStayDifferentRegion_iPhone.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStayDifferentRegion_iPhone.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1a942accfd3fcef5c76b89b4c1bd4dc92b6d7515da8648e1f4a3019ec310c429 -size 221505 +oid sha256:2b1501b03e054e05936523a93ec99aeefcbb1a29e3fb3d9ab54b5d6cd4be370b +size 1344751 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStayDifferentRegion_iPhone_dark.png b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStayDifferentRegion_iPhone_dark.png index ee44efbd3..754ba10f1 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStayDifferentRegion_iPhone_dark.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStayDifferentRegion_iPhone_dark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:f64cfea81569f945b537467a0afea4f83ba4b034c6bb699e14c9fdf35abeb96c -size 225708 +oid sha256:5d2651bc1358dc5ea353f3b69bd315c5b3d081e41e4338188f676d15283c9f76 +size 765255 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone.png b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone.png index 0c0b15e34..585df8afb 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0a932f25197ac1eddaf62554d834b4a656566812840dbd84eef011dbc4a7c38d -size 205865 +oid sha256:fec43f012b275635fc4f26c8d3c907a2e33f26b4ce094797c5683315e1c1d08b +size 1329722 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone_accessibility.png b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone_accessibility.png index 20fc4f87a..b79d1a5a0 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone_accessibility.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone_accessibility.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:30f88ab05efab31e8f854a16b7f03eec357349176192e8c3f24e9ce189a5950d -size 323572 +oid sha256:63befcc07248e9cbe21ff14a6f7c9faf46caaf45e65524c2d391d43fdb656792 +size 1494377 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone_dark.png b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone_dark.png index d2db0bb5f..4be1913da 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone_dark.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.PlannedStay_iPhone_dark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:9b4cabcefcc50c1b474672b35b76ee5f916aca79b5ab09dceeb9ed00cafd7372 -size 209011 +oid sha256:afc558716f8618dd5164fc32a696a72a09ee397f7a08787f2de1cb21ad29bf0e +size 745801 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.ShortPlannedStay_iPhone.png b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.ShortPlannedStay_iPhone.png index 56c6ec7dc..a5bd0fe82 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.ShortPlannedStay_iPhone.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.ShortPlannedStay_iPhone.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:4a6c35c78d06d90afb52fe052b2dae68c302012cfd7d122361e4c1dd08e0e775 -size 201954 +oid sha256:a6c4460a692e09fae7b44029c68547e2041bac6d93299509e63ea91881c1afbe +size 1323628 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.ShortPlannedStay_iPhone_dark.png b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.ShortPlannedStay_iPhone_dark.png index 7b846a30d..21621db35 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.ShortPlannedStay_iPhone_dark.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.ShortPlannedStay_iPhone_dark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:17de7f7c92f21776bc522a7e547cb7b08d3b928c34674d2971aa7517e8223a22 -size 205045 +oid sha256:e71c00bc0004317a6f00973bd012766fad1f077ded071d98e18b3e459f2e4e5f +size 739856 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.WithData_iPad.png b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.WithData_iPad.png index 7f55da383..05703f4fb 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.WithData_iPad.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.WithData_iPad.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:20272851632b4dfaa4b5d11604028f6a3dfbe7a1526357fc130617951fe1345b -size 402883 +oid sha256:e504eaba70b73cbd04eb7aad5579b8f9c44fe4b7b0461737479e0f068fd4912f +size 2420814 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.WithData_iPad_accessibility.png b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.WithData_iPad_accessibility.png index f02f22da9..1be4bc317 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.WithData_iPad_accessibility.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.WithData_iPad_accessibility.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2bd088c1545bb52bdc03d7ca9d89c9bde5a78c830f3f2768213d43c97a1bd619 -size 562991 +oid sha256:40189401720d51588cdc29c64443fd08e260810b19f220012fdfcca5d6d15528 +size 2499854 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.WithData_iPad_ax5.png b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.WithData_iPad_ax5.png index 63a16c412..38ce8e5e1 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.WithData_iPad_ax5.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.WithData_iPad_ax5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:0d18e4cae3737e79181d430acab79602714cab862cfcae24774e13df0c51c83a -size 650767 +oid sha256:c2b95cb7ea18e8a57617884c953cd44c4e71f00d65c2d03515e9e9baed2df5b5 +size 5012922 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.WithData_iPad_contrast.png b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.WithData_iPad_contrast.png index 6c7dcc91d..207daa03f 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.WithData_iPad_contrast.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.WithData_iPad_contrast.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:1c5572f2ae67984cbf853ce869d88df5535786a6b9c20989d93aea6ad027d08a -size 406490 +oid sha256:3b7e1b237dcc857d65a2b935e50717cce418cf226cb8aa5fa0ea91aeb826197a +size 2434370 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.WithData_iPad_dark.png b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.WithData_iPad_dark.png index bbb71c54d..4fb7f197b 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.WithData_iPad_dark.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.WithData_iPad_dark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:067cd0279c28d20cb9f2463ef1efe11d16bc6ba2fada5ea8c88cfb6df0176b27 -size 409025 +oid sha256:1855d1e49337483170ed6b1dcec03d3983b9834815183f573b423af0ae7d9476 +size 1405924 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.WithData_iPhone.png b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.WithData_iPhone.png index 07a82dee1..40e9a9215 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.WithData_iPhone.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.WithData_iPhone.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:5034d3b60d0e641df3da6db6f6a6961cf3a465f5b5d764d65ef343e04a959c31 -size 279459 +oid sha256:ccb1f03fc4ce00e97f0d4c1769412df30322817118dde1ea12f9fb5f935039d6 +size 1340753 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.WithData_iPhone_accessibility.png b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.WithData_iPhone_accessibility.png index 8d8c2c9fc..ec255a0c2 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.WithData_iPhone_accessibility.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.WithData_iPhone_accessibility.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:2ae20b26fe04dea8a1285e2ec3b80f14efea6ce3fd65fd1cc880d0fd82c2f691 -size 420968 +oid sha256:4c6aa4672bfc2610650d5a69881a75c9904b240b411d40efd780f472dc1f6e3d +size 1508959 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.WithData_iPhone_ax5.png b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.WithData_iPhone_ax5.png index 3776ace08..0d0c88830 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.WithData_iPhone_ax5.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.WithData_iPhone_ax5.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:95282e816c5b79ef0bffdf2e9feba50bf29ffadf014013bcb25884b082f72819 -size 565666 +oid sha256:dbff7c2bb9e2b59294bcbc95ba6c66944174b195bf05daaf41cbfc3dadbcf75b +size 3309171 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.WithData_iPhone_contrast.png b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.WithData_iPhone_contrast.png index fda3ddd77..a723c72b2 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.WithData_iPhone_contrast.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.WithData_iPhone_contrast.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:3d4e09a622c766f7bc0de77cdf118374c405dbd9a74978ad626a73691895c285 -size 284158 +oid sha256:9544d375d67c9dd6f4701a4fdaa45450e82d12aea963b3665317fb383dd77847 +size 1350163 diff --git a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.WithData_iPhone_dark.png b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.WithData_iPhone_dark.png index 17e98a193..4eb0e8ef6 100644 --- a/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.WithData_iPhone_dark.png +++ b/Where/WhereUI/SnapshotTests/__Snapshots__/PresenceTimelineListSnapshotTests/presenceTimeline.WithData_iPhone_dark.png @@ -1,3 +1,3 @@ version https://git-lfs.github.com/spec/v1 -oid sha256:88e2657da02f2f47465d3abe71c61f1d096a9a3817fae750ab7eb32ade302019 -size 282703 +oid sha256:b10f1d8f07fc60b3f6f106d7e418b11bc6ebf494d7c9260be60d0faa69fdcebe +size 796709 diff --git a/Where/WhereUI/Sources/Primary/PresenceTimelineList.swift b/Where/WhereUI/Sources/Primary/PresenceTimelineList.swift index 990b5daa1..d2941c584 100644 --- a/Where/WhereUI/Sources/Primary/PresenceTimelineList.swift +++ b/Where/WhereUI/Sources/Primary/PresenceTimelineList.swift @@ -1,3 +1,4 @@ +import RegionKit import SFSafeSymbols import SnapshotKit import SwiftUI @@ -15,6 +16,15 @@ struct PresenceTimelineList: View { let report: YearReportModel @Environment(\.stylesheet) private var stylesheet + @State private var plannedStayEditorTarget: PlannedStayEditorTarget? + + private struct PlannedStayEditorTarget: Identifiable { + let region: Region + + var id: Region { + region + } + } var body: some View { let yearReport = report.report @@ -30,76 +40,109 @@ struct PresenceTimelineList: View { false } - if stints.isEmpty, plannedInterval == nil { - ContentUnavailableView { - Label( - String(localized: .timelineEmptyTitle), - systemSymbol: .calendarDayTimelineLeft, - ) - } description: { - Text(String(localized: .timelineEmptyDescription)) - } - } else { - let pinsOverview = stylesheet.timeline.overview.pinsToViewport - - ScrollView { - LazyVStack(spacing: pinsOverview ? 0 : stylesheet.spacing.large) { - if !pinsOverview { - YearRibbon( - days: yearReport?.days ?? [], - year: report.selectedYear, - calendar: report.calendar, - ) - } + Group { + if stints.isEmpty, plannedInterval == nil { + ContentUnavailableView { + Label( + String(localized: .timelineEmptyTitle), + systemSymbol: .calendarDayTimelineLeft, + ) + } description: { + Text(String(localized: .timelineEmptyDescription)) + } + } else { + let pinsOverview = stylesheet.timeline.overview.pinsToViewport - LazyVStack(spacing: 0) { - ForEach(stints.enumerated(), id: \.element.id) { index, stint in - PresenceJourneyRow( - stint: stint, + ScrollView { + LazyVStack(spacing: stylesheet.spacing.large) { + if !pinsOverview { + YearRibbon( + days: yearReport?.days ?? [], + year: report.selectedYear, calendar: report.calendar, - daysInYear: report.daysInSelectedYear, - isFirst: index == stints.startIndex, - isLast: plannedInterval == nil - && index == stints.index(before: stints.endIndex), - cardPosition: joinsPlannedStay - && index == stints.index(before: stints.endIndex) - ? .top - : .standalone, ) } - if let plannedInterval { - PlannedPresenceJourneyRow( - interval: plannedInterval, - calendar: report.calendar, - daysInYear: report.daysInSelectedYear, - isFirst: stints.isEmpty, - cardPosition: joinsPlannedStay ? .bottom : .standalone, + LazyVStack(spacing: 0) { + ForEach(stints.enumerated(), id: \.element.id) { index, stint in + PresenceJourneyRow( + stint: stint, + calendar: report.calendar, + daysInYear: report.daysInSelectedYear, + isFirst: index == stints.startIndex, + isLast: plannedInterval == nil + && index == stints.index(before: stints.endIndex), + cardPosition: joinsPlannedStay + && index == stints.index(before: stints.endIndex) + ? .top + : .standalone, + ) + } + + if let plannedInterval { + PlannedPresenceJourneyRow( + interval: plannedInterval, + calendar: report.calendar, + daysInYear: report.daysInSelectedYear, + isFirst: stints.isEmpty, + cardPosition: joinsPlannedStay ? .bottom : .standalone, + ) + } + } + + if showsForecast { + LocationForecastPanel( + forecasts: timelineForecasts, + microprintRegions: report.ranking.primary.map(\.region), + plannedStay: report.forecasts.activePlannedStay, + editableRegions: report.ranking.primary.map(\.region), + editAction: { region in + plannedStayEditorTarget = + PlannedStayEditorTarget(region: region) + }, + clearAction: report.forecasts.clear, ) } } - } - .padding(.horizontal, stylesheet.spacing.xxLarge) - .padding(.top, pinsOverview ? 0 : stylesheet.spacing.large) - .padding(.bottom, stylesheet.spacing.large) - } - .safeAreaInset( - edge: .top, - spacing: 0, - ) { - if pinsOverview { - YearRibbon( - days: yearReport?.days ?? [], - year: report.selectedYear, - calendar: report.calendar, - ) .padding(.horizontal, stylesheet.spacing.xxLarge) - .padding(.top, stylesheet.spacing.large) + .padding(.top, pinsOverview ? 0 : stylesheet.spacing.large) .padding(.bottom, stylesheet.spacing.large) } + .safeAreaInset( + edge: .top, + spacing: 0, + ) { + if pinsOverview { + YearRibbon( + days: yearReport?.days ?? [], + year: report.selectedYear, + calendar: report.calendar, + ) + .padding(.horizontal, stylesheet.spacing.xxLarge) + .padding(.top, stylesheet.spacing.large) + .padding(.bottom, stylesheet.spacing.large) + } + } + .defaultScrollAnchor(.bottom, for: .initialOffset) + .id(report.selectedYear) } - .defaultScrollAnchor(.bottom, for: .initialOffset) - .id(report.selectedYear) + } + .sheet(item: $plannedStayEditorTarget) { target in + PlannedStayEditor( + region: target.region, + model: report.forecasts, + driftThreshold: report.driftThreshold, + ) + } + } + + private var showsForecast: Bool { + report.showsEstimatedTimeAndPlanning && !timelineForecasts.isEmpty + } + + private var timelineForecasts: [LocationForecast] { + report.ranking.primary.compactMap { + report.forecasts.forecast(for: $0.region, report: report.report) } } }