- 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>
109 lines
3.4 KiB
Swift
109 lines
3.4 KiB
Swift
import Foundation
|
|
import SwiftUI
|
|
|
|
@MainActor
|
|
class TrainingPlanViewModel: ObservableObject {
|
|
@Published var plans: [TrainingPlan] = []
|
|
@Published var selectedPlan: TrainingPlan?
|
|
@Published var isLoading: Bool = false
|
|
@Published var error: TrainingPlanError?
|
|
|
|
private let service: TrainingPlanServiceProtocol
|
|
|
|
init(service: TrainingPlanServiceProtocol = TrainingPlanService()) {
|
|
self.service = service
|
|
}
|
|
|
|
func fetchPlans(type: PlanType? = nil, difficulty: Difficulty? = nil) async {
|
|
isLoading = true
|
|
error = nil
|
|
defer { isLoading = false }
|
|
|
|
do {
|
|
plans = try await service.listPlans(type: type, difficulty: difficulty)
|
|
} catch let error as TrainingPlanError {
|
|
self.error = error
|
|
} catch {
|
|
print("Failed to fetch training plans: \(error)")
|
|
}
|
|
}
|
|
|
|
func selectPlan(id: String) async {
|
|
isLoading = true
|
|
error = nil
|
|
defer { isLoading = false }
|
|
|
|
do {
|
|
selectedPlan = try await service.getPlan(id: id)
|
|
} catch let error as TrainingPlanError {
|
|
self.error = error
|
|
} catch {
|
|
print("Failed to get plan: \(error)")
|
|
}
|
|
}
|
|
|
|
func generatePlan(request: GeneratePlanRequest) async -> TrainingPlan? {
|
|
isLoading = true
|
|
error = nil
|
|
defer { isLoading = false }
|
|
|
|
do {
|
|
let plan = try await service.generatePlan(request: request)
|
|
plans.insert(plan, at: 0)
|
|
return plan
|
|
} catch let error as TrainingPlanError {
|
|
self.error = error
|
|
return nil
|
|
} catch {
|
|
print("Failed to generate plan: \(error)")
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func followPlan(id: String) async {
|
|
do {
|
|
try await service.followPlan(id: id)
|
|
if let index = plans.firstIndex(where: { $0.id == id }) {
|
|
plans[index].isFollowing = true
|
|
objectWillChange.send()
|
|
}
|
|
} catch {
|
|
print("Failed to follow plan: \(error)")
|
|
}
|
|
}
|
|
|
|
func unfollowPlan(id: String) async {
|
|
do {
|
|
try await service.unfollowPlan(id: id)
|
|
if let index = plans.firstIndex(where: { $0.id == id }) {
|
|
plans[index].isFollowing = false
|
|
objectWillChange.send()
|
|
}
|
|
} catch {
|
|
print("Failed to unfollow plan: \(error)")
|
|
}
|
|
}
|
|
|
|
func updateSessionStatus(sessionId: String, status: SessionStatus) async {
|
|
do {
|
|
try await service.updateSessionStatus(sessionId: sessionId, status: status)
|
|
if var plan = selectedPlan {
|
|
for weekIndex in plan.weeklyWorkouts.indices {
|
|
for sessionIndex in plan.weeklyWorkouts[weekIndex].dailySessions.indices {
|
|
if plan.weeklyWorkouts[weekIndex].dailySessions[sessionIndex].id == sessionId {
|
|
plan.weeklyWorkouts[weekIndex].dailySessions[sessionIndex].status = status
|
|
selectedPlan = plan
|
|
return
|
|
}
|
|
}
|
|
}
|
|
}
|
|
} catch {
|
|
print("Failed to update session status: \(error)")
|
|
}
|
|
}
|
|
|
|
var planTypes: [PlanType] { PlanType.allCases }
|
|
var difficulties: [Difficulty] { Difficulty.allCases }
|
|
}
|