diff --git a/SportsTime/Features/Home/Views/HomeView.swift b/SportsTime/Features/Home/Views/HomeView.swift index b06b975..0011556 100644 --- a/SportsTime/Features/Home/Views/HomeView.swift +++ b/SportsTime/Features/Home/Views/HomeView.swift @@ -48,7 +48,6 @@ struct HomeView: View { .padding(Theme.Spacing.md) } .themedBackground() - .navigationTitle("SportsTime") .toolbar { ToolbarItem(placement: .primaryAction) { Button { @@ -470,35 +469,45 @@ struct SavedTripsListView: View { @Environment(\.colorScheme) private var colorScheme var body: some View { - Group { + ScrollView { if trips.isEmpty { - EmptyStateView( - icon: "suitcase", - title: "No Saved Trips", - message: "Your planned adventures will appear here. Start planning your first sports road trip!" - ) - .frame(maxWidth: .infinity, maxHeight: .infinity) + VStack(spacing: 16) { + Spacer() + .frame(height: 100) + + Image(systemName: "suitcase") + .font(.system(size: 60)) + .foregroundColor(.secondary) + + Text("No Saved Trips") + .font(.title2) + .fontWeight(.semibold) + + Text("Browse featured trips on the Home tab or create your own to get started.") + .font(.subheadline) + .foregroundColor(.secondary) + .multilineTextAlignment(.center) + .padding(.horizontal, 40) + } + .frame(maxWidth: .infinity) } else { - ScrollView { - LazyVStack(spacing: Theme.Spacing.md) { - ForEach(Array(trips.enumerated()), id: \.element.id) { index, savedTrip in - if let trip = savedTrip.trip { - NavigationLink { - TripDetailView(trip: trip, games: savedTrip.games) - } label: { - SavedTripListRow(trip: trip) - } - .buttonStyle(.plain) - .staggeredAnimation(index: index) + LazyVStack(spacing: Theme.Spacing.md) { + ForEach(Array(trips.enumerated()), id: \.element.id) { index, savedTrip in + if let trip = savedTrip.trip { + NavigationLink { + TripDetailView(trip: trip, games: savedTrip.games) + } label: { + SavedTripListRow(trip: trip) } + .buttonStyle(.plain) + .staggeredAnimation(index: index) } } - .padding(Theme.Spacing.md) } + .padding(Theme.Spacing.md) } } .themedBackground() - .navigationTitle("My Trips") } } diff --git a/SportsTime/Features/Schedule/Views/ScheduleListView.swift b/SportsTime/Features/Schedule/Views/ScheduleListView.swift index 595a062..3eba2a2 100644 --- a/SportsTime/Features/Schedule/Views/ScheduleListView.swift +++ b/SportsTime/Features/Schedule/Views/ScheduleListView.swift @@ -21,8 +21,9 @@ struct ScheduleListView: View { gamesList } } - .navigationTitle("Schedule") .searchable(text: $viewModel.searchText, prompt: "Search teams or venues") + .scrollContentBackground(.hidden) + .themedBackground() .toolbar { ToolbarItem(placement: .primaryAction) { Menu { diff --git a/SportsTime/Features/Settings/Views/SettingsView.swift b/SportsTime/Features/Settings/Views/SettingsView.swift index e7f0770..0b55685 100644 --- a/SportsTime/Features/Settings/Views/SettingsView.swift +++ b/SportsTime/Features/Settings/Views/SettingsView.swift @@ -29,7 +29,8 @@ struct SettingsView: View { // Reset resetSection } - .navigationTitle("Settings") + .scrollContentBackground(.hidden) + .themedBackground() .alert("Reset Settings", isPresented: $showResetConfirmation) { Button("Cancel", role: .cancel) { } Button("Reset", role: .destructive) { diff --git a/SportsTime/Features/Trip/Views/TripCreationView.swift b/SportsTime/Features/Trip/Views/TripCreationView.swift index 61c635d..b499dbf 100644 --- a/SportsTime/Features/Trip/Views/TripCreationView.swift +++ b/SportsTime/Features/Trip/Views/TripCreationView.swift @@ -82,8 +82,7 @@ struct TripCreationView: View { .padding(Theme.Spacing.md) } .themedBackground() - .navigationTitle("Plan Your Trip") - .toolbar { + .toolbar { ToolbarItem(placement: .cancellationAction) { Button("Cancel") { dismiss() @@ -551,21 +550,6 @@ struct TripCreationView: View { onIncrement: { viewModel.numberOfDrivers += 1 }, onDecrement: { viewModel.numberOfDrivers -= 1 } ) - - VStack(alignment: .leading, spacing: Theme.Spacing.xs) { - HStack { - Text("Max Hours/Driver/Day") - .font(.system(size: Theme.FontSize.caption)) - .foregroundStyle(Theme.textSecondary(colorScheme)) - Spacer() - Text("\(Int(viewModel.maxDrivingHoursPerDriver))h") - .font(.system(size: Theme.FontSize.caption, weight: .bold)) - .foregroundStyle(Theme.warmOrange) - } - Slider(value: $viewModel.maxDrivingHoursPerDriver, in: 4...12, step: 1) - .tint(Theme.warmOrange) - } - } } } @@ -1132,8 +1116,6 @@ struct TripOptionsView: View { .padding(.bottom, Theme.Spacing.xxl) } .themedBackground() - .navigationTitle("Choose Your Trip") - .navigationBarTitleDisplayMode(.inline) .navigationDestination(isPresented: $showTripDetail) { if let trip = selectedTrip { TripDetailView(trip: trip, games: games) diff --git a/SportsTime/Features/Trip/Views/TripDetailView.swift b/SportsTime/Features/Trip/Views/TripDetailView.swift index 301c3b1..e16e11a 100644 --- a/SportsTime/Features/Trip/Views/TripDetailView.swift +++ b/SportsTime/Features/Trip/Views/TripDetailView.swift @@ -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( + 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(