- 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>
79 lines
2.3 KiB
Swift
79 lines
2.3 KiB
Swift
import Foundation
|
|
import SwiftUI
|
|
|
|
@MainActor
|
|
class FamilyPlanViewModel: ObservableObject {
|
|
@Published var familyPlan: FamilyPlan?
|
|
@Published var leaderboard: [FamilyLeaderboardEntry] = []
|
|
@Published var selectedMetric: LeaderboardMetric = .distance
|
|
@Published var isLoading: Bool = false
|
|
@Published var error: FamilyPlanError?
|
|
|
|
private let service: FamilyPlanServiceProtocol
|
|
|
|
init(service: FamilyPlanServiceProtocol = FamilyPlanService()) {
|
|
self.service = service
|
|
}
|
|
|
|
func fetchFamilyPlan() async {
|
|
isLoading = true
|
|
error = nil
|
|
defer { isLoading = false }
|
|
|
|
do {
|
|
familyPlan = try await service.getFamilyPlan()
|
|
} catch let error as FamilyPlanError {
|
|
self.error = error
|
|
} catch {
|
|
print("Failed to fetch family plan: \(error)")
|
|
}
|
|
}
|
|
|
|
func inviteMember(email: String, name: String) async {
|
|
guard let plan = familyPlan, plan.availableSlots > 0 else { return }
|
|
|
|
let request = InviteMemberRequest(email: email, name: name)
|
|
do {
|
|
try await service.inviteMember(request: request)
|
|
objectWillChange.send()
|
|
} catch {
|
|
print("Failed to invite member: \(error)")
|
|
}
|
|
}
|
|
|
|
func removeMember(id: String) async {
|
|
guard let index = familyPlan?.members.firstIndex(where: { $0.id == id }) else { return }
|
|
do {
|
|
try await service.removeMember(id: id)
|
|
familyPlan?.members.remove(at: index)
|
|
objectWillChange.send()
|
|
} catch {
|
|
print("Failed to remove member: \(error)")
|
|
}
|
|
}
|
|
|
|
func fetchLeaderboard() async {
|
|
isLoading = true
|
|
error = nil
|
|
defer { isLoading = false }
|
|
|
|
do {
|
|
leaderboard = try await service.getLeaderboard(metric: selectedMetric)
|
|
} catch let error as FamilyPlanError {
|
|
self.error = error
|
|
} catch {
|
|
print("Failed to fetch leaderboard: \(error)")
|
|
}
|
|
}
|
|
|
|
var activeMembers: [FamilyMember] {
|
|
familyPlan?.members.filter { $0.role == .member || $0.role == .owner } ?? []
|
|
}
|
|
|
|
var pendingInvites: [FamilyMember] {
|
|
familyPlan?.members.filter { $0.role == .pending } ?? []
|
|
}
|
|
|
|
var metrics: [LeaderboardMetric] { LeaderboardMetric.allCases }
|
|
}
|