Refine trends and debug harness; clean up warnings
This commit is contained in:
@@ -3,6 +3,17 @@
|
||||
// Lives in the widget extension (the standard host for ActivityConfiguration).
|
||||
// Shows the cheapest station for the pinned fuel within the app's chosen
|
||||
// radius. Tapping anywhere opens Apple Maps directions to that station.
|
||||
//
|
||||
// ADAPTIVE LAYOUT: ActivityConfiguration shares ONE content view across the
|
||||
// Lock Screen, banner, and the CarPlay small slot — there is no per-platform
|
||||
// closure. So this view provides two layouts and lets ViewThatFits pick by
|
||||
// available width:
|
||||
// • richBody — the full three-column design (glyph · fuel+station · price),
|
||||
// wins wherever there's Lock Screen width (it carries an
|
||||
// explicit minWidth so it can never be squeezed into the car).
|
||||
// • compactBody — a minimal price-strip (glyph+fuel+price, station caption
|
||||
// below) that wins in the CarPlay/Apple Watch Smart Stack
|
||||
// small slot.
|
||||
|
||||
import ActivityKit
|
||||
import SwiftUI
|
||||
@@ -17,7 +28,7 @@ import WidgetKit
|
||||
struct FuelBoardLiveActivity: Widget {
|
||||
var body: some WidgetConfiguration {
|
||||
ActivityConfiguration(for: FuelBoardLiveActivityAttributes.self) { context in
|
||||
// Lock Screen / banner presentation
|
||||
// Lock Screen / banner / CarPlay small slot presentation
|
||||
FuelBoardLiveActivityView(context: context)
|
||||
} dynamicIsland: { context in
|
||||
DynamicIsland {
|
||||
@@ -44,43 +55,81 @@ struct FuelBoardLiveActivity: Widget {
|
||||
}
|
||||
}
|
||||
|
||||
/// Lock Screen / banner body — the main presentation.
|
||||
/// Lock Screen / banner / CarPlay body — adaptive.
|
||||
private struct FuelBoardLiveActivityView: View {
|
||||
let context: ActivityViewContext<FuelBoardLiveActivityAttributes>
|
||||
|
||||
var body: some View {
|
||||
Link(destination: context.state.mapsURL) {
|
||||
HStack(spacing: 12) {
|
||||
// LEFT — station brand glyph
|
||||
Image(systemName: "fuelpump.circle.fill")
|
||||
.font(.system(size: 32))
|
||||
.foregroundStyle(.green, .white)
|
||||
.frame(width: 40, height: 40)
|
||||
|
||||
// MIDDLE — fuel + station
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text("Cheapest \(context.state.fuel.displayName)")
|
||||
.font(.headline)
|
||||
Text("\(context.state.stationName) · \(context.state.distanceText)")
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(.secondary)
|
||||
.lineLimit(1)
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
// RIGHT — price
|
||||
VStack(alignment: .trailing, spacing: 2) {
|
||||
FuelStore.priceTextAttributed(context.state.pricePence,
|
||||
style: context.state.priceDisplayStyle,
|
||||
size: 22, weight: .bold)
|
||||
Text("Tap for directions")
|
||||
.font(.caption2)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
ViewThatFits(in: .horizontal) {
|
||||
// rich first — wins on full-width Lock Screen / banner
|
||||
richBody
|
||||
// CarPlay / Watch small slot is far narrower than this,
|
||||
// so ViewThatFits reliably falls through to compactBody.
|
||||
.frame(minWidth: 280)
|
||||
// compact fallback — the car's small supplemental slot
|
||||
compactBody
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
}
|
||||
|
||||
/// Full three-column design (unchanged): brand glyph · fuel+station · price.
|
||||
private var richBody: some View {
|
||||
HStack(spacing: 12) {
|
||||
// LEFT — station brand glyph
|
||||
Image(systemName: "fuelpump.circle.fill")
|
||||
.font(.system(size: 32))
|
||||
.foregroundStyle(.green, .white)
|
||||
.frame(width: 40, height: 40)
|
||||
|
||||
// MIDDLE — fuel + station
|
||||
VStack(alignment: .leading, spacing: 2) {
|
||||
Text("Cheapest \(context.state.fuel.displayName)")
|
||||
.font(.headline)
|
||||
Text("\(context.state.stationName) · \(context.state.distanceText)")
|
||||
.font(.subheadline)
|
||||
.foregroundStyle(.secondary)
|
||||
.lineLimit(1)
|
||||
}
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
|
||||
// RIGHT — price
|
||||
VStack(alignment: .trailing, spacing: 2) {
|
||||
FuelStore.priceTextAttributed(context.state.pricePence,
|
||||
style: context.state.priceDisplayStyle,
|
||||
size: 22, weight: .bold)
|
||||
Text("Tap for directions")
|
||||
.font(.caption2)
|
||||
.foregroundStyle(.secondary)
|
||||
}
|
||||
}
|
||||
.padding()
|
||||
}
|
||||
|
||||
/// Minimal strip for the small CarPlay / Watch Smart Stack slot:
|
||||
/// glyph + fuel left, bold price right, truncated station below.
|
||||
private var compactBody: some View {
|
||||
VStack(alignment: .leading, spacing: 3) {
|
||||
HStack(spacing: 5) {
|
||||
Image(systemName: "fuelpump.fill")
|
||||
.font(.caption2)
|
||||
.foregroundStyle(.green)
|
||||
Text(context.state.fuel.displayName)
|
||||
.font(.caption2.weight(.semibold))
|
||||
.lineLimit(1)
|
||||
Spacer(minLength: 4)
|
||||
FuelStore.priceTextAttributed(context.state.pricePence,
|
||||
style: context.state.priceDisplayStyle,
|
||||
size: 15, weight: .bold)
|
||||
.lineLimit(1)
|
||||
}
|
||||
Text(context.state.stationName)
|
||||
.font(.system(size: 9))
|
||||
.foregroundStyle(.secondary)
|
||||
.lineLimit(1)
|
||||
}
|
||||
.padding(8)
|
||||
}
|
||||
}
|
||||
|
||||
/// Dynamic Island expanded regions + compact trailing — price only.
|
||||
@@ -140,4 +189,4 @@ extension FuelBoardLiveActivityAttributes.ContentState {
|
||||
var mapsURL: URL {
|
||||
URL(string: "maps://?daddr=\(lat),\(lng)&t=d")!
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -156,7 +156,7 @@ struct FuelPriceTimelineProvider<Configuration: WidgetConfigurationIntent & Widg
|
||||
URLQueryItem(name: "price", value: String(first?.prices[d.fuel] ?? -1)),
|
||||
]
|
||||
guard let url = components.url else { return }
|
||||
var request = RelayFuelProvider.relayRequest(url, client: "widget", timeout: 2)
|
||||
let request = RelayFuelProvider.relayRequest(url, client: "widget", timeout: 2)
|
||||
Task {
|
||||
_ = try? await URLSession.shared.data(for: request)
|
||||
}
|
||||
@@ -312,7 +312,7 @@ struct FuelPriceTimelineProvider<Configuration: WidgetConfigurationIntent & Widg
|
||||
URLQueryItem(name: "radius", value: String(radiusKM)),
|
||||
URLQueryItem(name: "limit", value: "500"),
|
||||
]
|
||||
var request = RelayFuelProvider.relayRequest(components.url!, client: "widget", timeout: 5)
|
||||
let request = RelayFuelProvider.relayRequest(components.url!, client: "widget", timeout: 5)
|
||||
do {
|
||||
let (data, response) = try await URLSession.shared.data(for: request)
|
||||
guard let http = response as? HTTPURLResponse, http.statusCode == 200 else { return nil }
|
||||
@@ -340,7 +340,7 @@ struct FuelPriceTimelineProvider<Configuration: WidgetConfigurationIntent & Widg
|
||||
URLQueryItem(name: "fuel", value: fuel.rawValue),
|
||||
URLQueryItem(name: "limit", value: String(limit)),
|
||||
]
|
||||
var request = RelayFuelProvider.relayRequest(components.url!, client: "widget", timeout: 5)
|
||||
let request = RelayFuelProvider.relayRequest(components.url!, client: "widget", timeout: 5)
|
||||
do {
|
||||
let (data, response) = try await URLSession.shared.data(for: request)
|
||||
guard let http = response as? HTTPURLResponse, http.statusCode == 200 else { return nil }
|
||||
|
||||
Reference in New Issue
Block a user