- 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>
107 lines
3.1 KiB
Swift
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 }
|
|
}
|