Files
fuelboard/FuelBoardWidgets/FuelBoardLiveActivityView.swift
T
FuelBoard Contributor fcb83f1eed Live Activity: own fuel + distance config, independent of Stations tab
- Settings → Live Activity now has Fuel and Distance pickers (5/10/15,
  unit-aware) stored under fuelboard.liveActivityFuel / liveActivityRadiusMiles
- LiveActivityManager uses these instead of the Stations-tab selectedFuel /
  stationLimit; Stations changes no longer re-target the pill
- Fuel moved from Activity attributes into ContentState so changing fuel in
  Settings updates the RUNNING activity in place (no end+restart needed)
- Stations tab stays a pure data-interrogation + favourites surface
2026-08-12 20:58:05 +01:00

136 lines
4.9 KiB
Swift

// FuelBoardLiveActivityView.swift — renders the FuelBoard Live Activity.
//
// 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.
import ActivityKit
import SwiftUI
import WidgetKit
/// The Live Activity itself — registered in the widget bundle alongside the
/// regular price widget. No CarPlay entitlement involved: this renders on the
/// Lock Screen, Dynamic Island, and (iOS 26+ / CarPlay Ultra) the car display.
struct FuelBoardLiveActivity: Widget {
var body: some WidgetConfiguration {
ActivityConfiguration(for: FuelBoardLiveActivityAttributes.self) { context in
// Lock Screen / banner presentation
FuelBoardLiveActivityView(context: context)
} dynamicIsland: { context in
DynamicIsland {
DynamicIslandExpandedRegion(.leading) {
FuelBoardLiveActivityCompactView(context: context)
}
DynamicIslandExpandedRegion(.trailing) {
FuelBoardLiveActivityPriceView(context: context)
}
DynamicIslandExpandedRegion(.bottom) {
FuelBoardLiveActivityStationView(context: context)
}
} compactLeading: {
Image(systemName: "fuelpump.fill")
.foregroundStyle(.green)
} compactTrailing: {
FuelBoardLiveActivityPriceView(context: context)
} minimal: {
Text(context.state.priceText)
.font(.caption2.bold().monospacedDigit())
}
}
}
}
/// Lock Screen / banner body — the main presentation.
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) {
Text(context.state.priceText)
.font(.title2.bold().monospacedDigit())
Text("Tap for directions")
.font(.caption2)
.foregroundStyle(.secondary)
}
}
.padding()
}
}
}
/// Dynamic Island expanded regions + compact trailing — price only.
private struct FuelBoardLiveActivityPriceView: View {
let context: ActivityViewContext<FuelBoardLiveActivityAttributes>
var body: some View {
Text(context.state.priceText)
.font(.headline.bold().monospacedDigit())
}
}
/// Dynamic Island expanded — fuel + station name.
private struct FuelBoardLiveActivityCompactView: View {
let context: ActivityViewContext<FuelBoardLiveActivityAttributes>
var body: some View {
VStack(alignment: .leading, spacing: 2) {
Text("Cheapest \(context.state.fuel.displayName)")
.font(.caption2)
.foregroundStyle(.secondary)
Text(context.state.stationName)
.font(.caption.bold())
.lineLimit(1)
}
}
}
/// Dynamic Island expanded bottom — station + distance.
private struct FuelBoardLiveActivityStationView: View {
let context: ActivityViewContext<FuelBoardLiveActivityAttributes>
var body: some View {
Text("\(context.state.stationName) · \(context.state.distanceText)")
.font(.caption)
.lineLimit(1)
}
}
// MARK: - ContentState display helpers
extension FuelBoardLiveActivityAttributes.ContentState {
/// Display distance using the user's chosen unit (miles default).
var distanceText: String {
FuelStore.loadDistanceUnit().format(distanceKM)
}
/// £ price string from pence, e.g. 161.9 -> "£1.619".
var priceText: String {
String(format: "£%.3f", pricePence / 100)
}
/// Apple Maps directions URL to the pinned station.
var mapsURL: URL {
URL(string: "maps://?daddr=\(lat),\(lng)&t=d")!
}
}