Add Live Flights tab: real-time aircraft map with filters + tap detail
New top-level TabView (RootView) splits the app into: Tab 1 (Search): existing RoutePlannerView home Tab 2 (Live): live flight tracker Live tab features: - MapKit map showing every aircraft in the visible viewport, rotated to true heading. Color-coded by vertical state: climbing/level/ descending/on-ground. - Auto-refresh every 15s + on map pan/zoom (debounced); manual refresh button. Rate-limit aware (60s backoff on HTTP 429). - Tap any aircraft → modal sheet with live state grid (altitude, speed, heading, vertical rate, squawk, last-contact), current route (lazily fetched per-aircraft from OpenSky's /flights/ aircraft endpoint, mapped from ICAO to IATA airport codes), and recent flight history (up to 8 prior legs). - Filters: airline (multi-select from currently visible callsigns, with counts), aircraft type (ADS-B emitter category), airborne- only toggle. All filters render as horizontal chips and clear with a single tap. - Search bar: callsign/flight number — submitting centers the map on the match and opens its detail sheet. Data source: OpenSky Network REST API. Free, anonymous (~100 req/ day cap), JSON. Same ADS-B data FR24 starts with — without satellite ADS-B coverage but more than enough for the in-flight tracker use case. Reviewed FR24's APK and confirmed they migrated their live feed to gRPC+protobuf with anti-bot device-id headers; OpenSky's plain JSON is the right tradeoff for our build. Implementation: - LiveAircraft model: decodes OpenSky's mixed-type position arrays into a typed struct; computed properties for ft/knots/heading and airline ICAO extracted from callsign. - OpenSkyClient: actor with /states/all + /flights/aircraft. Bbox query, throttle-aware errors. - AircraftRegistry: ~80 ICAO → (IATA, name) entries for the major carriers; everything else falls through to the raw ICAO code. - LiveFlightsView: the main map + filter UI. - LiveFlightDetailSheet: tap modal with live state + route history. - RootView: TabView wrapping RoutePlannerView (Search) and the new LiveFlightsView (Live). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -46,6 +46,12 @@
|
||||
RE7700007777000077770001 /* ConnectionLoadDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = RE7700007777000077770002 /* ConnectionLoadDetailView.swift */; };
|
||||
RE8800008888000088880001 /* SearchRoute.swift in Sources */ = {isa = PBXBuildFile; fileRef = RE8800008888000088880002 /* SearchRoute.swift */; };
|
||||
T1000000000000000000001A /* AirlineLoadIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = T1000000000000000000001B /* AirlineLoadIntegrationTests.swift */; };
|
||||
LV1100001111000011110001 /* LiveAircraft.swift in Sources */ = {isa = PBXBuildFile; fileRef = LV1100001111000011110002 /* LiveAircraft.swift */; };
|
||||
LV2200002222000022220001 /* OpenSkyClient.swift in Sources */ = {isa = PBXBuildFile; fileRef = LV2200002222000022220002 /* OpenSkyClient.swift */; };
|
||||
LV3300003333000033330001 /* AircraftRegistry.swift in Sources */ = {isa = PBXBuildFile; fileRef = LV3300003333000033330002 /* AircraftRegistry.swift */; };
|
||||
LV4400004444000044440001 /* LiveFlightsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = LV4400004444000044440002 /* LiveFlightsView.swift */; };
|
||||
LV5500005555000055550001 /* LiveFlightDetailSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = LV5500005555000055550002 /* LiveFlightDetailSheet.swift */; };
|
||||
LV6600006666000066660001 /* RootView.swift in Sources */ = {isa = PBXBuildFile; fileRef = LV6600006666000066660002 /* RootView.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -100,6 +106,12 @@
|
||||
RE8800008888000088880002 /* SearchRoute.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SearchRoute.swift; sourceTree = "<group>"; };
|
||||
T1000000000000000000001B /* AirlineLoadIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirlineLoadIntegrationTests.swift; sourceTree = "<group>"; };
|
||||
T1000000000000000000003A /* FlightsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = FlightsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
LV1100001111000011110002 /* LiveAircraft.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveAircraft.swift; sourceTree = "<group>"; };
|
||||
LV2200002222000022220002 /* OpenSkyClient.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenSkyClient.swift; sourceTree = "<group>"; };
|
||||
LV3300003333000033330002 /* AircraftRegistry.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AircraftRegistry.swift; sourceTree = "<group>"; };
|
||||
LV4400004444000044440002 /* LiveFlightsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveFlightsView.swift; sourceTree = "<group>"; };
|
||||
LV5500005555000055550002 /* LiveFlightDetailSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LiveFlightDetailSheet.swift; sourceTree = "<group>"; };
|
||||
LV6600006666000066660002 /* RootView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RootView.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
/* Begin PBXFrameworksBuildPhase section */
|
||||
@@ -133,6 +145,9 @@
|
||||
BB1100001111000011110006 /* FlightLoadDetailView.swift */,
|
||||
RE3300003333000033330002 /* RoutePlannerView.swift */,
|
||||
RE7700007777000077770002 /* ConnectionLoadDetailView.swift */,
|
||||
LV4400004444000044440002 /* LiveFlightsView.swift */,
|
||||
LV5500005555000055550002 /* LiveFlightDetailSheet.swift */,
|
||||
LV6600006666000066660002 /* RootView.swift */,
|
||||
AA5555555555555555555555 /* Styles */,
|
||||
AA6666666666666666666666 /* Components */,
|
||||
);
|
||||
@@ -208,6 +223,8 @@
|
||||
BB1100001111000011110004 /* AirlineLoadService.swift */,
|
||||
BB2200002222000022220002 /* JSXWebViewFetcher.swift */,
|
||||
RE2200002222000022220002 /* RouteExplorerClient.swift */,
|
||||
LV2200002222000022220002 /* OpenSkyClient.swift */,
|
||||
LV3300003333000033330002 /* AircraftRegistry.swift */,
|
||||
);
|
||||
path = Services;
|
||||
sourceTree = "<group>";
|
||||
@@ -235,6 +252,7 @@
|
||||
BB1100001111000011110002 /* FlightLoad.swift */,
|
||||
RE1100001111000011110002 /* RouteExplorerModels.swift */,
|
||||
RE8800008888000088880002 /* SearchRoute.swift */,
|
||||
LV1100001111000011110002 /* LiveAircraft.swift */,
|
||||
);
|
||||
path = Models;
|
||||
sourceTree = "<group>";
|
||||
@@ -366,6 +384,12 @@
|
||||
RE6600006666000066660001 /* ConnectionRow.swift in Sources */,
|
||||
RE7700007777000077770001 /* ConnectionLoadDetailView.swift in Sources */,
|
||||
RE8800008888000088880001 /* SearchRoute.swift in Sources */,
|
||||
LV1100001111000011110001 /* LiveAircraft.swift in Sources */,
|
||||
LV2200002222000022220001 /* OpenSkyClient.swift in Sources */,
|
||||
LV3300003333000033330001 /* AircraftRegistry.swift in Sources */,
|
||||
LV4400004444000044440001 /* LiveFlightsView.swift in Sources */,
|
||||
LV5500005555000055550001 /* LiveFlightDetailSheet.swift in Sources */,
|
||||
LV6600006666000066660001 /* RootView.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user