feat(trip): add Open in Apple Maps button to trip detail map

- Add floating action button (bottom-right) on trip map
- Create AppleMapsLauncher service to handle route opening
- Collect all routable stops (trip stops + custom items with coords)
- Handle >16 waypoints with alert offering to open in parts
- Silently skip stops without coordinates

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-01-19 10:59:53 -06:00
parent 1b0abe2cc1
commit 239d22a872
2 changed files with 188 additions and 0 deletions

View File

@@ -44,6 +44,10 @@ struct TripDetailView: View {
@State private var dropTargetId: String? // Track which drop zone is being hovered
@State private var travelOverrides: [String: TravelOverride] = [:] // Key: travel ID, Value: day + sortOrder
// Apple Maps state
@State private var showMultiRouteAlert = false
@State private var multiRouteChunks: [[MKMapItem]] = []
private let exportService = ExportService()
private let dataProvider = AppDataProvider.shared
@@ -99,6 +103,16 @@ struct TripDetailView: View {
tripId: trip.id,
saveItineraryItem: saveItineraryItem
))
.alert("Large Trip Route", isPresented: $showMultiRouteAlert) {
ForEach(multiRouteChunks.indices, id: \.self) { index in
Button("Open Part \(index + 1) of \(multiRouteChunks.count)") {
AppleMapsLauncher.open(chunk: index, of: multiRouteChunks)
}
}
Button("Cancel", role: .cancel) { }
} message: {
Text("This trip has \(multiRouteChunks.flatMap { $0 }.count) stops, which exceeds Apple Maps' limit of 16. Open the route in parts?")
}
.onAppear { checkIfSaved() }
.task {
await loadGamesIfNeeded()
@@ -337,6 +351,24 @@ struct TripDetailView: View {
.padding(.top, 12)
.padding(.trailing, 12)
}
.overlay(alignment: .bottomTrailing) {
// Open in Apple Maps button
Button {
openInAppleMaps()
} label: {
Image(systemName: "map.fill")
.font(.title3)
.foregroundStyle(.white)
.padding(12)
.background(Theme.warmOrange)
.clipShape(Circle())
.shadow(color: .black.opacity(0.3), radius: 4, y: 2)
}
.padding(.bottom, 90) // Above the gradient
.padding(.trailing, 12)
.accessibilityLabel("Open in Apple Maps")
.accessibilityHint("Opens this trip route in Apple Maps")
}
// Gradient overlay at bottom
LinearGradient(
@@ -1189,6 +1221,26 @@ struct TripDetailView: View {
}
}
private func openInAppleMaps() {
let result = AppleMapsLauncher.prepare(
stops: trip.stops,
customItems: mappableCustomItems
)
switch result {
case .ready(let mapItems):
AppleMapsLauncher.open(mapItems)
case .multipleRoutes(let chunks):
multiRouteChunks = chunks
showMultiRouteAlert = true
case .noWaypoints:
// No routable locations - button shouldn't be visible but handle gracefully
break
}
}
private func saveTrip() {
// Check trip limit for free users
if !StoreManager.shared.isPro && savedTrips.count >= StoreManager.freeTripLimit {