From 9342ccc7c946eff9ee0558a1b360619bba06fbbe Mon Sep 17 00:00:00 2001 From: FuelBoard Contributor Date: Thu, 20 Aug 2026 18:49:54 +0100 Subject: [PATCH] trends: redraw lines on 7/30/90 selection Callers previously morphed the existing lines on a reload, so picking a new range only glided in place. Now every range/fuel selection (the only reloads; the sheet has no periodic refetch) replays the staggered per-series draw-in via revealSeries(), so the lines visibly redraw left->right when the user switches 7/30/90. Price<->vs-cheapest still morphs (pure view toggle, no reload). Reduce Motion jump-cuts. --- FuelBoard/TrendsView.swift | 71 ++++++++++++++++++-------------------- 1 file changed, 33 insertions(+), 38 deletions(-) diff --git a/FuelBoard/TrendsView.swift b/FuelBoard/TrendsView.swift index b91fee1..33e867e 100644 --- a/FuelBoard/TrendsView.swift +++ b/FuelBoard/TrendsView.swift @@ -165,44 +165,13 @@ struct TrendsView: View { // pointer probe already done above. loadFailed = firstSnapshot == nil } - // Morph vs trace-in: a reload (range/fuel switch) glides the existing - // lines to the new data; the first real draw traces each line in - // left→right. Reduce Motion jumps straight to the final state. - let hadData = hasAnyData - if hadData { - withAnimation(reduceMotion ? nil : .easeInOut(duration: 0.35)) { - series = fetched - } - // Newly-added favourites have no reveal entry yet — surface them - // fully so their line isn't left blank by the draw-in waterline. - for h in fetched where revealed[h.stationID] == nil { - revealed[h.stationID] = h.points.count - } - } else { - series = fetched - let hasPoints = fetched.contains { !$0.points.isEmpty } - if hasPoints { - if reduceMotion { - var all: [String: Int] = [:] - for h in fetched { all[h.stationID] = h.points.count } - revealed = all - } else { - revealed = [:] - let cascadeNS = UInt64(0.18 * 1_000_000_000) - for (i, h) in fetched.enumerated() { - let sid = h.stationID - let total = h.points.count - Task { - try? await Task.sleep(nanoseconds: UInt64(i) * cascadeNS) - guard !Task.isCancelled else { return } - withAnimation(.easeOut(duration: 0.5)) { - revealed[sid] = total - } - } - } - } - } - } + // Always redraw the lines on a selection change. `.task(id:)` fires on + // the initial appear and on every fuel/range switch (there's no + // periodic refetch in this sheet), so a staggered per-series draw-in + // replays exactly when the user picks 7/30/90 (or switches fuel) while + // a width-stable morph keeps the x-axis/y-range from jumping abruptly. + series = fetched + revealSeries(fetched) // A failure with no data IS a connection problem — raise the global // banner so the user isn't stuck with a silent retry state. Success // clears it (only if the banner is the connection banner). @@ -213,6 +182,32 @@ struct TrendsView: View { } } + /// Replays the staggered per-series draw-in for the given histories: + /// resets the waterline, then cascades each station's line left→right, + /// 0.18s apart, on every range/fuel selection. Reduce Motion jump-cuts + /// straight to the full state. + private func revealSeries(_ histories: [StationHistory]) { + let hasPoints = histories.contains { !$0.points.isEmpty } + guard hasPoints else { return } + if reduceMotion { + revealed = histories.reduce(into: [:]) { $0[$1.stationID] = $1.points.count } + return + } + revealed = [:] + let cascadeNS = UInt64(0.18 * 1_000_000_000) + for (i, h) in histories.enumerated() { + let sid = h.stationID + let total = h.points.count + Task { + try? await Task.sleep(nanoseconds: UInt64(i) * cascadeNS) + guard !Task.isCancelled else { return } + withAnimation(.easeOut(duration: 0.5)) { + revealed[sid] = total + } + } + } + } + private func yLabel(_ pence: Double) -> String { switch mode { case .price: