Files
Sportstime/SportsTime/Features/Trip/Views/Wizard/Steps/LocationsStep.swift
Trey t aa34c6585a feat(wizard): add mode-specific trip wizard inputs
- Add GamePickerStep with sheet-based Sport → Team → Game selection
- Add TeamPickerStep with sheet-based Sport → Team selection
- Add LocationsStep for start/end location selection with round trip toggle
- Update TripWizardViewModel with mode-specific fields and validation
- Update TripWizardView with conditional step rendering per mode
- Update ReviewStep with mode-aware validation display
- Fix gameFirst mode to derive date range from selected games

Each planning mode now shows only relevant steps:
- By Dates: Dates → Sports → Regions → Route → Repeat → Must Stops
- By Games: Game Picker → Route → Repeat → Must Stops
- By Route: Locations → Dates → Sports → Route → Repeat → Must Stops
- Follow Team: Team Picker → Dates → Route → Repeat → Must Stops

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-14 22:21:57 -06:00

176 lines
6.0 KiB
Swift

//
// LocationsStep.swift
// SportsTime
//
// Start and end location selection for "By Route" planning mode.
//
import SwiftUI
struct LocationsStep: View {
@Environment(\.colorScheme) private var colorScheme
@Binding var startLocation: LocationInput?
@Binding var endLocation: LocationInput?
@State private var showStartLocationSearch = false
@State private var showEndLocationSearch = false
@State private var isRoundTrip = false
var body: some View {
VStack(alignment: .leading, spacing: Theme.Spacing.md) {
StepHeader(
title: "Where are you traveling?",
subtitle: "Set your start and end points"
)
// Start Location
locationRow(
label: "Starting from",
location: startLocation,
placeholder: "Select start city",
onTap: { showStartLocationSearch = true },
onClear: { startLocation = nil }
)
// End Location
if !isRoundTrip {
locationRow(
label: "Ending at",
location: endLocation,
placeholder: "Select end city",
onTap: { showEndLocationSearch = true },
onClear: { endLocation = nil }
)
}
// Round Trip Toggle
Toggle(isOn: $isRoundTrip) {
HStack(spacing: Theme.Spacing.sm) {
Image(systemName: "arrow.triangle.2.circlepath")
.foregroundStyle(Theme.warmOrange)
Text("Round trip (return to start)")
.font(.subheadline)
.foregroundStyle(Theme.textPrimary(colorScheme))
}
}
.toggleStyle(SwitchToggleStyle(tint: Theme.warmOrange))
.onChange(of: isRoundTrip) { _, newValue in
if newValue {
endLocation = startLocation
} else {
endLocation = nil
}
}
.onChange(of: startLocation) { _, newValue in
if isRoundTrip {
endLocation = newValue
}
}
}
.padding(Theme.Spacing.lg)
.background(Theme.cardBackground(colorScheme))
.clipShape(RoundedRectangle(cornerRadius: Theme.CornerRadius.large))
.overlay {
RoundedRectangle(cornerRadius: Theme.CornerRadius.large)
.stroke(Theme.surfaceGlow(colorScheme), lineWidth: 1)
}
.sheet(isPresented: $showStartLocationSearch) {
LocationSearchSheet(inputType: .startLocation) { location in
startLocation = location
}
}
.sheet(isPresented: $showEndLocationSearch) {
LocationSearchSheet(inputType: .endLocation) { location in
endLocation = location
}
}
}
// MARK: - Location Row
private func locationRow(
label: String,
location: LocationInput?,
placeholder: String,
onTap: @escaping () -> Void,
onClear: @escaping () -> Void
) -> some View {
VStack(alignment: .leading, spacing: Theme.Spacing.xs) {
Text(label)
.font(.subheadline)
.fontWeight(.medium)
.foregroundStyle(Theme.textSecondary(colorScheme))
if let location = location {
// Selected location
HStack {
Image(systemName: "mappin.circle.fill")
.foregroundStyle(Theme.warmOrange)
VStack(alignment: .leading, spacing: 2) {
Text(location.name)
.font(.subheadline)
.fontWeight(.medium)
.foregroundStyle(Theme.textPrimary(colorScheme))
if let address = location.address, !address.isEmpty {
Text(address)
.font(.caption)
.foregroundStyle(Theme.textMuted(colorScheme))
.lineLimit(1)
}
}
Spacer()
Button(action: onClear) {
Image(systemName: "xmark.circle.fill")
.foregroundStyle(Theme.textMuted(colorScheme))
}
}
.padding(Theme.Spacing.sm)
.background(Theme.cardBackgroundElevated(colorScheme))
.clipShape(RoundedRectangle(cornerRadius: Theme.CornerRadius.medium))
} else {
// Empty state - tap to add
Button(action: onTap) {
HStack {
Image(systemName: "plus.circle")
.foregroundStyle(Theme.warmOrange)
Text(placeholder)
.font(.subheadline)
.foregroundStyle(Theme.textMuted(colorScheme))
Spacer()
Image(systemName: "chevron.right")
.font(.caption)
.foregroundStyle(Theme.textMuted(colorScheme))
}
.padding(Theme.Spacing.sm)
.background(Theme.cardBackgroundElevated(colorScheme))
.clipShape(RoundedRectangle(cornerRadius: Theme.CornerRadius.medium))
.overlay(
RoundedRectangle(cornerRadius: Theme.CornerRadius.medium)
.stroke(Theme.textMuted(colorScheme).opacity(0.3), lineWidth: 1)
)
}
.buttonStyle(.plain)
}
}
}
}
// MARK: - Preview
#Preview {
LocationsStep(
startLocation: .constant(nil),
endLocation: .constant(nil)
)
.padding()
.themedBackground()
}