watch: polish refresh status and compact live activity

This commit is contained in:
FuelBoard Contributor
2026-08-31 10:40:16 +01:00
parent 767080a983
commit 8bbcb0b4dc
2 changed files with 27 additions and 9 deletions
+16 -3
View File
@@ -112,6 +112,7 @@ private enum WatchRefreshState {
case stale(lastUpdated: String) case stale(lastUpdated: String)
case checking case checking
case requested(lastUpdated: String) case requested(lastUpdated: String)
case checkedNoNewData(lastUpdated: String)
case needsPhone case needsPhone
case noData case noData
@@ -120,7 +121,8 @@ private enum WatchRefreshState {
case .idle(let lastUpdated): return "Updated \(lastUpdated)" case .idle(let lastUpdated): return "Updated \(lastUpdated)"
case .stale(let lastUpdated): return "Cache from \(lastUpdated)" case .stale(let lastUpdated): return "Cache from \(lastUpdated)"
case .checking: return "Checking…" case .checking: return "Checking…"
case .requested(let lastUpdated): return "Requested · \(lastUpdated)" case .requested(let lastUpdated): return "Updated · \(lastUpdated)"
case .checkedNoNewData(let lastUpdated): return "No newer data · \(lastUpdated)"
case .needsPhone: return "Refresh on iPhone" case .needsPhone: return "Refresh on iPhone"
case .noData: return "No cached prices yet" case .noData: return "No cached prices yet"
} }
@@ -132,6 +134,7 @@ private enum WatchRefreshState {
case .stale: return .yellow case .stale: return .yellow
case .checking: return .yellow case .checking: return .yellow
case .requested: return .green case .requested: return .green
case .checkedNoNewData: return .secondary
case .needsPhone: return .orange case .needsPhone: return .orange
case .noData: return .secondary case .noData: return .secondary
} }
@@ -143,6 +146,7 @@ private enum WatchRefreshState {
case .stale: return "exclamationmark.circle" case .stale: return "exclamationmark.circle"
case .checking: return "arrow.triangle.2.circlepath" case .checking: return "arrow.triangle.2.circlepath"
case .requested: return "checkmark.circle" case .requested: return "checkmark.circle"
case .checkedNoNewData: return "clock.badge.exclamationmark"
case .needsPhone: return "iphone" case .needsPhone: return "iphone"
case .noData: return "tray" case .noData: return "tray"
} }
@@ -551,6 +555,7 @@ struct ContentView: View {
private func runCheckNow() { private func runCheckNow() {
refreshState = .checking refreshState = .checking
let previousLastRefresh = WatchFuelCache.loadLastRefresh()
let requestDate = Date() let requestDate = Date()
WatchFuelCache.requestRefresh(requestDate) WatchFuelCache.requestRefresh(requestDate)
WatchSyncManager.shared.requestRefresh(at: requestDate) WatchSyncManager.shared.requestRefresh(at: requestDate)
@@ -560,8 +565,16 @@ struct ContentView: View {
if WatchFuelCache.wasRefreshHandled(since: requestDate) { if WatchFuelCache.wasRefreshHandled(since: requestDate) {
await MainActor.run { await MainActor.run {
reloadFromCache() reloadFromCache()
let label = WatchFuelCache.loadLastRefresh().map(relativeRefreshText(for:)) ?? "just now" let currentLastRefresh = WatchFuelCache.loadLastRefresh()
refreshState = .requested(lastUpdated: label) if let currentLastRefresh,
currentLastRefresh >= requestDate,
currentLastRefresh > (previousLastRefresh ?? .distantPast) {
refreshState = .requested(lastUpdated: relativeRefreshText(for: currentLastRefresh))
} else if let currentLastRefresh {
refreshState = .checkedNoNewData(lastUpdated: relativeRefreshText(for: currentLastRefresh))
} else {
refreshState = .noData
}
} }
return return
} }
@@ -147,9 +147,9 @@ private struct FuelBoardLiveActivityView: View {
} }
/// Minimal strip for small space (CarPlay small / Watch smart stack): /// Minimal strip for small space (CarPlay small / Watch smart stack):
/// fuel type + bold price on one line, station · distance below. /// fuel type + distance + bold price on one line, station name below.
/// Deliberately no app name and no "Tap for directions" CarPlay is /// Deliberately no app name and no full address the watch-sized slot is
/// display-only, and the user's asks here are just fuel + price + distance. /// too tight, so the second line is reserved for the station name only.
private var compactBody: some View { private var compactBody: some View {
VStack(alignment: .leading, spacing: 3) { VStack(alignment: .leading, spacing: 3) {
HStack(spacing: 5) { HStack(spacing: 5) {
@@ -157,16 +157,21 @@ private struct FuelBoardLiveActivityView: View {
.font(.caption.bold()) .font(.caption.bold())
.lineLimit(1) .lineLimit(1)
Spacer(minLength: 4) Spacer(minLength: 4)
Text(context.state.distanceText)
.font(.caption2)
.foregroundStyle(.secondary)
.lineLimit(1)
Spacer(minLength: 4)
FuelStore.priceTextAttributed(context.state.pricePence, FuelStore.priceTextAttributed(context.state.pricePence,
style: context.state.priceDisplayStyle, style: context.state.priceDisplayStyle,
size: 15, weight: .bold) size: 15, weight: .bold)
.lineLimit(1) .lineLimit(1)
} }
Text("\(context.state.stationName) · \(context.state.distanceText)") Text(context.state.stationName)
.font(.system(size: 9)) .font(.system(size: 10, weight: .medium))
.foregroundStyle(.secondary) .foregroundStyle(.secondary)
.lineLimit(1) .lineLimit(1)
.minimumScaleFactor(0.7) .minimumScaleFactor(0.75)
.truncationMode(.tail) .truncationMode(.tail)
} }
.padding(8) .padding(8)