perf: add pagination to schedule list

Loads 50 games at a time to fix lag with large datasets.

- Add pagination state (displayedGames, currentPage, allFilteredGames)
- Add loadInitialGames() and loadMoreGames() methods
- Update gamesBySport to use displayedGames
- Add infinite scroll trigger via onAppear on last game
- Add ProgressView indicator when more games available
- Reset pagination when search text changes

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

View File

@@ -26,6 +26,22 @@ final class ScheduleViewModel {
private let dataProvider = AppDataProvider.shared
// MARK: - Pagination
private let pageSize = 50
private(set) var displayedGames: [RichGame] = []
private var currentPage = 0
private var allFilteredGames: [RichGame] = []
var hasMoreGames: Bool {
displayedGames.count < allFilteredGames.count
}
/// The last game in the displayed list, used for infinite scroll detection
var lastDisplayedGame: RichGame? {
displayedGames.last
}
// MARK: - Computed Properties
var filteredGames: [RichGame] {
@@ -51,7 +67,7 @@ final class ScheduleViewModel {
}
var gamesBySport: [(sport: Sport, games: [RichGame])] {
let grouped = Dictionary(grouping: filteredGames) { game in
let grouped = Dictionary(grouping: displayedGames) { game in
game.game.sport
}
// Sort by sport order (use allCases index for consistent ordering)
@@ -69,11 +85,35 @@ final class ScheduleViewModel {
selectedSports.count < Sport.supported.count || !searchText.isEmpty
}
// MARK: - Pagination Actions
/// Updates filtered games list based on current search text and resets pagination
func updateFilteredGames() {
allFilteredGames = filteredGames
loadInitialGames()
}
/// Loads the first page of games
func loadInitialGames() {
currentPage = 0
displayedGames = Array(allFilteredGames.prefix(pageSize))
}
/// Loads more games when scrolling to the bottom
func loadMoreGames() {
guard hasMoreGames else { return }
currentPage += 1
let start = currentPage * pageSize
let end = min(start + pageSize, allFilteredGames.count)
displayedGames.append(contentsOf: allFilteredGames[start..<end])
}
// MARK: - Actions
func loadGames() async {
guard !selectedSports.isEmpty else {
games = []
updateFilteredGames()
return
}
@@ -92,6 +132,7 @@ final class ScheduleViewModel {
self.errorMessage = providerError
self.error = dataProvider.error
isLoading = false
updateFilteredGames()
return
}
@@ -109,6 +150,7 @@ final class ScheduleViewModel {
}
isLoading = false
updateFilteredGames()
}
func clearError() {