Files
FrenoCorp/Lendair/ViewModels/FamilyPlanViewModel.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

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 }
}