// // HomeView.swift // SportsTime // import SwiftUI import SwiftData struct HomeView: View { @Environment(\.modelContext) private var modelContext @Environment(\.colorScheme) private var colorScheme @Query(sort: \SavedTrip.updatedAt, order: .reverse) private var savedTrips: [SavedTrip] @State private var showNewTrip = false @State private var selectedSport: Sport? @State private var selectedTab = 0 @State private var suggestedTripsGenerator = SuggestedTripsGenerator() @State private var selectedSuggestedTrip: SuggestedTrip? @State private var displayedTips: [PlanningTip] = [] var body: some View { TabView(selection: $selectedTab) { // Home Tab NavigationStack { ScrollView { VStack(spacing: Theme.Spacing.xl) { // Hero Card heroCard .staggeredAnimation(index: 0) // Quick Actions quickActions .staggeredAnimation(index: 1) // Suggested Trips suggestedTripsSection .staggeredAnimation(index: 2) // Saved Trips if !savedTrips.isEmpty { savedTripsSection .staggeredAnimation(index: 3) } // Featured / Tips tipsSection .staggeredAnimation(index: 4) } .padding(Theme.Spacing.md) } .themedBackground() .toolbar { ToolbarItem(placement: .primaryAction) { Button { showNewTrip = true } label: { Image(systemName: "plus.circle.fill") .font(.title2) .foregroundStyle(Theme.warmOrange) } } } } .tabItem { Label("Home", systemImage: "house.fill") } .tag(0) // Schedule Tab NavigationStack { ScheduleListView() } .tabItem { Label("Schedule", systemImage: "calendar") } .tag(1) // My Trips Tab NavigationStack { SavedTripsListView(trips: savedTrips) } .tabItem { Label("My Trips", systemImage: "suitcase.fill") } .tag(2) // Progress Tab NavigationStack { ProgressTabView() } .tabItem { Label("Progress", systemImage: "chart.bar.fill") } .tag(3) // Settings Tab NavigationStack { SettingsView() } .tabItem { Label("Settings", systemImage: "gear") } .tag(4) } .tint(Theme.warmOrange) .sheet(isPresented: $showNewTrip) { TripWizardView() } .onChange(of: showNewTrip) { _, isShowing in if !isShowing { selectedSport = nil } } .task { if suggestedTripsGenerator.suggestedTrips.isEmpty && !suggestedTripsGenerator.isLoading { await suggestedTripsGenerator.generateTrips() } } .sheet(item: $selectedSuggestedTrip) { suggestedTrip in NavigationStack { TripDetailView(trip: suggestedTrip.trip, games: suggestedTrip.richGames) } } } // MARK: - Hero Card private var heroCard: some View { VStack(spacing: Theme.Spacing.lg) { VStack(alignment: .leading, spacing: Theme.Spacing.sm) { Text("Adventure Awaits") .font(.largeTitle) .foregroundStyle(Theme.textPrimary(colorScheme)) Text("Plan your ultimate sports road trip. Visit stadiums, catch games, and create unforgettable memories.") .font(.body) .foregroundStyle(Theme.textSecondary(colorScheme)) .fixedSize(horizontal: false, vertical: true) } .frame(maxWidth: .infinity, alignment: .leading) Button { showNewTrip = true } label: { HStack(spacing: Theme.Spacing.xs) { Image(systemName: "map.fill") Text("Start Planning") .fontWeight(.semibold) } .frame(maxWidth: .infinity) .padding(Theme.Spacing.md) .background(Theme.warmOrange) .foregroundStyle(.white) .clipShape(RoundedRectangle(cornerRadius: Theme.CornerRadius.medium)) } .pressableStyle() .glowEffect(color: Theme.warmOrange, radius: 12) } .padding(Theme.Spacing.lg) .background(Theme.cardBackground(colorScheme)) .clipShape(RoundedRectangle(cornerRadius: Theme.CornerRadius.xlarge)) .overlay { RoundedRectangle(cornerRadius: Theme.CornerRadius.xlarge) .stroke(Theme.surfaceGlow(colorScheme), lineWidth: 1) } .shadow(color: Theme.cardShadow(colorScheme), radius: 15, y: 8) } // MARK: - Quick Actions private var quickActions: some View { VStack(alignment: .leading, spacing: Theme.Spacing.sm) { Text("Quick Start") .font(.title2) .foregroundStyle(Theme.textPrimary(colorScheme)) SportSelectorGrid { sport in selectedSport = sport showNewTrip = true } .padding(.horizontal, Theme.Spacing.md) .padding(.vertical, Theme.Spacing.md) .background(Theme.cardBackground(colorScheme)) .clipShape(RoundedRectangle(cornerRadius: Theme.CornerRadius.large)) .overlay { RoundedRectangle(cornerRadius: Theme.CornerRadius.large) .stroke(Theme.surfaceGlow(colorScheme), lineWidth: 1) } } } // MARK: - Suggested Trips @ViewBuilder private var suggestedTripsSection: some View { if suggestedTripsGenerator.isLoading { LoadingTripsView(message: suggestedTripsGenerator.loadingMessage) } else if !suggestedTripsGenerator.suggestedTrips.isEmpty { VStack(alignment: .leading, spacing: Theme.Spacing.sm) { // Header with refresh button HStack { Text("Featured Trips") .font(.title2) .foregroundStyle(Theme.textPrimary(colorScheme)) Spacer() Button { Task { await suggestedTripsGenerator.refreshTrips() } } label: { Image(systemName: "arrow.clockwise") .font(.subheadline) .foregroundStyle(Theme.warmOrange) } } // Horizontal carousel grouped by region ScrollView(.horizontal, showsIndicators: false) { HStack(spacing: Theme.Spacing.lg) { ForEach(suggestedTripsGenerator.tripsByRegion, id: \.region) { regionGroup in VStack(alignment: .leading, spacing: Theme.Spacing.sm) { // Region header HStack(spacing: Theme.Spacing.xs) { Image(systemName: regionGroup.region.iconName) .font(.caption) Text(regionGroup.region.shortName) .font(.subheadline) } .foregroundStyle(Theme.textSecondary(colorScheme)) // Trip cards for this region HStack(spacing: Theme.Spacing.md) { ForEach(regionGroup.trips) { suggestedTrip in Button { selectedSuggestedTrip = suggestedTrip } label: { SuggestedTripCard(suggestedTrip: suggestedTrip) } .buttonStyle(.plain) } } } } } .padding(.horizontal, 1) // Prevent clipping } } } else if let error = suggestedTripsGenerator.error { // Error state VStack(alignment: .leading, spacing: Theme.Spacing.sm) { Text("Featured Trips") .font(.title2) .foregroundStyle(Theme.textPrimary(colorScheme)) HStack { Image(systemName: "exclamationmark.triangle") .foregroundStyle(.orange) Text(error) .font(.subheadline) .foregroundStyle(Theme.textSecondary(colorScheme)) Spacer() Button("Retry") { Task { await suggestedTripsGenerator.generateTrips() } } .font(.subheadline) .foregroundStyle(Theme.warmOrange) } .padding(Theme.Spacing.md) .background(Theme.cardBackground(colorScheme)) .clipShape(RoundedRectangle(cornerRadius: Theme.CornerRadius.medium)) } } } // MARK: - Saved Trips private var savedTripsSection: some View { VStack(alignment: .leading, spacing: Theme.Spacing.sm) { HStack { Text("Recent Trips") .font(.title2) .foregroundStyle(Theme.textPrimary(colorScheme)) Spacer() Button { selectedTab = 2 } label: { HStack(spacing: 4) { Text("See All") Image(systemName: "chevron.right") .font(.caption) } .font(.subheadline) .foregroundStyle(Theme.warmOrange) } } ForEach(Array(savedTrips.prefix(3).enumerated()), id: \.element.id) { index, savedTrip in if let trip = savedTrip.trip { SavedTripCard(savedTrip: savedTrip, trip: trip) .staggeredAnimation(index: index, delay: 0.05) } } } } // MARK: - Tips private var tipsSection: some View { VStack(alignment: .leading, spacing: Theme.Spacing.sm) { Text("Planning Tips") .font(.title2) .foregroundStyle(Theme.textPrimary(colorScheme)) VStack(spacing: Theme.Spacing.xs) { ForEach(displayedTips) { tip in TipRow(icon: tip.icon, title: tip.title, subtitle: tip.subtitle) } } .padding(Theme.Spacing.md) .background(Theme.cardBackground(colorScheme)) .clipShape(RoundedRectangle(cornerRadius: Theme.CornerRadius.large)) .overlay { RoundedRectangle(cornerRadius: Theme.CornerRadius.large) .stroke(Theme.surfaceGlow(colorScheme), lineWidth: 1) } } .onAppear { if displayedTips.isEmpty { displayedTips = PlanningTips.random(3) } } } } // MARK: - Supporting Views struct SavedTripCard: View { let savedTrip: SavedTrip let trip: Trip @Environment(\.colorScheme) private var colorScheme var body: some View { NavigationLink { TripDetailView(trip: trip, games: savedTrip.games) } label: { HStack(spacing: Theme.Spacing.md) { // Route preview icon ZStack { Circle() .fill(Theme.warmOrange.opacity(0.15)) .frame(width: 44, height: 44) Image(systemName: "map.fill") .foregroundStyle(Theme.warmOrange) } VStack(alignment: .leading, spacing: 4) { Text(trip.name) .font(.body) .foregroundStyle(Theme.textPrimary(colorScheme)) Text(trip.formattedDateRange) .font(.subheadline) .foregroundStyle(Theme.textSecondary(colorScheme)) HStack(spacing: Theme.Spacing.sm) { HStack(spacing: 4) { Image(systemName: "mappin") .font(.caption2) Text("\(trip.stops.count) cities") } HStack(spacing: 4) { Image(systemName: "sportscourt") .font(.caption2) Text("\(trip.totalGames) games") } } .font(.caption) .foregroundStyle(Theme.textMuted(colorScheme)) } Spacer() Image(systemName: "chevron.right") .font(.caption) .foregroundStyle(Theme.textMuted(colorScheme)) } .padding(Theme.Spacing.md) .background(Theme.cardBackground(colorScheme)) .clipShape(RoundedRectangle(cornerRadius: Theme.CornerRadius.medium)) .overlay { RoundedRectangle(cornerRadius: Theme.CornerRadius.medium) .stroke(Theme.surfaceGlow(colorScheme), lineWidth: 1) } } .buttonStyle(.plain) } } struct TipRow: View { let icon: String let title: String let subtitle: String @Environment(\.colorScheme) private var colorScheme var body: some View { HStack(spacing: Theme.Spacing.sm) { ZStack { Circle() .fill(Theme.routeGold.opacity(0.15)) .frame(width: 36, height: 36) Image(systemName: icon) .font(.subheadline) .foregroundStyle(Theme.routeGold) } VStack(alignment: .leading, spacing: 2) { Text(title) .font(.subheadline) .foregroundStyle(Theme.textPrimary(colorScheme)) Text(subtitle) .font(.caption) .foregroundStyle(Theme.textSecondary(colorScheme)) } Spacer() } } } // MARK: - Saved Trips List View struct SavedTripsListView: View { let trips: [SavedTrip] @Environment(\.colorScheme) private var colorScheme /// Trips sorted by most cities (stops) first private var sortedTrips: [SavedTrip] { trips.sorted { ($0.trip?.stops.count ?? 0) > ($1.trip?.stops.count ?? 0) } } var body: some View { ScrollView { if trips.isEmpty { VStack(spacing: 16) { Spacer() .frame(height: 100) Image(systemName: "suitcase") .font(.largeTitle) .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 { LazyVStack(spacing: Theme.Spacing.md) { ForEach(Array(sortedTrips.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) } } .themedBackground() } } struct SavedTripListRow: View { let trip: Trip @Environment(\.colorScheme) private var colorScheme var body: some View { HStack(spacing: Theme.Spacing.md) { // Route preview VStack(spacing: 4) { ForEach(0..