Initial commit: SportsTime trip planning app
- Three-scenario planning engine (A: date range, B: selected games, C: directional routes) - GeographicRouteExplorer with anchor game support for route exploration - Shared ItineraryBuilder for travel segment calculation - TravelEstimator for driving time/distance estimation - SwiftUI views for trip creation and detail display - CloudKit integration for schedule data - Python scraping scripts for sports schedules 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
198
SportsTime/Core/Models/Local/SavedTrip.swift
Normal file
198
SportsTime/Core/Models/Local/SavedTrip.swift
Normal file
@@ -0,0 +1,198 @@
|
||||
//
|
||||
// SavedTrip.swift
|
||||
// SportsTime
|
||||
//
|
||||
// SwiftData models for local persistence
|
||||
//
|
||||
|
||||
import Foundation
|
||||
import SwiftData
|
||||
|
||||
@Model
|
||||
final class SavedTrip {
|
||||
@Attribute(.unique) var id: UUID
|
||||
var name: String
|
||||
var createdAt: Date
|
||||
var updatedAt: Date
|
||||
var status: String
|
||||
var tripData: Data // Encoded Trip struct
|
||||
|
||||
@Relationship(deleteRule: .cascade)
|
||||
var votes: [TripVote]?
|
||||
|
||||
init(
|
||||
id: UUID = UUID(),
|
||||
name: String,
|
||||
createdAt: Date = Date(),
|
||||
updatedAt: Date = Date(),
|
||||
status: TripStatus = .planned,
|
||||
tripData: Data
|
||||
) {
|
||||
self.id = id
|
||||
self.name = name
|
||||
self.createdAt = createdAt
|
||||
self.updatedAt = updatedAt
|
||||
self.status = status.rawValue
|
||||
self.tripData = tripData
|
||||
}
|
||||
|
||||
var trip: Trip? {
|
||||
try? JSONDecoder().decode(Trip.self, from: tripData)
|
||||
}
|
||||
|
||||
var tripStatus: TripStatus {
|
||||
TripStatus(rawValue: status) ?? .draft
|
||||
}
|
||||
|
||||
static func from(_ trip: Trip, status: TripStatus = .planned) -> SavedTrip? {
|
||||
guard let data = try? JSONEncoder().encode(trip) else { return nil }
|
||||
return SavedTrip(
|
||||
id: trip.id,
|
||||
name: trip.name,
|
||||
createdAt: trip.createdAt,
|
||||
updatedAt: trip.updatedAt,
|
||||
status: status,
|
||||
tripData: data
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Trip Vote (Phase 2)
|
||||
|
||||
@Model
|
||||
final class TripVote {
|
||||
@Attribute(.unique) var id: UUID
|
||||
var tripId: UUID
|
||||
var voterId: String
|
||||
var voterName: String
|
||||
var gameVotes: Data // [UUID: Bool] encoded
|
||||
var routeVotes: Data // [String: Int] encoded
|
||||
var leisurePreference: String
|
||||
var createdAt: Date
|
||||
|
||||
init(
|
||||
id: UUID = UUID(),
|
||||
tripId: UUID,
|
||||
voterId: String,
|
||||
voterName: String,
|
||||
gameVotes: Data,
|
||||
routeVotes: Data,
|
||||
leisurePreference: LeisureLevel = .moderate,
|
||||
createdAt: Date = Date()
|
||||
) {
|
||||
self.id = id
|
||||
self.tripId = tripId
|
||||
self.voterId = voterId
|
||||
self.voterName = voterName
|
||||
self.gameVotes = gameVotes
|
||||
self.routeVotes = routeVotes
|
||||
self.leisurePreference = leisurePreference.rawValue
|
||||
self.createdAt = createdAt
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - User Preferences
|
||||
|
||||
@Model
|
||||
final class UserPreferences {
|
||||
@Attribute(.unique) var id: UUID
|
||||
var defaultSports: Data // [Sport] encoded
|
||||
var defaultTravelMode: String
|
||||
var defaultLeisureLevel: String
|
||||
var defaultLodgingType: String
|
||||
var homeLocation: Data? // LocationInput encoded
|
||||
var needsEVCharging: Bool
|
||||
var numberOfDrivers: Int
|
||||
var maxDrivingHours: Double?
|
||||
|
||||
init(
|
||||
id: UUID = UUID(),
|
||||
defaultSports: [Sport] = Sport.supported,
|
||||
defaultTravelMode: TravelMode = .drive,
|
||||
defaultLeisureLevel: LeisureLevel = .moderate,
|
||||
defaultLodgingType: LodgingType = .hotel,
|
||||
homeLocation: LocationInput? = nil,
|
||||
needsEVCharging: Bool = false,
|
||||
numberOfDrivers: Int = 1,
|
||||
maxDrivingHours: Double? = nil
|
||||
) {
|
||||
self.id = id
|
||||
self.defaultSports = (try? JSONEncoder().encode(defaultSports)) ?? Data()
|
||||
self.defaultTravelMode = defaultTravelMode.rawValue
|
||||
self.defaultLeisureLevel = defaultLeisureLevel.rawValue
|
||||
self.defaultLodgingType = defaultLodgingType.rawValue
|
||||
self.homeLocation = try? JSONEncoder().encode(homeLocation)
|
||||
self.needsEVCharging = needsEVCharging
|
||||
self.numberOfDrivers = numberOfDrivers
|
||||
self.maxDrivingHours = maxDrivingHours
|
||||
}
|
||||
|
||||
var sports: [Sport] {
|
||||
(try? JSONDecoder().decode([Sport].self, from: defaultSports)) ?? Sport.supported
|
||||
}
|
||||
|
||||
var travelMode: TravelMode {
|
||||
TravelMode(rawValue: defaultTravelMode) ?? .drive
|
||||
}
|
||||
|
||||
var leisureLevel: LeisureLevel {
|
||||
LeisureLevel(rawValue: defaultLeisureLevel) ?? .moderate
|
||||
}
|
||||
|
||||
var lodgingType: LodgingType {
|
||||
LodgingType(rawValue: defaultLodgingType) ?? .hotel
|
||||
}
|
||||
|
||||
var home: LocationInput? {
|
||||
guard let data = homeLocation else { return nil }
|
||||
return try? JSONDecoder().decode(LocationInput.self, from: data)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Cached Schedule
|
||||
|
||||
@Model
|
||||
final class CachedSchedule {
|
||||
@Attribute(.unique) var id: UUID
|
||||
var sport: String
|
||||
var season: String
|
||||
var lastUpdated: Date
|
||||
var gamesData: Data // [Game] encoded
|
||||
var teamsData: Data // [Team] encoded
|
||||
var stadiumsData: Data // [Stadium] encoded
|
||||
|
||||
init(
|
||||
id: UUID = UUID(),
|
||||
sport: Sport,
|
||||
season: String,
|
||||
lastUpdated: Date = Date(),
|
||||
games: [Game],
|
||||
teams: [Team],
|
||||
stadiums: [Stadium]
|
||||
) {
|
||||
self.id = id
|
||||
self.sport = sport.rawValue
|
||||
self.season = season
|
||||
self.lastUpdated = lastUpdated
|
||||
self.gamesData = (try? JSONEncoder().encode(games)) ?? Data()
|
||||
self.teamsData = (try? JSONEncoder().encode(teams)) ?? Data()
|
||||
self.stadiumsData = (try? JSONEncoder().encode(stadiums)) ?? Data()
|
||||
}
|
||||
|
||||
var games: [Game] {
|
||||
(try? JSONDecoder().decode([Game].self, from: gamesData)) ?? []
|
||||
}
|
||||
|
||||
var teams: [Team] {
|
||||
(try? JSONDecoder().decode([Team].self, from: teamsData)) ?? []
|
||||
}
|
||||
|
||||
var stadiums: [Stadium] {
|
||||
(try? JSONDecoder().decode([Stadium].self, from: stadiumsData)) ?? []
|
||||
}
|
||||
|
||||
var isStale: Bool {
|
||||
let staleThreshold: TimeInterval = 24 * 60 * 60 // 24 hours
|
||||
return Date().timeIntervalSince(lastUpdated) > staleThreshold
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user