Files
FrenoCorp/Lendair/Models/TrainingPlan.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

313 lines
7.7 KiB
Swift

import Foundation
import SwiftUI
// MARK: - Training Plan
struct TrainingPlan: Identifiable, Equatable, Codable {
let id: String
let title: String
let description: String
let planType: PlanType
let durationWeeks: Int
let difficulty: Difficulty
let startDate: Date
let endDate: Date
let weeklyWorkouts: [WeeklyWorkout]
var progress: PlanProgress
var isFollowing: Bool
let createdAt: Date
enum CodingKeys: String, CodingKey {
case id, title, description, planType, durationWeeks, difficulty, startDate, endDate, weeklyWorkouts, progress, isFollowing, createdAt
}
init(
id: String,
title: String,
description: String,
planType: PlanType,
durationWeeks: Int,
difficulty: Difficulty,
startDate: Date,
endDate: Date,
weeklyWorkouts: [WeeklyWorkout],
progress: PlanProgress,
isFollowing: Bool,
createdAt: Date
) {
self.id = id
self.title = title
self.description = description
self.planType = planType
self.durationWeeks = durationWeeks
self.difficulty = difficulty
self.startDate = startDate
self.endDate = endDate
self.weeklyWorkouts = weeklyWorkouts
self.progress = progress
self.isFollowing = isFollowing
self.createdAt = createdAt
}
static func == (lhs: TrainingPlan, rhs: TrainingPlan) -> Bool {
lhs.id == rhs.id
}
}
// MARK: - Plan Type
enum PlanType: String, CaseIterable, Codable {
case fiveK = "5K"
case tenK = "10K"
case halfMarathon = "HALF_MARATHON"
case fullMarathon = "FULL_MARATHON"
case custom = "CUSTOM"
var displayName: String {
switch self {
case .fiveK: return "5K"
case .tenK: return "10K"
case .halfMarathon: return "Half Marathon"
case .fullMarathon: return "Full Marathon"
case .custom: return "Custom"
}
}
var distanceKm: Double {
switch self {
case .fiveK: return 5.0
case .tenK: return 10.0
case .halfMarathon: return 21.1
case .fullMarathon: return 42.2
case .custom: return 0.0
}
}
var icon: String {
switch self {
case .fiveK: return "figure.run"
case .tenK: return "figure.run"
case .halfMarathon: return "flag.fill"
case .fullMarathon: return "flag.fill"
case .custom: return "wrench.and.screwdriver"
}
}
}
// MARK: - Difficulty
enum Difficulty: String, CaseIterable, Codable {
case beginner
case intermediate
case advanced
case elite
var displayName: String {
switch self {
case .beginner: return "Beginner"
case .intermediate: return "Intermediate"
case .advanced: return "Advanced"
case .elite: return "Elite"
}
}
var color: Color {
switch self {
case .beginner: return .green
case .intermediate: return .blue
case .advanced: return .orange
case .elite: return .red
}
}
}
// MARK: - Weekly Workout
struct WeeklyWorkout: Identifiable, Codable {
let id: String
let weekNumber: Int
let dailySessions: [DailySession]
var completedSessions: Int {
dailySessions.filter { $0.status == .completed }.count
}
var totalSessions: Int {
dailySessions.count
}
var progressPercentage: Double {
totalSessions == 0 ? 0 : Double(completedSessions) / Double(totalSessions) * 100
}
}
// MARK: - Daily Session
struct DailySession: Identifiable, Codable {
let id: String
let dayOfWeek: DayOfWeek
let workoutType: WorkoutType
let title: String
let description: String
let targetDistanceKm: Double?
let targetDurationMinutes: Int?
let targetPaceMinPerKm: Double?
let intensity: Intensity
var status: SessionStatus
var completedDistanceKm: Double?
var completedDurationMinutes: Int?
enum CodingKeys: String, CodingKey {
case id, dayOfWeek, workoutType, title, description, targetDistanceKm, targetDurationMinutes, targetPaceMinPerKm, intensity, status, completedDistanceKm, completedDurationMinutes
}
}
// MARK: - Day of Week
enum DayOfWeek: String, CaseIterable, Codable {
case monday, tuesday, wednesday, thursday, friday, saturday, sunday
var displayName: String {
switch self {
case .monday: return "Mon"
case .tuesday: return "Tue"
case .wednesday: return "Wed"
case .thursday: return "Thu"
case .friday: return "Fri"
case .saturday: return "Sat"
case .sunday: return "Sun"
}
}
}
// MARK: - Workout Type
enum WorkoutType: String, CaseIterable, Codable {
case easyRun
case tempoRun
case intervalTraining
case longRun
case speedWork
case recoveryRun
case crossTraining
case rest
var displayName: String {
switch self {
case .easyRun: return "Easy Run"
case .tempoRun: return "Tempo Run"
case .intervalTraining: return "Intervals"
case .longRun: return "Long Run"
case .speedWork: return "Speed Work"
case .recoveryRun: return "Recovery Run"
case .crossTraining: return "Cross Train"
case .rest: return "Rest"
}
}
var icon: String {
switch self {
case .easyRun: return "figure.walk"
case .tempoRun: return "figure.run"
case .intervalTraining: return "bolt.fill"
case .longRun: return "figure.run"
case .speedWork: return "speedometer"
case .recoveryRun: return "leaf.fill"
case .crossTraining: return "dumbbell.fill"
case .rest: return "moon.fill"
}
}
var color: Color {
switch self {
case .easyRun: return .green
case .tempoRun: return .blue
case .intervalTraining: return .purple
case .longRun: return .orange
case .speedWork: return .red
case .recoveryRun: return .mint
case .crossTraining: return .gray
case .rest: return .secondary
}
}
}
// MARK: - Intensity
enum Intensity: String, CaseIterable, Codable {
case veryEasy
case easy
case moderate
case hard
case veryHard
var displayName: String {
switch self {
case .veryEasy: return "Very Easy"
case .easy: return "Easy"
case .moderate: return "Moderate"
case .hard: return "Hard"
case .veryHard: return "Very Hard"
}
}
}
// MARK: - Session Status
enum SessionStatus: String, CaseIterable, Codable {
case pending
case inProgress
case completed
case skipped
}
// MARK: - Plan Progress
struct PlanProgress: Codable {
let completedWeeks: Int
let totalWeeks: Int
let completedSessions: Int
let totalSessions: Int
let currentWeekNumber: Int
var percentage: Double {
totalWeeks == 0 ? 0 : Double(completedWeeks) / Double(totalWeeks) * 100
}
var sessionPercentage: Double {
totalSessions == 0 ? 0 : Double(completedSessions) / Double(totalSessions) * 100
}
}
// MARK: - AI Plan Generation Request
struct GeneratePlanRequest: Encodable {
let planType: PlanType
let difficulty: Difficulty
let startDate: Date
let currentWeeklyMileageKm: Double?
let goalTimeMinutes: Int?
let availableDays: [DayOfWeek]
}
// MARK: - API Response Types
struct TrainingPlanListResponse: Decodable {
let plans: [TrainingPlan]
let hasMore: Bool
}
struct TrainingPlanDetailResponse: Decodable {
let plan: TrainingPlan
}
struct GeneratePlanResponse: Decodable {
let plan: TrainingPlan
}
struct UpdateSessionStatusResponse: Decodable {
let success: Bool
let sessionId: String
let status: SessionStatus
}