Fix game times with UTC data, restructure schedule by date
- Update games_canonical.json to use ISO 8601 UTC timestamps (game_datetime_utc) - Fix BootstrapService timezone-aware parsing for venue-local fallback - Fix thread-unsafe shared DateFormatter in RichGame local time display - Bump SchemaVersion to 4 to force re-bootstrap with correct UTC data - Restructure schedule view: group by date instead of sport, with sport icons on each row and date section headers showing game counts - Fix schedule row backgrounds using Theme.cardBackground instead of black - Sort games by UTC time with local-time tiebreaker for same-instant games Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -8,35 +8,6 @@
|
||||
import SwiftUI
|
||||
import MapKit
|
||||
|
||||
/// Category for custom itinerary items with emoji icons
|
||||
enum ItemCategory: String, CaseIterable {
|
||||
case restaurant
|
||||
case attraction
|
||||
case fuel
|
||||
case hotel
|
||||
case other
|
||||
|
||||
var icon: String {
|
||||
switch self {
|
||||
case .restaurant: return "\u{1F37D}" // 🍽️
|
||||
case .attraction: return "\u{1F3A2}" // 🎢
|
||||
case .fuel: return "\u{26FD}" // ⛽
|
||||
case .hotel: return "\u{1F3E8}" // 🏨
|
||||
case .other: return "\u{1F4CC}" // 📌
|
||||
}
|
||||
}
|
||||
|
||||
var label: String {
|
||||
switch self {
|
||||
case .restaurant: return "Eat"
|
||||
case .attraction: return "See"
|
||||
case .fuel: return "Fuel"
|
||||
case .hotel: return "Stay"
|
||||
case .other: return "Other"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Legacy sheet for adding/editing custom itinerary items.
|
||||
/// - Note: Use `QuickAddItemSheet` instead for new code.
|
||||
@available(*, deprecated, message: "Use QuickAddItemSheet instead")
|
||||
@@ -56,7 +27,6 @@ struct AddItemSheet: View {
|
||||
}
|
||||
|
||||
@State private var entryMode: EntryMode = .searchPlaces
|
||||
@State private var selectedCategory: ItemCategory = .restaurant
|
||||
@State private var title: String = ""
|
||||
@State private var isSaving = false
|
||||
|
||||
@@ -71,9 +41,6 @@ struct AddItemSheet: View {
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
VStack(spacing: Theme.Spacing.lg) {
|
||||
// Category picker
|
||||
categoryPicker
|
||||
|
||||
// Entry mode picker (only for new items)
|
||||
if !isEditing {
|
||||
Picker("Entry Mode", selection: $entryMode) {
|
||||
@@ -113,8 +80,6 @@ struct AddItemSheet: View {
|
||||
}
|
||||
.onAppear {
|
||||
if let existing = existingItem, let info = existing.customInfo {
|
||||
// Find matching category by icon, default to .other
|
||||
selectedCategory = ItemCategory.allCases.first { $0.icon == info.icon } ?? .other
|
||||
title = info.title
|
||||
// If editing a mappable item, switch to custom mode
|
||||
entryMode = .custom
|
||||
@@ -264,20 +229,6 @@ struct AddItemSheet: View {
|
||||
return components.isEmpty ? nil : components.joined(separator: ", ")
|
||||
}
|
||||
|
||||
@ViewBuilder
|
||||
private var categoryPicker: some View {
|
||||
HStack(spacing: Theme.Spacing.md) {
|
||||
ForEach(ItemCategory.allCases, id: \.self) { category in
|
||||
CategoryButton(
|
||||
category: category,
|
||||
isSelected: selectedCategory == category
|
||||
) {
|
||||
selectedCategory = category
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func saveItem() {
|
||||
isSaving = true
|
||||
|
||||
@@ -290,7 +241,7 @@ struct AddItemSheet: View {
|
||||
|
||||
let customInfo = CustomInfo(
|
||||
title: trimmedTitle,
|
||||
icon: selectedCategory.icon,
|
||||
icon: "\u{1F4CC}",
|
||||
time: existingInfo.time,
|
||||
latitude: existingInfo.latitude,
|
||||
longitude: existingInfo.longitude,
|
||||
@@ -312,7 +263,7 @@ struct AddItemSheet: View {
|
||||
|
||||
let customInfo = CustomInfo(
|
||||
title: placeName,
|
||||
icon: selectedCategory.icon,
|
||||
icon: "\u{1F4CC}",
|
||||
time: nil,
|
||||
latitude: coordinate.latitude,
|
||||
longitude: coordinate.longitude,
|
||||
@@ -333,7 +284,7 @@ struct AddItemSheet: View {
|
||||
|
||||
let customInfo = CustomInfo(
|
||||
title: trimmedTitle,
|
||||
icon: selectedCategory.icon,
|
||||
icon: "\u{1F4CC}",
|
||||
time: nil
|
||||
)
|
||||
|
||||
@@ -403,36 +354,6 @@ private struct PlaceResultRow: View {
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Category Button
|
||||
|
||||
private struct CategoryButton: View {
|
||||
let category: ItemCategory
|
||||
let isSelected: Bool
|
||||
let action: () -> Void
|
||||
|
||||
var body: some View {
|
||||
Button(action: action) {
|
||||
VStack(spacing: 4) {
|
||||
Text(category.icon)
|
||||
.font(.title2)
|
||||
Text(category.label)
|
||||
.font(.caption2)
|
||||
}
|
||||
.frame(maxWidth: .infinity)
|
||||
.padding(.vertical, 12)
|
||||
.background(isSelected ? Theme.warmOrange.opacity(0.2) : Color.clear)
|
||||
.cornerRadius(8)
|
||||
.overlay(
|
||||
RoundedRectangle(cornerRadius: 8)
|
||||
.stroke(isSelected ? Theme.warmOrange : Color.secondary.opacity(0.3), lineWidth: isSelected ? 2 : 1)
|
||||
)
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
.accessibilityValue(isSelected ? "Selected" : "Not selected")
|
||||
.accessibilityAddTraits(isSelected ? .isSelected : [])
|
||||
}
|
||||
}
|
||||
|
||||
#Preview {
|
||||
AddItemSheet(
|
||||
tripId: UUID(),
|
||||
|
||||
Reference in New Issue
Block a user