Improve UI consistency and add heart save button
- Add themed background to all tab views (Schedule, Settings, My Trips) - Remove navigation titles from all screens (tab bar provides context) - Add empty state to My Trips view - Remove max driving hours slider from trip planner (available in Settings) - Replace save menu with heart button overlay on trip detail map - Add ability to unsave trips by tapping heart again 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -21,7 +21,6 @@ struct TripDetailView: View {
|
||||
@State private var shareURL: URL?
|
||||
@State private var mapCameraPosition: MapCameraPosition = .automatic
|
||||
@State private var isSaved = false
|
||||
@State private var showSaveConfirmation = false
|
||||
@State private var routePolylines: [MKPolyline] = []
|
||||
@State private var isLoadingRoutes = false
|
||||
|
||||
@@ -57,8 +56,6 @@ struct TripDetailView: View {
|
||||
}
|
||||
}
|
||||
.background(Theme.backgroundGradient(colorScheme))
|
||||
.navigationTitle(trip.name)
|
||||
.navigationBarTitleDisplayMode(.inline)
|
||||
.toolbarBackground(Theme.cardBackground(colorScheme), for: .navigationBar)
|
||||
.toolbar {
|
||||
ToolbarItemGroup(placement: .primaryAction) {
|
||||
@@ -71,23 +68,12 @@ struct TripDetailView: View {
|
||||
.foregroundStyle(Theme.warmOrange)
|
||||
}
|
||||
|
||||
Menu {
|
||||
Button {
|
||||
Task {
|
||||
await exportPDF()
|
||||
}
|
||||
} label: {
|
||||
Label("Export PDF", systemImage: "doc.fill")
|
||||
Button {
|
||||
Task {
|
||||
await exportPDF()
|
||||
}
|
||||
|
||||
Button {
|
||||
saveTrip()
|
||||
} label: {
|
||||
Label(isSaved ? "Saved" : "Save Trip", systemImage: isSaved ? "bookmark.fill" : "bookmark")
|
||||
}
|
||||
.disabled(isSaved)
|
||||
} label: {
|
||||
Image(systemName: "ellipsis.circle")
|
||||
Image(systemName: "doc.fill")
|
||||
.foregroundStyle(Theme.warmOrange)
|
||||
}
|
||||
}
|
||||
@@ -104,11 +90,6 @@ struct TripDetailView: View {
|
||||
ShareSheet(items: [trip.name, trip.formattedDateRange])
|
||||
}
|
||||
}
|
||||
.alert("Trip Saved", isPresented: $showSaveConfirmation) {
|
||||
Button("OK", role: .cancel) { }
|
||||
} message: {
|
||||
Text("Your trip has been saved and can be accessed from My Trips.")
|
||||
}
|
||||
.onAppear {
|
||||
checkIfSaved()
|
||||
}
|
||||
@@ -132,6 +113,22 @@ struct TripDetailView: View {
|
||||
}
|
||||
}
|
||||
.mapStyle(colorScheme == .dark ? .standard(elevation: .flat, emphasis: .muted) : .standard)
|
||||
.overlay(alignment: .topTrailing) {
|
||||
// Save/Unsave heart button
|
||||
Button {
|
||||
toggleSaved()
|
||||
} label: {
|
||||
Image(systemName: isSaved ? "heart.fill" : "heart")
|
||||
.font(.system(size: 22, weight: .medium))
|
||||
.foregroundStyle(isSaved ? .red : .white)
|
||||
.padding(12)
|
||||
.background(.ultraThinMaterial)
|
||||
.clipShape(Circle())
|
||||
.shadow(color: .black.opacity(0.2), radius: 4, y: 2)
|
||||
}
|
||||
.padding(.top, 12)
|
||||
.padding(.trailing, 12)
|
||||
}
|
||||
|
||||
// Gradient overlay at bottom
|
||||
LinearGradient(
|
||||
@@ -449,6 +446,14 @@ struct TripDetailView: View {
|
||||
showShareSheet = true
|
||||
}
|
||||
|
||||
private func toggleSaved() {
|
||||
if isSaved {
|
||||
unsaveTrip()
|
||||
} else {
|
||||
saveTrip()
|
||||
}
|
||||
}
|
||||
|
||||
private func saveTrip() {
|
||||
guard let savedTrip = SavedTrip.from(trip, games: games, status: .planned) else {
|
||||
print("Failed to create SavedTrip")
|
||||
@@ -459,13 +464,34 @@ struct TripDetailView: View {
|
||||
|
||||
do {
|
||||
try modelContext.save()
|
||||
isSaved = true
|
||||
showSaveConfirmation = true
|
||||
withAnimation(.spring(response: 0.3, dampingFraction: 0.6)) {
|
||||
isSaved = true
|
||||
}
|
||||
} catch {
|
||||
print("Failed to save trip: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
private func unsaveTrip() {
|
||||
let tripId = trip.id
|
||||
let descriptor = FetchDescriptor<SavedTrip>(
|
||||
predicate: #Predicate { $0.id == tripId }
|
||||
)
|
||||
|
||||
do {
|
||||
let savedTrips = try modelContext.fetch(descriptor)
|
||||
for savedTrip in savedTrips {
|
||||
modelContext.delete(savedTrip)
|
||||
}
|
||||
try modelContext.save()
|
||||
withAnimation(.spring(response: 0.3, dampingFraction: 0.6)) {
|
||||
isSaved = false
|
||||
}
|
||||
} catch {
|
||||
print("Failed to unsave trip: \(error)")
|
||||
}
|
||||
}
|
||||
|
||||
private func checkIfSaved() {
|
||||
let tripId = trip.id
|
||||
let descriptor = FetchDescriptor<SavedTrip>(
|
||||
|
||||
Reference in New Issue
Block a user