feat(progress): add progress tracking enhancements

- Enable zoom/pan on progress map with reset button
- Add visit count badges to stadium chips
- Create GamesHistoryView with year grouping and sport filters
- Create StadiumVisitHistoryView for viewing all visits to a stadium
- Add VisitListCard and GamesHistoryRow components
- Add "See All" navigation from Recent Visits to Games History
- Add tests for map interactions, visit lists, and games history

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-01-12 21:34:33 -06:00
parent ed526cabeb
commit 89167c01d7
11 changed files with 979 additions and 16 deletions

View File

@@ -15,16 +15,11 @@ struct ProgressMapView: View {
let visitStatus: [String: StadiumVisitStatus]
@Binding var selectedStadium: Stadium?
// Fixed region for continental US - map is locked to this view
private let usRegion = MKCoordinateRegion(
center: CLLocationCoordinate2D(latitude: 39.8283, longitude: -98.5795),
span: MKCoordinateSpan(latitudeDelta: 50, longitudeDelta: 60)
)
@State private var mapViewModel = MapInteractionViewModel()
@State private var cameraPosition: MapCameraPosition = .region(MapInteractionViewModel.defaultRegion)
var body: some View {
// Use initialPosition with empty interactionModes to disable pan/zoom
// while keeping annotations tappable
Map(initialPosition: .region(usRegion), interactionModes: []) {
Map(position: $cameraPosition, interactionModes: [.zoom, .pan]) {
ForEach(stadiums) { stadium in
Annotation(
stadium.name,
@@ -50,6 +45,26 @@ struct ProgressMapView: View {
}
}
}
.onMapCameraChange { _ in
mapViewModel.userDidInteract()
}
.overlay(alignment: .bottomTrailing) {
if mapViewModel.shouldShowResetButton {
Button {
withAnimation(.easeInOut(duration: 0.5)) {
cameraPosition = .region(MapInteractionViewModel.defaultRegion)
mapViewModel.resetToDefault()
selectedStadium = nil
}
} label: {
Image(systemName: "arrow.counterclockwise")
.font(.title3)
.padding(12)
.background(.regularMaterial, in: Circle())
}
.padding()
}
}
.mapStyle(.standard(elevation: .realistic))
.clipShape(RoundedRectangle(cornerRadius: 16))
}