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:
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user