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

@@ -106,7 +106,7 @@ struct HomeView: View {
}
.sheet(item: $selectedSuggestedTrip) { suggestedTrip in
NavigationStack {
TripDetailView(trip: suggestedTrip.trip, games: suggestedTrip.richGames)
TripDetailView(trip: suggestedTrip.trip)
}
}
.sheet(isPresented: $showProPaywall) {
@@ -315,7 +315,7 @@ struct SavedTripCard: View {
var body: some View {
NavigationLink {
TripDetailView(trip: trip, games: savedTrip.games)
TripDetailView(trip: trip)
} label: {
HStack(spacing: Theme.Spacing.md) {
// Route preview icon
@@ -555,7 +555,7 @@ struct SavedTripsListView: View {
ForEach(Array(sortedTrips.enumerated()), id: \.element.id) { index, savedTrip in
if let trip = savedTrip.trip {
NavigationLink {
TripDetailView(trip: trip, games: savedTrip.games)
TripDetailView(trip: trip)
} label: {
SavedTripListRow(trip: trip)
}

View File

@@ -166,7 +166,7 @@ struct HomeContent_Airbnb: View {
ForEach(savedTrips.prefix(4)) { savedTrip in
if let trip = savedTrip.trip {
NavigationLink {
TripDetailView(trip: trip, games: savedTrip.games)
TripDetailView(trip: trip)
} label: {
tripCard(trip)
}

View File

@@ -123,7 +123,7 @@ struct HomeContent_AppleMaps: View {
ForEach(Array(savedTrips.prefix(3).enumerated()), id: \.element.id) { index, savedTrip in
if let trip = savedTrip.trip {
NavigationLink {
TripDetailView(trip: trip, games: savedTrip.games)
TripDetailView(trip: trip)
} label: {
recentRow(trip, isLast: index == min(2, savedTrips.count - 1))
}

View File

@@ -424,7 +424,7 @@ struct HomeContent_ArtDeco: View {
ForEach(savedTrips.prefix(3)) { savedTrip in
if let trip = savedTrip.trip {
NavigationLink {
TripDetailView(trip: trip, games: savedTrip.games)
TripDetailView(trip: trip)
} label: {
HStack {
decoDiamond

View File

@@ -224,7 +224,7 @@ struct HomeContent_Brutalist: View {
ForEach(savedTrips.prefix(3)) { savedTrip in
if let trip = savedTrip.trip {
NavigationLink {
TripDetailView(trip: trip, games: savedTrip.games)
TripDetailView(trip: trip)
} label: {
HStack {
Text(trip.name.uppercased())

View File

@@ -215,7 +215,7 @@ struct HomeContent_CarrotWeather: View {
ForEach(savedTrips.prefix(3)) { savedTrip in
if let trip = savedTrip.trip {
NavigationLink {
TripDetailView(trip: trip, games: savedTrip.games)
TripDetailView(trip: trip)
} label: {
tripRow(trip)
}

View File

@@ -207,7 +207,7 @@ struct HomeContent_Classic: View {
ForEach(Array(savedTrips.prefix(3).enumerated()), id: \.element.id) { index, savedTrip in
if let trip = savedTrip.trip {
NavigationLink {
TripDetailView(trip: trip, games: savedTrip.games)
TripDetailView(trip: trip)
} label: {
classicTripCard(savedTrip: savedTrip, trip: trip)
}

View File

@@ -376,7 +376,7 @@ struct HomeContent_DarkIndustrial: View {
ForEach(savedTrips.prefix(3)) { savedTrip in
if let trip = savedTrip.trip {
NavigationLink {
TripDetailView(trip: trip, games: savedTrip.games)
TripDetailView(trip: trip)
} label: {
HStack {
Rectangle()

View File

@@ -183,7 +183,7 @@ struct HomeContent_Fantastical: View {
ForEach(savedTrips.prefix(4)) { savedTrip in
if let trip = savedTrip.trip {
NavigationLink {
TripDetailView(trip: trip, games: savedTrip.games)
TripDetailView(trip: trip)
} label: {
eventRow(trip)
}

View File

@@ -193,7 +193,7 @@ struct HomeContent_Flighty: View {
ForEach(savedTrips.prefix(2)) { savedTrip in
if let trip = savedTrip.trip {
NavigationLink {
TripDetailView(trip: trip, games: savedTrip.games)
TripDetailView(trip: trip)
} label: {
tripCard(trip)
}

View File

@@ -284,7 +284,7 @@ struct HomeContent_Glassmorphism: View {
ForEach(savedTrips.prefix(3)) { savedTrip in
if let trip = savedTrip.trip {
NavigationLink {
TripDetailView(trip: trip, games: savedTrip.games)
TripDetailView(trip: trip)
} label: {
HStack(spacing: 16) {
// Glow orb

View File

@@ -283,7 +283,7 @@ struct HomeContent_LuxuryEditorial: View {
ForEach(savedTrips.prefix(3)) { savedTrip in
if let trip = savedTrip.trip {
NavigationLink {
TripDetailView(trip: trip, games: savedTrip.games)
TripDetailView(trip: trip)
} label: {
HStack(alignment: .top, spacing: 16) {
// Number

View File

@@ -392,7 +392,7 @@ struct HomeContent_MaximalistChaos: View {
ForEach(Array(savedTrips.prefix(3).enumerated()), id: \.element.id) { index, savedTrip in
if let trip = savedTrip.trip {
NavigationLink {
TripDetailView(trip: trip, games: savedTrip.games)
TripDetailView(trip: trip)
} label: {
let colors = [skyBlue, hotOrange, electricPurple]
let accentColor = colors[index % colors.count]

View File

@@ -290,7 +290,7 @@ struct HomeContent_NeoBrutalist: View {
ForEach(savedTrips.prefix(3)) { savedTrip in
if let trip = savedTrip.trip {
NavigationLink {
TripDetailView(trip: trip, games: savedTrip.games)
TripDetailView(trip: trip)
} label: {
HStack {
Rectangle()

View File

@@ -169,7 +169,7 @@ struct HomeContent_NikeRunClub: View {
ForEach(Array(savedTrips.prefix(3).enumerated()), id: \.element.id) { index, savedTrip in
if let trip = savedTrip.trip {
NavigationLink {
TripDetailView(trip: trip, games: savedTrip.games)
TripDetailView(trip: trip)
} label: {
activityRow(trip, index: index)
}

View File

@@ -248,7 +248,7 @@ struct HomeContent_Organic: View {
ForEach(savedTrips.prefix(3)) { savedTrip in
if let trip = savedTrip.trip {
NavigationLink {
TripDetailView(trip: trip, games: savedTrip.games)
TripDetailView(trip: trip)
} label: {
HStack(spacing: 14) {
// Organic dot cluster

View File

@@ -304,7 +304,7 @@ struct HomeContent_Playful: View {
ForEach(Array(savedTrips.prefix(3).enumerated()), id: \.element.id) { index, savedTrip in
if let trip = savedTrip.trip {
NavigationLink {
TripDetailView(trip: trip, games: savedTrip.games)
TripDetailView(trip: trip)
} label: {
let colors = [candyYellow, candyGreen, candyBlue]
let accentColor = colors[index % colors.count]

View File

@@ -303,7 +303,7 @@ struct HomeContent_RetroFuturism: View {
ForEach(savedTrips.prefix(3)) { savedTrip in
if let trip = savedTrip.trip {
NavigationLink {
TripDetailView(trip: trip, games: savedTrip.games)
TripDetailView(trip: trip)
} label: {
HStack {
Text(trip.name)

View File

@@ -162,7 +162,7 @@ struct HomeContent_SeatGeek: View {
ForEach(savedTrips.prefix(4)) { savedTrip in
if let trip = savedTrip.trip {
NavigationLink {
TripDetailView(trip: trip, games: savedTrip.games)
TripDetailView(trip: trip)
} label: {
yourTripCard(trip)
}

View File

@@ -321,7 +321,7 @@ struct HomeContent_SoftPastel: View {
ForEach(Array(savedTrips.prefix(3).enumerated()), id: \.element.id) { index, savedTrip in
if let trip = savedTrip.trip {
NavigationLink {
TripDetailView(trip: trip, games: savedTrip.games)
TripDetailView(trip: trip)
} label: {
let colors = [pastelPeach, pastelMint, pastelLavender]
let accentColor = colors[index % colors.count]

View File

@@ -101,7 +101,7 @@ struct HomeContent_Spotify: View {
ForEach(savedTrips.prefix(5)) { savedTrip in
if let trip = savedTrip.trip {
NavigationLink {
TripDetailView(trip: trip, games: savedTrip.games)
TripDetailView(trip: trip)
} label: {
tripCoverCard(trip)
}

View File

@@ -210,7 +210,7 @@ struct HomeContent_Strava: View {
ForEach(savedTrips.prefix(3)) { savedTrip in
if let trip = savedTrip.trip {
NavigationLink {
TripDetailView(trip: trip, games: savedTrip.games)
TripDetailView(trip: trip)
} label: {
activityCard(trip)
}

View File

@@ -304,7 +304,7 @@ struct HomeContent_SwissModernist: View {
ForEach(Array(savedTrips.prefix(3).enumerated()), id: \.element.id) { index, savedTrip in
if let trip = savedTrip.trip {
NavigationLink {
TripDetailView(trip: trip, games: savedTrip.games)
TripDetailView(trip: trip)
} label: {
VStack(spacing: 0) {
HStack(spacing: 24) {

View File

@@ -162,7 +162,7 @@ struct HomeContent_Things3: View {
ForEach(Array(savedTrips.prefix(3).enumerated()), id: \.element.id) { index, savedTrip in
if let trip = savedTrip.trip {
NavigationLink {
TripDetailView(trip: trip, games: savedTrip.games)
TripDetailView(trip: trip)
} label: {
tripRow(trip)
}

View File

@@ -14,6 +14,7 @@ struct PollDetailView: View {
@State private var showShareSheet = false
@State private var showDeleteConfirmation = false
@State private var showVotingSheet = false
@State private var selectedTrip: Trip?
@State private var isOwner = false
let pollId: UUID?
@@ -123,12 +124,25 @@ struct PollDetailView: View {
} message: {
Text("This will permanently delete the poll and all votes. This action cannot be undone.")
}
.sheet(item: $selectedTrip) { trip in
NavigationStack {
TripDetailView(trip: trip)
.navigationBarTitleDisplayMode(.inline)
.toolbar {
ToolbarItem(placement: .cancellationAction) {
Button("Done") {
selectedTrip = nil
}
}
}
}
}
}
@ViewBuilder
private func pollContent(_ poll: TripPoll) -> some View {
ScrollView {
VStack(spacing: 20) {
VStack(spacing: Theme.Spacing.lg) {
// Share Code Card
shareCodeCard(poll)
@@ -143,85 +157,161 @@ struct PollDetailView: View {
// Trip Previews
tripPreviewsSection(poll)
}
.padding()
.padding(.horizontal, Theme.Spacing.md)
.padding(.vertical, Theme.Spacing.lg)
}
.background(Theme.backgroundGradient(colorScheme))
}
@ViewBuilder
private func shareCodeCard(_ poll: TripPoll) -> some View {
VStack(spacing: 8) {
Text("Share Code")
.font(.caption)
.foregroundStyle(.secondary)
VStack(spacing: Theme.Spacing.md) {
// Icon
ZStack {
Circle()
.fill(Theme.warmOrange.opacity(0.15))
.frame(width: 56, height: 56)
Image(systemName: "link.circle.fill")
.font(.system(size: 28))
.foregroundStyle(Theme.warmOrange)
}
Text(poll.shareCode)
.font(.system(size: 32, weight: .bold, design: .monospaced))
.foregroundStyle(Theme.warmOrange)
VStack(spacing: Theme.Spacing.xs) {
Text("Share Code")
.font(.subheadline)
.foregroundStyle(Theme.textSecondary(colorScheme))
Text("sportstime://poll/\(poll.shareCode)")
.font(.caption2)
.foregroundStyle(.secondary)
Text(poll.shareCode)
.font(.system(size: 36, weight: .bold, design: .monospaced))
.foregroundStyle(Theme.warmOrange)
.tracking(4)
}
// Copy button
Button {
UIPasteboard.general.string = poll.shareCode
} label: {
Label("Copy Code", systemImage: "doc.on.doc")
.font(.subheadline.weight(.medium))
.foregroundStyle(Theme.warmOrange)
.padding(.horizontal, Theme.Spacing.md)
.padding(.vertical, Theme.Spacing.sm)
.background(Theme.warmOrange.opacity(0.1))
.clipShape(Capsule())
}
}
.frame(maxWidth: .infinity)
.padding()
.padding(Theme.Spacing.lg)
.background(Theme.cardBackground(colorScheme))
.clipShape(RoundedRectangle(cornerRadius: 12))
.clipShape(RoundedRectangle(cornerRadius: Theme.CornerRadius.large))
.overlay(
RoundedRectangle(cornerRadius: Theme.CornerRadius.large)
.strokeBorder(Theme.warmOrange.opacity(0.2), lineWidth: 1)
)
}
@ViewBuilder
private var votingStatusCard: some View {
HStack {
VStack(alignment: .leading, spacing: 4) {
Text(viewModel.hasVoted ? "You voted" : "You haven't voted yet")
.font(.headline)
HStack(spacing: Theme.Spacing.md) {
// Status icon
ZStack {
Circle()
.fill(viewModel.hasVoted ? Theme.mlsGreen.opacity(0.15) : Theme.warmOrange.opacity(0.15))
.frame(width: 44, height: 44)
Image(systemName: viewModel.hasVoted ? "checkmark.circle.fill" : "hand.raised.fill")
.font(.title3)
.foregroundStyle(viewModel.hasVoted ? Theme.mlsGreen : Theme.warmOrange)
}
Text("\(viewModel.votes.count) total votes")
.font(.subheadline)
.foregroundStyle(.secondary)
VStack(alignment: .leading, spacing: 2) {
Text(viewModel.hasVoted ? "You voted" : "Cast your vote")
.font(.headline)
.foregroundStyle(Theme.textPrimary(colorScheme))
HStack(spacing: Theme.Spacing.xs) {
Image(systemName: "person.2.fill")
.font(.caption2)
Text("\(viewModel.votes.count) vote\(viewModel.votes.count == 1 ? "" : "s")")
.font(.subheadline)
}
.foregroundStyle(Theme.textSecondary(colorScheme))
}
Spacer()
Button(viewModel.hasVoted ? "Change Vote" : "Vote Now") {
Button {
showVotingSheet = true
} label: {
Text(viewModel.hasVoted ? "Change" : "Vote")
.font(.subheadline.weight(.semibold))
.foregroundStyle(.white)
.padding(.horizontal, Theme.Spacing.md)
.padding(.vertical, Theme.Spacing.sm)
.background(Theme.warmOrange)
.clipShape(Capsule())
}
.buttonStyle(.borderedProminent)
.tint(Theme.warmOrange)
}
.padding()
.padding(Theme.Spacing.md)
.background(Theme.cardBackground(colorScheme))
.clipShape(RoundedRectangle(cornerRadius: 12))
.clipShape(RoundedRectangle(cornerRadius: Theme.CornerRadius.medium))
}
@ViewBuilder
private func resultsSection(_ results: PollResults) -> some View {
VStack(alignment: .leading, spacing: 12) {
Text("Results")
.font(.headline)
VStack(alignment: .leading, spacing: Theme.Spacing.md) {
// Section header
HStack(spacing: Theme.Spacing.sm) {
Image(systemName: "chart.bar.fill")
.foregroundStyle(Theme.warmOrange)
Text("Results")
.font(.headline)
.foregroundStyle(Theme.textPrimary(colorScheme))
}
ForEach(results.tripScores, id: \.tripIndex) { item in
let trip = results.poll.tripSnapshots[item.tripIndex]
ResultRow(
rank: results.tripScores.firstIndex { $0.tripIndex == item.tripIndex }! + 1,
tripName: trip.name,
score: item.score,
percentage: results.scorePercentage(for: item.tripIndex)
)
VStack(spacing: Theme.Spacing.sm) {
ForEach(results.tripScores, id: \.tripIndex) { item in
let trip = results.poll.tripSnapshots[item.tripIndex]
let rank = results.tripScores.firstIndex { $0.tripIndex == item.tripIndex }! + 1
ResultRow(
rank: rank,
tripName: trip.name,
score: item.score,
percentage: results.scorePercentage(for: item.tripIndex),
isLeader: rank == 1 && item.score > 0
)
}
}
}
.padding()
.padding(Theme.Spacing.md)
.background(Theme.cardBackground(colorScheme))
.clipShape(RoundedRectangle(cornerRadius: 12))
.clipShape(RoundedRectangle(cornerRadius: Theme.CornerRadius.medium))
}
@ViewBuilder
private func tripPreviewsSection(_ poll: TripPoll) -> some View {
VStack(alignment: .leading, spacing: 12) {
Text("Trip Options")
.font(.headline)
VStack(alignment: .leading, spacing: Theme.Spacing.md) {
// Section header
HStack(spacing: Theme.Spacing.sm) {
Image(systemName: "map.fill")
.foregroundStyle(Theme.warmOrange)
Text("Trip Options")
.font(.headline)
.foregroundStyle(Theme.textPrimary(colorScheme))
Spacer()
Text("\(poll.tripSnapshots.count) trips")
.font(.caption)
.foregroundStyle(Theme.textMuted(colorScheme))
}
ForEach(Array(poll.tripSnapshots.enumerated()), id: \.element.id) { index, trip in
TripPreviewCard(trip: trip, index: index + 1)
Button {
selectedTrip = trip
} label: {
TripPreviewCard(trip: trip, index: index + 1)
}
.buttonStyle(.plain)
}
}
}
@@ -241,43 +331,72 @@ struct PollDetailView: View {
// MARK: - Result Row
private struct ResultRow: View {
@Environment(\.colorScheme) private var colorScheme
let rank: Int
let tripName: String
let score: Int
let percentage: Double
var isLeader: Bool = false
private var rankIcon: String {
switch rank {
case 1: return "trophy.fill"
case 2: return "medal.fill"
case 3: return "medal.fill"
default: return "\(rank).circle.fill"
}
}
private var rankColor: Color {
switch rank {
case 1: return Theme.warmOrange
case 2: return .gray
case 3: return .brown
default: return .secondary
}
}
var body: some View {
HStack(spacing: 12) {
Text("#\(rank)")
.font(.headline)
.foregroundStyle(rank == 1 ? Theme.warmOrange : .secondary)
.frame(width: 30)
HStack(spacing: Theme.Spacing.sm) {
// Rank badge
ZStack {
Circle()
.fill(rankColor.opacity(0.15))
.frame(width: 36, height: 36)
Image(systemName: rankIcon)
.font(.subheadline)
.foregroundStyle(rankColor)
}
VStack(alignment: .leading, spacing: 4) {
Text(tripName)
.font(.subheadline)
.font(.subheadline.weight(isLeader ? .semibold : .regular))
.foregroundStyle(Theme.textPrimary(colorScheme))
GeometryReader { geometry in
ZStack(alignment: .leading) {
Rectangle()
.fill(Color.secondary.opacity(0.2))
.frame(height: 8)
.clipShape(RoundedRectangle(cornerRadius: 4))
RoundedRectangle(cornerRadius: 4)
.fill(Theme.cardBackgroundElevated(colorScheme))
.frame(height: 6)
Rectangle()
.fill(rank == 1 ? Theme.warmOrange : Color.secondary)
.frame(width: geometry.size.width * percentage, height: 8)
.clipShape(RoundedRectangle(cornerRadius: 4))
RoundedRectangle(cornerRadius: 4)
.fill(rankColor)
.frame(width: max(geometry.size.width * percentage, percentage > 0 ? 6 : 0), height: 6)
}
}
.frame(height: 8)
.frame(height: 6)
}
// Score badge
Text("\(score)")
.font(.caption)
.foregroundStyle(.secondary)
.frame(width: 40, alignment: .trailing)
.font(.subheadline.weight(.medium).monospacedDigit())
.foregroundStyle(rankColor)
.padding(.horizontal, 10)
.padding(.vertical, 4)
.background(rankColor.opacity(0.1))
.clipShape(Capsule())
}
.padding(.vertical, Theme.Spacing.xs)
}
}
@@ -289,47 +408,59 @@ private struct TripPreviewCard: View {
let index: Int
var body: some View {
VStack(alignment: .leading, spacing: 8) {
HStack {
Text("Option \(index)")
.font(.caption)
.fontWeight(.semibold)
.foregroundStyle(.white)
.padding(.horizontal, 8)
.padding(.vertical, 4)
.background(Theme.warmOrange)
.clipShape(Capsule())
HStack(spacing: 12) {
VStack(alignment: .leading, spacing: 8) {
HStack {
Text("Option \(index)")
.font(.caption)
.fontWeight(.semibold)
.foregroundStyle(.white)
.padding(.horizontal, 8)
.padding(.vertical, 4)
.background(Theme.warmOrange)
.clipShape(Capsule())
Text(trip.name)
.font(.headline)
}
Text(trip.name)
.font(.headline)
.foregroundStyle(.primary)
}
// Date range and duration
HStack {
Label(trip.formattedDateRange, systemImage: "calendar")
Spacer()
Label("\(trip.tripDuration) days", systemImage: "clock")
}
.font(.caption)
.foregroundStyle(.secondary)
HStack {
Label("\(trip.stops.count) stops", systemImage: "mappin.and.ellipse")
Spacer()
Label("\(trip.stops.flatMap { $0.games }.count) games", systemImage: "sportscourt")
}
.font(.caption)
.foregroundStyle(.secondary)
// Show cities
Text(trip.stops.map { $0.city }.joined(separator: ""))
// Date range and duration
HStack {
Label(trip.formattedDateRange, systemImage: "calendar")
Spacer()
Label("\(trip.tripDuration) days", systemImage: "clock")
}
.font(.caption)
.foregroundStyle(.secondary)
.lineLimit(2)
HStack {
Label("\(trip.stops.count) stops", systemImage: "mappin.and.ellipse")
Spacer()
Label("\(trip.stops.flatMap { $0.games }.count) games", systemImage: "sportscourt")
}
.font(.caption)
.foregroundStyle(.secondary)
// Show cities
Text(trip.stops.map { $0.city }.joined(separator: ""))
.font(.caption)
.foregroundStyle(.secondary)
.lineLimit(2)
}
Image(systemName: "chevron.right")
.font(.caption)
.fontWeight(.semibold)
.foregroundStyle(.tertiary)
}
.padding()
.background(Theme.cardBackground(colorScheme))
.clipShape(RoundedRectangle(cornerRadius: 12))
.overlay(
RoundedRectangle(cornerRadius: 12)
.strokeBorder(Color.secondary.opacity(0.1), lineWidth: 1)
)
}
}

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: [:]
)
)
}
}

View File

@@ -292,7 +292,7 @@ struct TripOptionsView: View {
.themedBackground()
.navigationDestination(isPresented: $showTripDetail) {
if let trip = selectedTrip {
TripDetailView(trip: trip, games: games)
TripDetailView(trip: trip)
}
}
.onChange(of: showTripDetail) { _, isShowing in

View File

@@ -40,14 +40,4 @@ sharing needs to completely overhauled. should be able to share a trip summary,
// bugs
- fucking game show at 7 am ... the fuck?
all all trips view when choosing "packed" "moderate" "relaxed" the capsule the option is in does a weird animation that looks off.
- Text on achievements is not wrapping and is being cutoff
- Remove username on share
- more on share doesnt do anything
- Created a poll and when I tap on it I get poll not found?
- group poll refreshed every time I go to screen, should update in bg and pull to refresh?
- User who made it should be able to delete it
- Trip details arent showing
- Home Screen quick start should be removed
- Today games arent highlighted in the schedule tab
- features trip showing both nba and nhl but only has a nhl game