Stabilize beta release with warning cleanup and edge-case fixes
This commit is contained in:
@@ -16,8 +16,8 @@ final class ScheduleViewModel {
|
||||
// MARK: - Filter State
|
||||
|
||||
var selectedSports: Set<Sport> = Set(Sport.supported)
|
||||
var startDate: Date = Calendar.current.startOfDay(for: Date())
|
||||
var endDate: Date = Calendar.current.date(byAdding: .day, value: 14, to: Calendar.current.startOfDay(for: Date())) ?? Date()
|
||||
var startDate: Date = ScheduleViewModel.defaultDateRange().start
|
||||
var endDate: Date = ScheduleViewModel.defaultDateRange().end
|
||||
var searchText: String = ""
|
||||
|
||||
// MARK: - Data State
|
||||
@@ -28,7 +28,9 @@ final class ScheduleViewModel {
|
||||
private(set) var errorMessage: String?
|
||||
|
||||
private let dataProvider = AppDataProvider.shared
|
||||
@ObservationIgnored
|
||||
nonisolated(unsafe) private var loadTask: Task<Void, Never>?
|
||||
private var latestLoadRequestID = UUID()
|
||||
|
||||
// MARK: - Pre-computed Groupings (avoid computed property overhead)
|
||||
|
||||
@@ -48,14 +50,43 @@ final class ScheduleViewModel {
|
||||
}
|
||||
|
||||
var hasFilters: Bool {
|
||||
selectedSports.count < Sport.supported.count || !searchText.isEmpty
|
||||
let defaults = Self.defaultDateRange()
|
||||
let calendar = Calendar.current
|
||||
let hasDateFilter = !calendar.isDate(startDate, inSameDayAs: defaults.start) ||
|
||||
!calendar.isDate(endDate, inSameDayAs: defaults.end)
|
||||
return selectedSports.count < Sport.supported.count || !searchText.isEmpty || hasDateFilter
|
||||
}
|
||||
|
||||
private static func defaultDateRange() -> (start: Date, end: Date) {
|
||||
let calendar = Calendar.current
|
||||
let start = calendar.startOfDay(for: Date())
|
||||
let endDay = calendar.date(byAdding: .day, value: 14, to: start) ?? start
|
||||
let end = calendar.date(bySettingHour: 23, minute: 59, second: 59, of: endDay) ?? endDay
|
||||
return (start, end)
|
||||
}
|
||||
|
||||
private static func normalizedDateRange(start: Date, end: Date) -> (start: Date, end: Date) {
|
||||
let calendar = Calendar.current
|
||||
let normalizedStart = calendar.startOfDay(for: start)
|
||||
let endDay = calendar.startOfDay(for: end)
|
||||
let normalizedEnd = calendar.date(bySettingHour: 23, minute: 59, second: 59, of: endDay) ?? endDay
|
||||
return (normalizedStart, normalizedEnd)
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
func loadGames() async {
|
||||
guard !selectedSports.isEmpty else {
|
||||
let requestID = beginLoadRequest()
|
||||
let queryStartDate = startDate
|
||||
let queryEndDate = endDate
|
||||
let querySports = selectedSports
|
||||
|
||||
guard !querySports.isEmpty else {
|
||||
guard !isStaleLoad(requestID) else { return }
|
||||
games = []
|
||||
isLoading = false
|
||||
error = nil
|
||||
errorMessage = nil
|
||||
updateFilteredGames()
|
||||
return
|
||||
}
|
||||
@@ -64,11 +95,6 @@ final class ScheduleViewModel {
|
||||
error = nil
|
||||
errorMessage = nil
|
||||
|
||||
// Start diagnostics
|
||||
let queryStartDate = startDate
|
||||
let queryEndDate = endDate
|
||||
let querySports = selectedSports
|
||||
|
||||
logger.info("📅 Loading games: \(querySports.map(\.rawValue).joined(separator: ", "))")
|
||||
logger.info("📅 Date range: \(queryStartDate.formatted()) to \(queryEndDate.formatted())")
|
||||
|
||||
@@ -78,9 +104,11 @@ final class ScheduleViewModel {
|
||||
logger.info("📅 Teams empty, loading initial data...")
|
||||
await dataProvider.loadInitialData()
|
||||
}
|
||||
guard !isStaleLoad(requestID) else { return }
|
||||
|
||||
// Check if data provider had an error
|
||||
if let providerError = dataProvider.errorMessage {
|
||||
guard !isStaleLoad(requestID) else { return }
|
||||
self.errorMessage = providerError
|
||||
self.error = dataProvider.error
|
||||
isLoading = false
|
||||
@@ -92,11 +120,13 @@ final class ScheduleViewModel {
|
||||
// Log team/stadium counts for diagnostics
|
||||
logger.info("📅 Loaded \(self.dataProvider.teams.count) teams, \(self.dataProvider.stadiums.count) stadiums")
|
||||
|
||||
games = try await dataProvider.filterRichGames(
|
||||
sports: selectedSports,
|
||||
startDate: startDate,
|
||||
endDate: endDate
|
||||
let loadedGames = try await dataProvider.filterRichGames(
|
||||
sports: querySports,
|
||||
startDate: queryStartDate,
|
||||
endDate: queryEndDate
|
||||
)
|
||||
guard !isStaleLoad(requestID) else { return }
|
||||
games = loadedGames
|
||||
|
||||
// Update diagnostics
|
||||
var newDiagnostics = ScheduleDiagnostics()
|
||||
@@ -116,7 +146,7 @@ final class ScheduleViewModel {
|
||||
|
||||
self.diagnostics = newDiagnostics
|
||||
|
||||
AnalyticsManager.shared.track(.scheduleViewed(sports: Array(selectedSports).map(\.rawValue)))
|
||||
AnalyticsManager.shared.track(.scheduleViewed(sports: Array(querySports).map(\.rawValue)))
|
||||
logger.info("📅 Returned \(self.games.count) games")
|
||||
for (sport, count) in sportCounts.sorted(by: { $0.key.rawValue < $1.key.rawValue }) {
|
||||
logger.info("📅 \(sport.rawValue): \(count) games")
|
||||
@@ -133,15 +163,18 @@ final class ScheduleViewModel {
|
||||
#endif
|
||||
|
||||
} catch let cloudKitError as CloudKitError {
|
||||
guard !isStaleLoad(requestID) else { return }
|
||||
self.error = cloudKitError
|
||||
self.errorMessage = cloudKitError.errorDescription
|
||||
logger.error("📅 CloudKit error: \(cloudKitError.errorDescription ?? "unknown")")
|
||||
} catch {
|
||||
guard !isStaleLoad(requestID) else { return }
|
||||
self.error = error
|
||||
self.errorMessage = error.localizedDescription
|
||||
logger.error("📅 Error loading games: \(error.localizedDescription)")
|
||||
}
|
||||
|
||||
guard !isStaleLoad(requestID) else { return }
|
||||
isLoading = false
|
||||
updateFilteredGames()
|
||||
}
|
||||
@@ -165,15 +198,17 @@ final class ScheduleViewModel {
|
||||
func resetFilters() {
|
||||
selectedSports = Set(Sport.supported)
|
||||
searchText = ""
|
||||
startDate = Date()
|
||||
endDate = Calendar.current.date(byAdding: .day, value: 14, to: Date()) ?? Date()
|
||||
let defaults = Self.defaultDateRange()
|
||||
startDate = defaults.start
|
||||
endDate = defaults.end
|
||||
loadTask?.cancel()
|
||||
loadTask = Task { await loadGames() }
|
||||
}
|
||||
|
||||
func updateDateRange(start: Date, end: Date) {
|
||||
startDate = start
|
||||
endDate = end
|
||||
let normalized = Self.normalizedDateRange(start: start, end: end)
|
||||
startDate = normalized.start
|
||||
endDate = normalized.end
|
||||
loadTask?.cancel()
|
||||
loadTask = Task { await loadGames() }
|
||||
}
|
||||
@@ -212,6 +247,16 @@ final class ScheduleViewModel {
|
||||
return lhs.game.dateTime < rhs.game.dateTime
|
||||
}) }
|
||||
}
|
||||
|
||||
private func beginLoadRequest() -> UUID {
|
||||
let requestID = UUID()
|
||||
latestLoadRequestID = requestID
|
||||
return requestID
|
||||
}
|
||||
|
||||
private func isStaleLoad(_ requestID: UUID) -> Bool {
|
||||
Task.isCancelled || latestLoadRequestID != requestID
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Diagnostics Model
|
||||
|
||||
Reference in New Issue
Block a user