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:
@@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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 {
|
||||
|
||||
Reference in New Issue
Block a user