Redesign trip option cards and fix various UI/planning issues

TripOptionCard improvements:
- Replace horizontal route with vertical layout (start → end with arrow)
- Remove rank badges (1, 2, 3, etc.)
- Split stats into two rows: cities/miles and sports with game counts
- Clear selection when navigating back from detail view

Settings cleanup:
- Remove unused settings (preferred game time, playoff games, notifications)
- Convert remaining settings to sliders

Planning fixes:
- Fix multi-day driving calculation in canTransition
- Remove over-restrictive trip rejection in TravelEstimator
- Clear games cache when sport selection changes

UI polish:
- RoutePreviewStrip shows all cities (abbreviated)

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-01-07 21:05:25 -06:00
parent 4184af60b5
commit 5bbfd30a70
12 changed files with 230 additions and 208 deletions

View File

@@ -20,15 +20,7 @@ final class SettingsViewModel {
didSet { savePreferences() }
}
var preferredGameTime: PreferredGameTime {
didSet { savePreferences() }
}
var includePlayoffGames: Bool {
didSet { savePreferences() }
}
var notificationsEnabled: Bool {
var maxTripOptions: Int {
didSet { savePreferences() }
}
@@ -56,19 +48,12 @@ final class SettingsViewModel {
self.selectedSports = Set(Sport.supported)
}
// Travel preferences - use local variable to avoid self access before init complete
// Travel preferences
let savedDrivingHours = defaults.integer(forKey: "maxDrivingHoursPerDay")
self.maxDrivingHoursPerDay = savedDrivingHours == 0 ? 8 : savedDrivingHours
if let timeRaw = defaults.string(forKey: "preferredGameTime"),
let time = PreferredGameTime(rawValue: timeRaw) {
self.preferredGameTime = time
} else {
self.preferredGameTime = .evening
}
self.includePlayoffGames = defaults.object(forKey: "includePlayoffGames") as? Bool ?? true
self.notificationsEnabled = defaults.object(forKey: "notificationsEnabled") as? Bool ?? true
let savedMaxTripOptions = defaults.integer(forKey: "maxTripOptions")
self.maxTripOptions = savedMaxTripOptions == 0 ? 10 : savedMaxTripOptions
// Last sync
self.lastSyncDate = defaults.object(forKey: "lastSyncDate") as? Date
@@ -110,9 +95,7 @@ final class SettingsViewModel {
func resetToDefaults() {
selectedSports = Set(Sport.supported)
maxDrivingHoursPerDay = 8
preferredGameTime = .evening
includePlayoffGames = true
notificationsEnabled = true
maxTripOptions = 10
}
// MARK: - Persistence
@@ -121,34 +104,6 @@ final class SettingsViewModel {
let defaults = UserDefaults.standard
defaults.set(selectedSports.map(\.rawValue), forKey: "selectedSports")
defaults.set(maxDrivingHoursPerDay, forKey: "maxDrivingHoursPerDay")
defaults.set(preferredGameTime.rawValue, forKey: "preferredGameTime")
defaults.set(includePlayoffGames, forKey: "includePlayoffGames")
defaults.set(notificationsEnabled, forKey: "notificationsEnabled")
}
}
// MARK: - Supporting Types
enum PreferredGameTime: String, CaseIterable, Identifiable {
case any = "any"
case afternoon = "afternoon"
case evening = "evening"
var id: String { rawValue }
var displayName: String {
switch self {
case .any: return "Any Time"
case .afternoon: return "Afternoon"
case .evening: return "Evening"
}
}
var description: String {
switch self {
case .any: return "No preference"
case .afternoon: return "1 PM - 5 PM"
case .evening: return "6 PM - 10 PM"
}
defaults.set(maxTripOptions, forKey: "maxTripOptions")
}
}

View File

@@ -17,12 +17,6 @@ struct SettingsView: View {
// Travel Preferences
travelSection
// Game Preferences
gamePreferencesSection
// Notifications
notificationsSection
// Data Sync
dataSection
@@ -71,13 +65,38 @@ struct SettingsView: View {
private var travelSection: some View {
Section {
Stepper(value: $viewModel.maxDrivingHoursPerDay, in: 2...12) {
VStack(alignment: .leading, spacing: 8) {
HStack {
Text("Max Driving Per Day")
Spacer()
Text("\(viewModel.maxDrivingHoursPerDay) hours")
.foregroundStyle(.secondary)
}
Slider(
value: Binding(
get: { Double(viewModel.maxDrivingHoursPerDay) },
set: { viewModel.maxDrivingHoursPerDay = Int($0) }
),
in: 2...12,
step: 1
)
}
VStack(alignment: .leading, spacing: 8) {
HStack {
Text("Trip Options to Show")
Spacer()
Text("\(viewModel.maxTripOptions)")
.foregroundStyle(.secondary)
}
Slider(
value: Binding(
get: { Double(viewModel.maxTripOptions) },
set: { viewModel.maxTripOptions = Int($0) }
),
in: 1...20,
step: 1
)
}
} header: {
Text("Travel Preferences")
@@ -86,39 +105,6 @@ struct SettingsView: View {
}
}
// MARK: - Game Preferences Section
private var gamePreferencesSection: some View {
Section {
Picker("Preferred Game Time", selection: $viewModel.preferredGameTime) {
ForEach(PreferredGameTime.allCases) { time in
VStack(alignment: .leading) {
Text(time.displayName)
}
.tag(time)
}
}
Toggle("Include Playoff Games", isOn: $viewModel.includePlayoffGames)
} header: {
Text("Game Preferences")
} footer: {
Text("These preferences affect trip optimization.")
}
}
// MARK: - Notifications Section
private var notificationsSection: some View {
Section {
Toggle("Schedule Updates", isOn: $viewModel.notificationsEnabled)
} header: {
Text("Notifications")
} footer: {
Text("Get notified when games in your trips are rescheduled.")
}
}
// MARK: - Data Section
private var dataSection: some View {