Files
FrenoCorp/Lendair/ViewModels/RaceDiscoveryViewModel.swift
Michael Freno 57a460761a FRE-4665: Implement Phase 3 AI training plans and premium features
- Models: TrainingPlan, Race, FamilyPlan, BeginnerMode, CommunityEvent
- Services: 5 service layers with protocol-based architecture
- ViewModels: 5 view models with @MainActor ObservableObject pattern
- Views: 10 SwiftUI views for all Phase 3 features
- Updated README with full Phase 3 documentation

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-05-03 15:21:01 -04:00

107 lines
3.1 KiB
Swift

import Foundation
import SwiftUI
@MainActor
class RaceDiscoveryViewModel: ObservableObject {
@Published var races: [Race] = []
@Published var savedRaces: [Race] = []
@Published var selectedRace: Race?
@Published var isLoading: Bool = false
@Published var error: RaceError?
@Published var filter: RaceFilter = RaceFilter()
private let service: RaceServiceProtocol
init(service: RaceServiceProtocol = RaceService()) {
self.service = service
}
func fetchRaces() async {
isLoading = true
error = nil
defer { isLoading = false }
do {
races = try await service.listRaces(filter: filter)
} catch let error as RaceError {
self.error = error
} catch {
print("Failed to fetch races: \(error)")
}
}
func selectRace(id: String) async {
isLoading = true
error = nil
defer { isLoading = false }
do {
let race = try await service.getRace(id: id)
selectedRace = race
if let index = races.firstIndex(where: { $0.id == id }) {
races[index] = race
objectWillChange.send()
}
} catch let error as RaceError {
self.error = error
} catch {
print("Failed to get race: \(error)")
}
}
func toggleSaveRace(id: String) async {
guard let race = races.first(where: { $0.id == id }) else { return }
let newSavedState = !race.isSaved
do {
try await service.saveRace(id: id, isSaved: newSavedState)
if let index = races.firstIndex(where: { $0.id == id }) {
races[index].isSaved = newSavedState
objectWillChange.send()
}
if newSavedState {
savedRaces.append(races.first(where: { $0.id == id }) ?? race)
} else {
savedRaces.removeAll { $0.id == id }
}
objectWillChange.send()
} catch {
print("Failed to toggle save race: \(error)")
}
}
func registerForRace(id: String) async {
do {
try await service.registerForRace(id: id)
if let index = races.firstIndex(where: { $0.id == id }) {
races[index].isRegistered = true
objectWillChange.send()
}
} catch {
print("Failed to register for race: \(error)")
}
}
func fetchSavedRaces() async {
isLoading = true
error = nil
defer { isLoading = false }
do {
savedRaces = try await service.listRaces(filter: RaceFilter(limit: 50, offset: 0))
.filter { $0.isSaved }
} catch let error as RaceError {
self.error = error
} catch {
print("Failed to fetch saved races: \(error)")
}
}
var upcomingRaces: [Race] {
races.filter { $0.isUpcoming }.sorted { $0.raceDate < $1.raceDate }
}
var raceTypes: [RaceType] { RaceType.allCases }
var terrainTypes: [TerrainType] { TerrainType.allCases }
}