Small widgetURL and medium list Links route through StationTapAction via FuelStation.tapDestinationURL: Open map keeps the Maps URL, More info uses a fuelboard://station deep link (stationID plus coordinate/name fallback). Deep link carries fallback coords; ContentView presents the detail sheet when the station is cached, else the map sheet. Live Activity detailURL upgraded to the same rich link.
126 lines
4.8 KiB
Swift
126 lines
4.8 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=…&lat=…&lng=…&name=…` link (from a
|
|
/// widget or Live Activity tap when Settings → Tap station = More info)
|
|
/// posts a notification that ContentView observes to present the detail
|
|
/// sheet (falling back to the map sheet when the station has left
|
|
/// the cache).
|
|
private func handleOpenURL(_ url: URL) {
|
|
guard let components = URLComponents(url: url, resolvingAgainstBaseURL: false)
|
|
else { return }
|
|
|
|
if components.scheme == "fuelboard",
|
|
components.host == "station" {
|
|
let items = components.queryItems ?? []
|
|
var userInfo: [String: Any] = [:]
|
|
if let id = items.first(where: { $0.name == "stationID" })?.value {
|
|
userInfo["stationID"] = id
|
|
}
|
|
if let lat = items.first(where: { $0.name == "lat" })?.value.flatMap(Double.init) {
|
|
userInfo["stationLat"] = lat
|
|
}
|
|
if let lng = items.first(where: { $0.name == "lng" })?.value.flatMap(Double.init) {
|
|
userInfo["stationLng"] = lng
|
|
}
|
|
if let name = items.first(where: { $0.name == "name" })?.value {
|
|
userInfo["stationName"] = name
|
|
}
|
|
NotificationCenter.default.post(
|
|
name: .fuelBoardShowStationDetail,
|
|
object: nil,
|
|
userInfo: userInfo
|
|
)
|
|
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)
|
|
}
|
|
}
|