diff --git a/Sources/BarUI/ContentView.swift b/Sources/BarUI/ContentView.swift index 0637650..db5c276 100644 --- a/Sources/BarUI/ContentView.swift +++ b/Sources/BarUI/ContentView.swift @@ -126,7 +126,9 @@ struct ContentView: View { OverviewView(overview: archive.overview, archiveFailure: archive.failure) case .barApp(let app): BarAppPageView(app: app, status: monitor.status(of: app), home: Self.home, remote: remote, skipped: archive.overview?.skippedRecords[app], history: archive.history, overview: archive.overview) - case .archive: + case .archive(.events): + ResetsAndPerksPageView(overview: archive.overview, archiveFailure: archive.failure) + case .archive(.imports): PlaceholderPageView(item: item) } } diff --git a/Sources/BarUI/OverviewView.swift b/Sources/BarUI/OverviewView.swift index e8c25bc..6f91eb5 100644 --- a/Sources/BarUI/OverviewView.swift +++ b/Sources/BarUI/OverviewView.swift @@ -71,7 +71,7 @@ struct OverviewView: View { } } -private struct ProblemLabel: View { +struct ProblemLabel: View { let problem: String var body: some View { diff --git a/Sources/BarUI/ResetsAndPerksPageView.swift b/Sources/BarUI/ResetsAndPerksPageView.swift new file mode 100644 index 0000000..25d8aac --- /dev/null +++ b/Sources/BarUI/ResetsAndPerksPageView.swift @@ -0,0 +1,56 @@ +import BarUICore +import SwiftUI + +/// Every reset, Perk change and Tracking change in the Archive. The Overview +/// shows the same section for its range; this page opens on All time so +/// nothing older is out of reach. +struct ResetsAndPerksPageView: View { + /// Nil when the Archive couldn't be opened; `archiveFailure` says why. + let overview: LimitsOverview? + let archiveFailure: String? + + @State private var range: TimeRange = .all + /// Who this view is to the overview, which reads as far back as the + /// longest range any view shows. + @State private var viewer = UUID() + + var body: some View { + Group { + if let overview { + TimelineView(.periodic(from: .now, by: 30)) { context in + ScrollView { + VStack(alignment: .leading, spacing: 20) { + // A failed read keeps the last events, so without + // this a short history would pass for the whole Archive. + if let problem = overview.problem { + ProblemLabel(problem: problem) + } + ResetsAndPerksSection( + events: overview.events(range: range, now: context.date), + range: range, + now: context.date, + tokensDomain: nil, + rangeSelection: $range + ) + } + .padding(.horizontal, 32) + .padding(.vertical, 16) + } + } + .task(id: range) { + await overview.show(range, by: viewer) + } + .onDisappear { + overview.stopShowing(by: viewer) + } + } else { + ContentUnavailableView( + "The Archive couldn't be opened", + systemImage: "externaldrive.badge.exclamationmark", + description: Text(archiveFailure ?? "") + ) + } + } + .navigationTitle(ArchivePage.events.title) + } +} diff --git a/Sources/BarUI/ResetsAndPerksSection.swift b/Sources/BarUI/ResetsAndPerksSection.swift index 44242b0..1527873 100644 --- a/Sources/BarUI/ResetsAndPerksSection.swift +++ b/Sources/BarUI/ResetsAndPerksSection.swift @@ -10,6 +10,9 @@ struct ResetsAndPerksSection: View { let now: Date /// The Tokens chart's span, so this chart's days sit under the same days. let tokensDomain: ClosedRange? + /// The page's own range control; nil on the Overview, where the Tokens + /// chart's control drives both sections. + var rangeSelection: Binding? /// Off by default: a 5-hour session resets several times a day, and those /// rows would bury the weekly resets and everything else. @@ -27,6 +30,16 @@ struct ResetsAndPerksSection: View { Text("Resets & Perks") .font(.headline) Spacer() + if let rangeSelection { + Picker("Range", selection: rangeSelection) { + ForEach(TimeRange.allCases) { range in + Text(range.label).tag(range) + } + } + .pickerStyle(.segmented) + .labelsHidden() + .fixedSize() + } Toggle("Show session resets", isOn: $showSessionResets) .toggleStyle(.switch) .controlSize(.small)