perf: add pagination to game selection view

Fixes lag and color inconsistency in trip creation game selection.
- Add pagination with 50 games per page to TripCreationViewModel
- Add displayedAvailableGames, loadInitialAvailableGames, loadMoreAvailableGames
- Update GamePickerSheet to use paginated data with infinite scroll trigger
- Show "Load more games" button with progress indicator and count

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-01-12 19:16:42 -06:00
parent 746b56bc77
commit 1ef3b0335e
2 changed files with 66 additions and 2 deletions

View File

@@ -112,6 +112,28 @@ final class TripCreationViewModel {
var availableGames: [RichGame] = []
var isLoadingGames: Bool = false
// MARK: - Game Pagination
private let gamePageSize = 50
var displayedAvailableGames: [RichGame] = []
private var currentGamePage = 0
var hasMoreAvailableGames: Bool {
displayedAvailableGames.count < availableGames.count
}
func loadInitialAvailableGames() {
currentGamePage = 0
displayedAvailableGames = Array(availableGames.prefix(gamePageSize))
}
func loadMoreAvailableGames() {
guard hasMoreAvailableGames else { return }
currentGamePage += 1
let start = currentGamePage * gamePageSize
let end = min(start + gamePageSize, availableGames.count)
displayedAvailableGames.append(contentsOf: availableGames[start..<end])
}
// Travel
var travelMode: TravelMode = .drive
var routePreference: RoutePreference = .balanced
@@ -276,6 +298,9 @@ final class TripCreationViewModel {
}
return RichGame(game: game, homeTeam: homeTeam, awayTeam: awayTeam, stadium: stadium)
}
// Initialize paginated display
loadInitialAvailableGames()
} catch {
viewState = .error("Failed to load schedule data: \(error.localizedDescription)")
}
@@ -501,6 +526,9 @@ final class TripCreationViewModel {
return RichGame(game: game, homeTeam: homeTeam, awayTeam: awayTeam, stadium: stadium)
}.sorted { $0.game.dateTime < $1.game.dateTime }
// Initialize paginated display
loadInitialAvailableGames()
} catch {
viewState = .error("Failed to load games: \(error.localizedDescription)")
}
@@ -541,6 +569,8 @@ final class TripCreationViewModel {
mustStopLocations = []
preferredCities = []
availableGames = []
displayedAvailableGames = []
currentGamePage = 0
isLoadingGames = false
currentPreferences = nil
allowRepeatCities = true

View File

@@ -114,8 +114,11 @@ struct TripCreationView: View {
}
.sheet(isPresented: $showGamePicker) {
GamePickerSheet(
games: viewModel.availableGames,
selectedIds: $viewModel.mustSeeGameIds
games: viewModel.displayedAvailableGames,
selectedIds: $viewModel.mustSeeGameIds,
hasMoreGames: viewModel.hasMoreAvailableGames,
totalGamesCount: viewModel.availableGames.count,
loadMoreGames: { viewModel.loadMoreAvailableGames() }
)
}
.sheet(isPresented: $showCityInput) {
@@ -452,6 +455,12 @@ struct TripCreationView: View {
.font(.subheadline)
.foregroundStyle(Theme.textSecondary(colorScheme))
}
.onAppear {
// Ensure pagination is initialized when view appears
if viewModel.displayedAvailableGames.isEmpty && !viewModel.availableGames.isEmpty {
viewModel.loadInitialAvailableGames()
}
}
Spacer()
@@ -950,6 +959,9 @@ extension TripCreationViewModel.ViewState {
struct GamePickerSheet: View {
let games: [RichGame]
@Binding var selectedIds: Set<String>
let hasMoreGames: Bool
let totalGamesCount: Int
let loadMoreGames: () -> Void
@Environment(\.dismiss) private var dismiss
@Environment(\.colorScheme) private var colorScheme
@@ -1047,6 +1059,28 @@ struct GamePickerSheet: View {
selectedCount: selectedCountForSport(sport)
)
}
// Load more indicator with infinite scroll trigger
if hasMoreGames {
Button {
loadMoreGames()
} label: {
HStack(spacing: Theme.Spacing.sm) {
ProgressView()
.tint(Theme.warmOrange)
Text("Load more games (\(games.count) of \(totalGamesCount))")
.font(.subheadline)
.foregroundStyle(Theme.textSecondary(colorScheme))
}
.frame(maxWidth: .infinity)
.padding(Theme.Spacing.md)
.background(Theme.cardBackground(colorScheme))
}
.buttonStyle(.plain)
.onAppear {
loadMoreGames()
}
}
}
}
.themedBackground()