Files
fuelboard/FuelBoard/FuelBoardApp.swift
T
FuelBoard Contributor 465e6cd4d4 feat: honour Tap station action on notification and Live Activity taps
Notification body/action taps route through StationTapAction:
Open map opens Apple Maps (existing); More info presents the
in-app detail sheet (falls back to the map sheet when the
station is not in cache).

Live Activity Lock Screen card and island station row pick
their Link destination the same way: Maps URL for Open map,
fuelboard://station deep link for More info, handled in
FuelBoardApp and observed by ContentView.

Also adds the small non-interactive map preview above the
Directions button in the More info sheet.
2026-09-16 13:11:36 +01:00

111 lines
4.1 KiB
Swift

import ActivityKit
import BackgroundTasks
import SwiftUI
@main
struct FuelBoardApp: App {
init() {
WatchSyncManager.shared.activate()
#if DEBUG
// QA hook (Debug builds only): `-qaLiveActivity e10|e5|diesel` starts a
// Live Activity with a long station name so the Lock Screen / island
// layout can be rendered in the Simulator for visual QA.
let args = ProcessInfo.processInfo.arguments
if let idx = args.firstIndex(of: "-qaLiveActivity"),
args.indices.contains(idx + 1),
let fuel = FuelType(rawValue: args[idx + 1]) {
startQALiveActivity(fuel: fuel)
}
#endif
}
var body: some Scene {
WindowGroup {
ContentView()
.onOpenURL(perform: handleOpenURL)
.onReceive(NotificationCenter.default.publisher(for: .fuelBoardWatchRefreshRequested)) { _ in
WatchSyncManager.shared.pushSnapshot()
}
}
.backgroundTask(.appRefresh(SmartDataRefreshScheduler.taskIdentifier)) {
await SmartDataRefreshCoordinator.runBackgroundProbe()
}
}
#if DEBUG
private func startQALiveActivity(fuel: FuelType) {
let state = FuelBoardLiveActivityAttributes.ContentState(
fuel: fuel,
stationID: "qa-phoenix",
stationName: "Phoenix Filling Stations",
brand: "Phoenix",
pricePence: 1499,
priceDisplayStyle: FuelStore.loadPriceDisplayStyle(),
distanceKM: 8.0,
lat: 51.5,
lng: -0.12,
updatedAt: Date()
)
let attrs = FuelBoardLiveActivityAttributes()
do {
let activity = try Activity.request(
attributes: attrs,
content: .init(state: state, staleDate: nil),
pushType: nil
)
print("QA-LIVE-ACTIVITY STARTED id=\(activity.id)")
} catch {
print("QA-LIVE-ACTIVITY FAILED: \(error)")
}
}
#endif
/// Handles deep links that end up in the app. Widget taps arrive here in
/// two cases:
/// - legacy/cached widget timelines using the `fuelboard://` relay, or
/// - the Home Screen delivering a `maps://` URL to the containing app
/// instead of opening Maps directly (the system's choice on iOS).
/// Either way we forward to the native Apple Maps directions URL. The
/// `http://maps.apple.com` form is also handled, from very old timelines.
/// In CarPlay the widget uses the same `maps://` URL, which routes to
/// Apple Maps without ever launching this app.
/// A `fuelboard://station?stationID=…` link (from a Live Activity tap when
/// Settings → Tap station = More info) posts a notification that
/// ContentView observes to present the detail sheet.
private func handleOpenURL(_ url: URL) {
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false)
else { return }
if components.scheme == "fuelboard",
components.host == "station",
let id = components.queryItems?.first(where: { $0.name == "stationID" })?.value {
NotificationCenter.default.post(
name: .fuelBoardShowStationDetail,
object: nil,
userInfo: ["stationID": id]
)
return
}
guard let items = components.queryItems
else { return }
// Preferred: maps://?daddr=lat,lng&t=d (also covers http://maps.apple.com).
if let daddr = items.first(where: { $0.name == "daddr" })?.value {
openMaps(daddr: daddr)
return
}
// Legacy relay: fuelboard://directions?lat=..&lng=..
if let lat = items.first(where: { $0.name == "lat" })?.value.flatMap(Double.init),
let lng = items.first(where: { $0.name == "lng" })?.value.flatMap(Double.init) {
openMaps(daddr: "\(lat),\(lng)")
}
}
private func openMaps(daddr: String) {
guard let mapsURL = URL(string: "maps://?daddr=\(daddr)&t=d") else { return }
UIApplication.shared.open(mapsURL)
}
}