Files
Sportstime/SportsTime/Features/Trip/ViewModels/TripWizardViewModel.swift
Trey t 94bb68d431 fix(wizard): improve UX with step reordering and UI polish
- Reorder wizard steps: dates before sports (enables availability check)
- Add contentShape(Rectangle()) for full tap targets on all cards
- Fix route preference showing preselected value
- Fix sport cards having inconsistent heights
- Speed up step reveal animation (0.3s → 0.15s)
- Add debounced scroll delay to avoid interrupting selection

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-12 21:13:45 -06:00

149 lines
3.6 KiB
Swift

//
// TripWizardViewModel.swift
// SportsTime
//
// ViewModel for progressive-reveal trip wizard.
//
import Foundation
import SwiftUI
@Observable
final class TripWizardViewModel {
// MARK: - Planning Mode
var planningMode: PlanningMode? = nil {
didSet {
if oldValue != nil && oldValue != planningMode {
resetDownstreamFromPlanningMode()
}
}
}
// 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: - Reveal State (computed)
var isPlanningModeStepVisible: Bool { true }
var isDatesStepVisible: Bool {
planningMode != nil
}
var isSportsStepVisible: Bool {
isDatesStepVisible && hasSetDates
}
var isRegionsStepVisible: Bool {
isSportsStepVisible && !selectedSports.isEmpty
}
var isRoutePreferenceStepVisible: Bool {
isRegionsStepVisible && !selectedRegions.isEmpty
}
var isRepeatCitiesStepVisible: Bool {
isRoutePreferenceStepVisible && hasSetRoutePreference
}
var isMustStopsStepVisible: Bool {
isRepeatCitiesStepVisible && hasSetRepeatCities
}
var isReviewStepVisible: Bool {
isMustStopsStepVisible
}
/// Combined state for animation tracking
var revealState: Int {
var state = 0
if isSportsStepVisible { state += 1 }
if isDatesStepVisible { state += 2 }
if isRegionsStepVisible { state += 4 }
if isRoutePreferenceStepVisible { state += 8 }
if isRepeatCitiesStepVisible { state += 16 }
if isMustStopsStepVisible { state += 32 }
if isReviewStepVisible { state += 64 }
return state
}
// 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 resetDownstreamFromPlanningMode() {
selectedSports = []
hasSetDates = false
selectedRegions = []
hasSetRoutePreference = false
hasSetRepeatCities = false
mustStopLocations = []
}
}