History v3: search, sort, filters, interactive lifetime map

Pulls the History tab toward Flighty's Passport in three ways:

Search + sort + filters on the list
- .searchable text field across flight #, route, IATA codes
- 6-way sort menu (newest/oldest, longest/shortest, by airline,
  by flight number); list either groups by year (date sorts) or
  goes flat (everything else)
- New HistoryFilters value type: year set + airline set + airport
  set + aircraft-type set + query. .matches(flight) predicate
- New HistoryFilterSheet with multi-select chips and live counts
  per option (we only show years/airlines/airports/types you've
  actually flown)
- Active-filter chip row above the list, tap to remove individual
  filters; "Clear" wipes all
- Totals strip retitles to FILTERED TOTALS when filters are on
  and recomputes against the visible subset

Interactive route map
- Tap an arc style preserved, but most-recent flight now
  highlighted brighter so it stands out at a glance
- Tap an airport dot → AirportFlightsView for that airport
- Filter sync: dots tied to currently-filtered airports light up
- Replay button in the toolbar restarts the reveal animation
- Map respects History tab's filters (visible arcs match) but
  draws airport dots from the FULL log so geography stays stable

Airport drilldown (new)
- AirportFlightsView shows summary (departed/arrived/total),
  top 10 destinations from that airport, and the full list of
  flights through it
- "Filter list" toolbar action sets the History filter to just
  that airport and pops back

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Trey T
2026-05-29 10:38:28 -05:00
parent d639cdef15
commit a33a56176d
6 changed files with 681 additions and 74 deletions
+12
View File
@@ -78,6 +78,9 @@
HX1000001000000010000001 /* AirframeMetadataService.swift in Sources */ = {isa = PBXBuildFile; fileRef = HX1000001000000010000002 /* AirframeMetadataService.swift */; };
HX1100001100000011000001 /* CSVFlightImporter.swift in Sources */ = {isa = PBXBuildFile; fileRef = HX1100001100000011000002 /* CSVFlightImporter.swift */; };
HX1200001200000012000001 /* ImportCSVView.swift in Sources */ = {isa = PBXBuildFile; fileRef = HX1200001200000012000002 /* ImportCSVView.swift */; };
HX1300001300000013000001 /* HistoryFilters.swift in Sources */ = {isa = PBXBuildFile; fileRef = HX1300001300000013000002 /* HistoryFilters.swift */; };
HX1400001400000014000001 /* HistoryFilterSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = HX1400001400000014000002 /* HistoryFilterSheet.swift */; };
HX1500001500000015000001 /* AirportFlightsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = HX1500001500000015000002 /* AirportFlightsView.swift */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -165,6 +168,9 @@
HX1000001000000010000002 /* AirframeMetadataService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirframeMetadataService.swift; sourceTree = "<group>"; };
HX1100001100000011000002 /* CSVFlightImporter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CSVFlightImporter.swift; sourceTree = "<group>"; };
HX1200001200000012000002 /* ImportCSVView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ImportCSVView.swift; sourceTree = "<group>"; };
HX1300001300000013000002 /* HistoryFilters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryFilters.swift; sourceTree = "<group>"; };
HX1400001400000014000002 /* HistoryFilterSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HistoryFilterSheet.swift; sourceTree = "<group>"; };
HX1500001500000015000002 /* AirportFlightsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AirportFlightsView.swift; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -212,6 +218,8 @@
HX0E000EEEE000EEEE000002 /* HistoryRouteMapView.swift */,
HX0F000FFFF000FFFF000002 /* YearInReviewView.swift */,
HX1200001200000012000002 /* ImportCSVView.swift */,
HX1400001400000014000002 /* HistoryFilterSheet.swift */,
HX1500001500000015000002 /* AirportFlightsView.swift */,
AA5555555555555555555555 /* Styles */,
AA6666666666666666666666 /* Components */,
);
@@ -302,6 +310,7 @@
HX0700007777000077770002 /* WalletPassObserver.swift */,
HX1000001000000010000002 /* AirframeMetadataService.swift */,
HX1100001100000011000002 /* CSVFlightImporter.swift */,
HX1300001300000013000002 /* HistoryFilters.swift */,
);
path = Services;
sourceTree = "<group>";
@@ -495,6 +504,9 @@
HX1000001000000010000001 /* AirframeMetadataService.swift in Sources */,
HX1100001100000011000001 /* CSVFlightImporter.swift in Sources */,
HX1200001200000012000001 /* ImportCSVView.swift in Sources */,
HX1300001300000013000001 /* HistoryFilters.swift in Sources */,
HX1400001400000014000001 /* HistoryFilterSheet.swift in Sources */,
HX1500001500000015000001 /* AirportFlightsView.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};