refactor: TripDetailView loads games on demand, improve poll UI

- Refactor TripDetailView to fetch games from AppDataProvider when not
  provided, adding loading state indicator for better UX
- Update all callers (25+ HomeContent variants, TripOptionsView, HomeView)
  to use simpler TripDetailView(trip:) initializer
- Fix PollDetailView sheet issue by using sheet(item:) instead of
  sheet(isPresented:) to prevent blank screen on first tap
- Improve PollDetailView UI with Theme styling, icons, and better
  visual hierarchy for share code, voting status, and results sections

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
Trey t
2026-01-14 11:31:05 -06:00
parent b5aea31b1a
commit 2ad458bffd
28 changed files with 314 additions and 135 deletions

View File

@@ -12,7 +12,7 @@ struct TripDetailView: View {
@Environment(\.colorScheme) private var colorScheme
let trip: Trip
let games: [String: RichGame]
private let providedGames: [String: RichGame]?
@Query private var savedTrips: [SavedTrip]
@State private var showProPaywall = false
@@ -25,10 +25,29 @@ struct TripDetailView: View {
@State private var isSaved = false
@State private var routePolylines: [MKPolyline] = []
@State private var isLoadingRoutes = false
@State private var loadedGames: [String: RichGame] = [:]
@State private var isLoadingGames = false
private let exportService = ExportService()
private let dataProvider = AppDataProvider.shared
/// Games dictionary - uses provided games if available, otherwise uses loaded games
private var games: [String: RichGame] {
providedGames ?? loadedGames
}
/// Initialize with trip and games dictionary (existing callers)
init(trip: Trip, games: [String: RichGame]) {
self.trip = trip
self.providedGames = games
}
/// Initialize with just trip - games will be loaded from AppDataProvider
init(trip: Trip) {
self.trip = trip
self.providedGames = nil
}
var body: some View {
ScrollView {
VStack(spacing: 0) {
@@ -94,6 +113,9 @@ struct TripDetailView: View {
.onAppear {
checkIfSaved()
}
.task {
await loadGamesIfNeeded()
}
.overlay {
if isExporting {
exportProgressOverlay
@@ -300,7 +322,15 @@ struct TripDetailView: View {
.font(.title2)
.foregroundStyle(Theme.textPrimary(colorScheme))
ForEach(Array(itinerarySections.enumerated()), id: \.offset) { index, section in
if isLoadingGames {
HStack {
Spacer()
ProgressView("Loading games...")
.padding(.vertical, Theme.Spacing.xl)
Spacer()
}
} else {
ForEach(Array(itinerarySections.enumerated()), id: \.offset) { index, section in
switch section {
case .day(let dayNumber, let date, let gamesOnDay):
DaySection(
@@ -313,6 +343,7 @@ struct TripDetailView: View {
TravelSection(segment: segment)
.staggeredAnimation(index: index)
}
}
}
}
}
@@ -493,6 +524,34 @@ struct TripDetailView: View {
// MARK: - Actions
/// Load games from AppDataProvider if not provided
private func loadGamesIfNeeded() async {
// Skip if games were provided
guard providedGames == nil else { return }
// Collect all game IDs from the trip
let gameIds = trip.stops.flatMap { $0.games }
guard !gameIds.isEmpty else { return }
isLoadingGames = true
// Load RichGame data from AppDataProvider
var loaded: [String: RichGame] = [:]
for gameId in gameIds {
do {
if let game = try await dataProvider.fetchGame(by: gameId),
let richGame = dataProvider.richGame(from: game) {
loaded[gameId] = richGame
}
} catch {
// Skip games that fail to load
}
}
loadedGames = loaded
isLoadingGames = false
}
private func exportPDF() async {
isExporting = true
exportProgress = nil
@@ -911,8 +970,7 @@ struct ShareSheet: UIViewControllerRepresentable {
startLocation: LocationInput(name: "New York"),
endLocation: LocationInput(name: "Chicago")
)
),
games: [:]
)
)
}
}