From 746b56bc7763fbbaa9ca28a67bc7ee4b712eea5b Mon Sep 17 00:00:00 2001 From: Trey t Date: Mon, 12 Jan 2026 19:07:54 -0600 Subject: [PATCH] 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 --- .../ViewModels/ScheduleViewModel.swift | 44 ++++++++++++++++++- .../Schedule/Views/ScheduleListView.swift | 25 ++++++++++- 2 files changed, 66 insertions(+), 3 deletions(-) diff --git a/SportsTime/Features/Schedule/ViewModels/ScheduleViewModel.swift b/SportsTime/Features/Schedule/ViewModels/ScheduleViewModel.swift index 8a505b1..7be2438 100644 --- a/SportsTime/Features/Schedule/ViewModels/ScheduleViewModel.swift +++ b/SportsTime/Features/Schedule/ViewModels/ScheduleViewModel.swift @@ -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..