- Add ShareCardSportBackground with floating sport icons for share cards - Share cards now show sport-specific backgrounds (single or multiple sports) - Achievement collection share respects sport filter selection - Add ability to share individual achievements from detail sheet - Trip wizard ReviewStep highlights missing required fields in red - Add FieldValidation model to TripWizardViewModel Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
149 lines
3.5 KiB
Swift
149 lines
3.5 KiB
Swift
//
|
|
// TripWizardViewModel.swift
|
|
// SportsTime
|
|
//
|
|
// ViewModel for trip wizard.
|
|
//
|
|
|
|
import Foundation
|
|
import SwiftUI
|
|
|
|
@Observable
|
|
final class TripWizardViewModel {
|
|
|
|
// MARK: - Planning Mode
|
|
|
|
var planningMode: PlanningMode? = nil {
|
|
didSet {
|
|
if oldValue != nil && oldValue != planningMode {
|
|
resetAllSelections()
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - Sports Selection
|
|
|
|
var selectedSports: Set<Sport> = []
|
|
|
|
// MARK: - Dates
|
|
|
|
var startDate: Date = Date()
|
|
var endDate: Date = Date().addingTimeInterval(86400 * 7)
|
|
var hasSetDates: Bool = false
|
|
|
|
// MARK: - Regions
|
|
|
|
var selectedRegions: Set<Region> = []
|
|
|
|
// MARK: - Route Preferences
|
|
|
|
var routePreference: RoutePreference = .balanced
|
|
var hasSetRoutePreference: Bool = false
|
|
|
|
// MARK: - Repeat Cities
|
|
|
|
var allowRepeatCities: Bool = false
|
|
var hasSetRepeatCities: Bool = false
|
|
|
|
// MARK: - Must Stops
|
|
|
|
var mustStopLocations: [LocationInput] = []
|
|
|
|
// MARK: - Planning State
|
|
|
|
var isPlanning: Bool = false
|
|
|
|
// MARK: - Sport Availability
|
|
|
|
var sportAvailability: [Sport: Bool] = [:]
|
|
var isLoadingSportAvailability: Bool = false
|
|
|
|
// MARK: - Visibility
|
|
|
|
/// All steps visible once planning mode is selected
|
|
var areStepsVisible: Bool {
|
|
planningMode != nil
|
|
}
|
|
|
|
// MARK: - Validation
|
|
|
|
/// All required fields must be set before planning
|
|
var canPlanTrip: Bool {
|
|
planningMode != nil &&
|
|
hasSetDates &&
|
|
!selectedSports.isEmpty &&
|
|
!selectedRegions.isEmpty &&
|
|
hasSetRoutePreference &&
|
|
hasSetRepeatCities
|
|
}
|
|
|
|
/// Field validation for the review step - shows which fields are missing
|
|
var fieldValidation: FieldValidation {
|
|
FieldValidation(
|
|
sports: selectedSports.isEmpty ? .missing : .valid,
|
|
dates: hasSetDates ? .valid : .missing,
|
|
regions: selectedRegions.isEmpty ? .missing : .valid,
|
|
routePreference: hasSetRoutePreference ? .valid : .missing,
|
|
repeatCities: hasSetRepeatCities ? .valid : .missing
|
|
)
|
|
}
|
|
|
|
// MARK: - Sport Availability
|
|
|
|
func canSelectSport(_ sport: Sport) -> Bool {
|
|
sportAvailability[sport] ?? true // Default to available if not checked
|
|
}
|
|
|
|
func fetchSportAvailability() async {
|
|
guard hasSetDates else { return }
|
|
|
|
isLoadingSportAvailability = true
|
|
defer { isLoadingSportAvailability = false }
|
|
|
|
var availability: [Sport: Bool] = [:]
|
|
|
|
for sport in Sport.supported {
|
|
do {
|
|
let games = try await AppDataProvider.shared.filterGames(
|
|
sports: [sport],
|
|
startDate: startDate,
|
|
endDate: endDate
|
|
)
|
|
availability[sport] = !games.isEmpty
|
|
} catch {
|
|
availability[sport] = true // Default to available on error
|
|
}
|
|
}
|
|
|
|
await MainActor.run {
|
|
self.sportAvailability = availability
|
|
}
|
|
}
|
|
|
|
// MARK: - Reset Logic
|
|
|
|
private func resetAllSelections() {
|
|
selectedSports = []
|
|
hasSetDates = false
|
|
selectedRegions = []
|
|
hasSetRoutePreference = false
|
|
hasSetRepeatCities = false
|
|
mustStopLocations = []
|
|
}
|
|
}
|
|
|
|
// MARK: - Field Validation
|
|
|
|
struct FieldValidation {
|
|
enum Status {
|
|
case valid
|
|
case missing
|
|
}
|
|
|
|
let sports: Status
|
|
let dates: Status
|
|
let regions: Status
|
|
let routePreference: Status
|
|
let repeatCities: Status
|
|
}
|