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:
183
Lendair/Models/Race.swift
Normal file
183
Lendair/Models/Race.swift
Normal file
@@ -0,0 +1,183 @@
|
||||
import Foundation
|
||||
import SwiftUI
|
||||
|
||||
// MARK: - Race
|
||||
|
||||
struct Race: Identifiable, Equatable, Codable {
|
||||
let id: String
|
||||
let name: String
|
||||
let description: String
|
||||
let location: String
|
||||
let latitude: Double
|
||||
let longitude: Double
|
||||
let raceDate: Date
|
||||
let distanceKm: Double
|
||||
let raceType: RaceType
|
||||
let organizerName: String
|
||||
let registrationUrl: String?
|
||||
let imageUrl: String?
|
||||
let participantCount: Int?
|
||||
let isRegistered: Bool
|
||||
let isSaved: Bool
|
||||
let elevationGain: Double
|
||||
let terrainType: TerrainType
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case id, name, description, location, latitude, longitude, raceDate, distanceKm, raceType, organizerName, registrationUrl, imageUrl, participantCount, isRegistered, isSaved, elevationGain, terrainType
|
||||
}
|
||||
|
||||
init(
|
||||
id: String,
|
||||
name: String,
|
||||
description: String,
|
||||
location: String,
|
||||
latitude: Double,
|
||||
longitude: Double,
|
||||
raceDate: Date,
|
||||
distanceKm: Double,
|
||||
raceType: RaceType,
|
||||
organizerName: String,
|
||||
registrationUrl: String?,
|
||||
imageUrl: String?,
|
||||
participantCount: Int?,
|
||||
isRegistered: Bool,
|
||||
isSaved: Bool,
|
||||
elevationGain: Double,
|
||||
terrainType: TerrainType
|
||||
) {
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.description = description
|
||||
self.location = location
|
||||
self.latitude = latitude
|
||||
self.longitude = longitude
|
||||
self.raceDate = raceDate
|
||||
self.distanceKm = distanceKm
|
||||
self.raceType = raceType
|
||||
self.organizerName = organizerName
|
||||
self.registrationUrl = registrationUrl
|
||||
self.imageUrl = imageUrl
|
||||
self.participantCount = participantCount
|
||||
self.isRegistered = isRegistered
|
||||
self.isSaved = isSaved
|
||||
self.elevationGain = elevationGain
|
||||
self.terrainType = terrainType
|
||||
}
|
||||
|
||||
static func == (lhs: Race, rhs: Race) -> Bool {
|
||||
lhs.id == rhs.id && lhs.isRegistered == rhs.isRegistered && lhs.isSaved == rhs.isSaved
|
||||
}
|
||||
|
||||
var daysUntilRace: Int {
|
||||
let calendar = Calendar.current
|
||||
return calendar.dateComponents([.day], from: Date(), to: raceDate).day ?? 0
|
||||
}
|
||||
|
||||
var isUpcoming: Bool {
|
||||
raceDate > Date()
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Race Type
|
||||
|
||||
enum RaceType: String, CaseIterable, Codable {
|
||||
case road
|
||||
case trail
|
||||
case track
|
||||
case virtual
|
||||
|
||||
var displayName: String {
|
||||
switch self {
|
||||
case .road: return "Road"
|
||||
case .trail: return "Trail"
|
||||
case .track: return "Track"
|
||||
case .virtual: return "Virtual"
|
||||
}
|
||||
}
|
||||
|
||||
var icon: String {
|
||||
switch self {
|
||||
case .road: return "car.fill"
|
||||
case .trail: return "mountain.2.fill"
|
||||
case .track: return "circle.fill"
|
||||
case .virtual: return "globe"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Terrain Type
|
||||
|
||||
enum TerrainType: String, CaseIterable, Codable {
|
||||
case flat
|
||||
case rolling
|
||||
case hilly
|
||||
case mountainous
|
||||
|
||||
var displayName: String {
|
||||
switch self {
|
||||
case .flat: return "Flat"
|
||||
case .rolling: return "Rolling"
|
||||
case .hilly: return "Hilly"
|
||||
case .mountainous: return "Mountainous"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Race Filter
|
||||
|
||||
struct RaceFilter: Encodable {
|
||||
var distanceKm: Double?
|
||||
var raceType: RaceType?
|
||||
var terrainType: TerrainType?
|
||||
var startDate: Date?
|
||||
var endDate: Date?
|
||||
var location: String?
|
||||
var radiusKm: Double?
|
||||
var limit: Int
|
||||
var offset: Int
|
||||
|
||||
init(
|
||||
distanceKm: Double? = nil,
|
||||
raceType: RaceType? = nil,
|
||||
terrainType: TerrainType? = nil,
|
||||
startDate: Date? = nil,
|
||||
endDate: Date? = nil,
|
||||
location: String? = nil,
|
||||
radiusKm: Double? = nil,
|
||||
limit: Int = 20,
|
||||
offset: Int = 0
|
||||
) {
|
||||
self.distanceKm = distanceKm
|
||||
self.raceType = raceType
|
||||
self.terrainType = terrainType
|
||||
self.startDate = startDate
|
||||
self.endDate = endDate
|
||||
self.location = location
|
||||
self.radiusKm = radiusKm
|
||||
self.limit = limit
|
||||
self.offset = offset
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - API Response Types
|
||||
|
||||
struct RaceListResponse: Decodable {
|
||||
let races: [Race]
|
||||
let hasMore: Bool
|
||||
}
|
||||
|
||||
struct RaceDetailResponse: Decodable {
|
||||
let race: Race
|
||||
}
|
||||
|
||||
struct SaveRaceResponse: Decodable {
|
||||
let success: Bool
|
||||
let raceId: String
|
||||
let isSaved: Bool
|
||||
}
|
||||
|
||||
struct RegisterRaceResponse: Decodable {
|
||||
let success: Bool
|
||||
let raceId: String
|
||||
let registrationUrl: String?
|
||||
}
|
||||
Reference in New Issue
Block a user