diff --git a/SportsTime/Features/Progress/ViewModels/ProgressViewModel.swift b/SportsTime/Features/Progress/ViewModels/ProgressViewModel.swift index 7469d73..9223c8a 100644 --- a/SportsTime/Features/Progress/ViewModels/ProgressViewModel.swift +++ b/SportsTime/Features/Progress/ViewModels/ProgressViewModel.swift @@ -16,7 +16,7 @@ final class ProgressViewModel { // MARK: - State var selectedSport: Sport = .mlb - var isLoading = false + var isLoading = true var error: Error? var errorMessage: String? @@ -42,10 +42,8 @@ final class ProgressViewModel { visits .filter { $0.sportEnum == selectedSport } .compactMap { visit -> String? in - // Match visit's canonical stadium ID to a stadium - stadiums.first { stadium in - stadium.id == visit.stadiumId - }?.id + // O(1) dictionary lookup via DataProvider + dataProvider.stadium(for: visit.stadiumId)?.id } ) @@ -120,7 +118,7 @@ final class ProgressViewModel { .sorted { $0.visitDate > $1.visitDate } .prefix(10) .compactMap { visit -> VisitSummary? in - guard let stadium = stadiums.first(where: { $0.id == visit.stadiumId }), + guard let stadium = dataProvider.stadium(for: visit.stadiumId), let sport = visit.sportEnum else { return nil } diff --git a/SportsTime/Features/Progress/Views/ProgressTabView.swift b/SportsTime/Features/Progress/Views/ProgressTabView.swift index d9e0ad6..4640ce9 100644 --- a/SportsTime/Features/Progress/Views/ProgressTabView.swift +++ b/SportsTime/Features/Progress/Views/ProgressTabView.swift @@ -20,46 +20,19 @@ struct ProgressTabView: View { @Query private var visits: [StadiumVisit] + /// O(1) lookup for visits by ID (built from @Query results) + private var visitsById: [UUID: StadiumVisit] { + Dictionary(uniqueKeysWithValues: visits.map { ($0.id, $0) }) + } + var body: some View { - ScrollView { - VStack(spacing: Theme.Spacing.lg) { - // League Selector - leagueSelector - .staggeredAnimation(index: 0) - - // Progress Summary Card - progressSummaryCard - .staggeredAnimation(index: 1) - - // Map View - ProgressMapView( - stadiums: viewModel.sportStadiums, - visitStatus: viewModel.stadiumVisitStatus, - selectedStadium: $selectedStadium - ) - .frame(height: 300) - .clipShape(RoundedRectangle(cornerRadius: Theme.CornerRadius.large)) - .overlay { - RoundedRectangle(cornerRadius: Theme.CornerRadius.large) - .stroke(Theme.surfaceGlow(colorScheme), lineWidth: 1) - } - .staggeredAnimation(index: 2) - - // Stadium Lists - stadiumListsSection - .staggeredAnimation(index: 3) - - // Achievements Teaser - achievementsSection - .staggeredAnimation(index: 4) - - // Recent Visits - if !viewModel.recentVisits.isEmpty { - recentVisitsSection - .staggeredAnimation(index: 5) - } + Group { + if viewModel.stadiums.isEmpty { + ProgressView("Loading...") + .frame(maxWidth: .infinity, maxHeight: .infinity) + } else { + scrollContent } - .padding(Theme.Spacing.md) } .themedBackground() .toolbar { @@ -126,6 +99,51 @@ struct ProgressTabView: View { } } + // MARK: - Scroll Content + + private var scrollContent: some View { + ScrollView { + VStack(spacing: Theme.Spacing.lg) { + // League Selector + leagueSelector + .staggeredAnimation(index: 0) + + // Progress Summary Card + progressSummaryCard + .staggeredAnimation(index: 1) + + // Map View + ProgressMapView( + stadiums: viewModel.sportStadiums, + visitStatus: viewModel.stadiumVisitStatus, + selectedStadium: $selectedStadium + ) + .frame(height: 300) + .clipShape(RoundedRectangle(cornerRadius: Theme.CornerRadius.large)) + .overlay { + RoundedRectangle(cornerRadius: Theme.CornerRadius.large) + .stroke(Theme.surfaceGlow(colorScheme), lineWidth: 1) + } + .staggeredAnimation(index: 2) + + // Stadium Lists + stadiumListsSection + .staggeredAnimation(index: 3) + + // Achievements Teaser + achievementsSection + .staggeredAnimation(index: 4) + + // Recent Visits + if !viewModel.recentVisits.isEmpty { + recentVisitsSection + .staggeredAnimation(index: 5) + } + } + .padding(Theme.Spacing.md) + } + } + // MARK: - League Selector private var leagueSelector: some View { @@ -385,7 +403,7 @@ struct ProgressTabView: View { } ForEach(viewModel.recentVisits) { visitSummary in - if let stadiumVisit = visits.first(where: { $0.id == visitSummary.id }) { + if let stadiumVisit = visitsById[visitSummary.id] { NavigationLink { VisitDetailView(visit: stadiumVisit, stadium: visitSummary.stadium) } label: {