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>
This commit is contained in:
106
Lendair/ViewModels/RaceDiscoveryViewModel.swift
Normal file
106
Lendair/ViewModels/RaceDiscoveryViewModel.swift
Normal file
@@ -0,0 +1,106 @@
|
||||
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 }
|
||||
}
|
||||
Reference in New Issue
Block a user