Live tab: bundled airline DB, OpenSky login, in-flight trails

Three follow-ups to the live tab landed together:

1) Bundled airline registry
   - airlines.json (208KB, 2,695 entries sourced from FR24's
     /mobile/airlines feed and slimmed to {icao,iata,name,logo}).
   - AircraftRegistry rewritten as an instance singleton that loads
     the bundle at startup, indexes by both ICAO and IATA, and falls
     back to a small hardcoded subset if the bundle is unavailable.
   - Detail sheet now shows the airline's logo (loaded from FR24's
     CDN via AsyncImage) alongside the callsign. Filter chips use
     the real names everywhere.

2) OpenSky account login
   - OpenSkyCredentials: Keychain wrapper that stores username +
     password using SecItem APIs. Posts a notification on change so
     the OpenSkyClient can refresh its in-memory copy.
   - OpenSkyClient now sends HTTP Basic auth when credentials are
     present. Anonymous fallback unchanged.
   - OpenSkySettingsView: tap the gear in the footer to sign in.
     Credentials are verified against /states/all before being
     stored; sign-out clears Keychain. Raises the quota from ~100
     to ~4000 requests/day.

3) Flight trails
   - AircraftTrack model decodes OpenSky's /tracks/all heterogeneous
     path array into typed TrackPoint entries.
   - OpenSkyClient.track(icao24:) fetches the current/most-recent
     track for an aircraft.
   - LiveFlightsView renders a MapPolyline trail along the path of
     whichever aircraft is currently selected. Cleared on
     deselection; race-guarded against rapid selection changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Trey T
2026-05-27 06:16:49 -05:00
parent 888943deb4
commit 6b33a104c8
9 changed files with 490 additions and 93 deletions
+12
View File
@@ -52,6 +52,9 @@
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 */; };
LV7700007777000077770001 /* OpenSkyCredentials.swift in Sources */ = {isa = PBXBuildFile; fileRef = LV7700007777000077770002 /* OpenSkyCredentials.swift */; };
LV8800008888000088880001 /* OpenSkySettingsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = LV8800008888000088880002 /* OpenSkySettingsView.swift */; };
LV9900009999000099990001 /* airlines.json in Resources */ = {isa = PBXBuildFile; fileRef = LV9900009999000099990002 /* airlines.json */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -112,6 +115,9 @@
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>"; };
LV7700007777000077770002 /* OpenSkyCredentials.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenSkyCredentials.swift; sourceTree = "<group>"; };
LV8800008888000088880002 /* OpenSkySettingsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OpenSkySettingsView.swift; sourceTree = "<group>"; };
LV9900009999000099990002 /* airlines.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = airlines.json; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -148,6 +154,7 @@
LV4400004444000044440002 /* LiveFlightsView.swift */,
LV5500005555000055550002 /* LiveFlightDetailSheet.swift */,
LV6600006666000066660002 /* RootView.swift */,
LV8800008888000088880002 /* OpenSkySettingsView.swift */,
AA5555555555555555555555 /* Styles */,
AA6666666666666666666666 /* Components */,
);
@@ -182,6 +189,7 @@
1B20C5393D8F432A93097C2C /* Views */,
D9E26DCDE2904210ABCA7855 /* Assets.xcassets */,
53F457716F0642BDBCBA93EA /* airports.json */,
LV9900009999000099990002 /* airlines.json */,
);
path = Flights;
sourceTree = "<group>";
@@ -225,6 +233,7 @@
RE2200002222000022220002 /* RouteExplorerClient.swift */,
LV2200002222000022220002 /* OpenSkyClient.swift */,
LV3300003333000033330002 /* AircraftRegistry.swift */,
LV7700007777000077770002 /* OpenSkyCredentials.swift */,
);
path = Services;
sourceTree = "<group>";
@@ -338,6 +347,7 @@
files = (
F79789179F4443FD859BDEF0 /* Assets.xcassets in Resources */,
80D2BC95002A4931B3C10B4C /* airports.json in Resources */,
LV9900009999000099990001 /* airlines.json in Resources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -390,6 +400,8 @@
LV4400004444000044440001 /* LiveFlightsView.swift in Sources */,
LV5500005555000055550001 /* LiveFlightDetailSheet.swift in Sources */,
LV6600006666000066660001 /* RootView.swift in Sources */,
LV7700007777000077770001 /* OpenSkyCredentials.swift in Sources */,
LV8800008888000088880001 /* OpenSkySettingsView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};