// // POIDetailSheet.swift // SportsTime // // Detail sheet for a nearby point of interest with map preview and add-to-day action. // import SwiftUI import MapKit struct POIDetailSheet: View { @Environment(\.dismiss) private var dismiss @Environment(\.colorScheme) private var colorScheme let poi: POISearchService.POI let day: Int let onAddToDay: (POISearchService.POI) -> Void var body: some View { NavigationStack { ScrollView { VStack(spacing: 0) { mapPreview detailContent } } .background(Theme.backgroundGradient(colorScheme)) .navigationTitle(poi.name) .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .cancellationAction) { Button("Close") { dismiss() } .foregroundStyle(Theme.textSecondary(colorScheme)) } } .onAppear { AnalyticsManager.shared.track(.poiDetailViewed( poiName: poi.name, category: poi.category.displayName )) } } .presentationDetents([.medium, .large]) } // MARK: - Map Preview private var mapPreview: some View { Map(initialPosition: .region(MKCoordinateRegion( center: poi.coordinate, latitudinalMeters: 800, longitudinalMeters: 800 ))) { Marker(poi.name, coordinate: poi.coordinate) .tint(Theme.warmOrange) } .mapStyle(.standard(pointsOfInterest: .excludingAll)) .frame(height: 160) .allowsHitTesting(false) .accessibilityLabel("Map showing \(poi.name)") } // MARK: - Detail Content private var detailContent: some View { VStack(spacing: Theme.Spacing.lg) { // Info section VStack(alignment: .leading, spacing: Theme.Spacing.md) { // Category badge HStack(spacing: Theme.Spacing.xs) { Image(systemName: poi.category.iconName) .font(.caption.weight(.semibold)) .foregroundStyle(.white) .frame(width: 24, height: 24) .background(categoryColor) .clipShape(RoundedRectangle(cornerRadius: 6)) Text(poi.category.displayName) .font(.subheadline) .fontWeight(.medium) .foregroundStyle(Theme.textSecondary(colorScheme)) } // Name Text(poi.name) .font(.title2) .fontWeight(.bold) .foregroundStyle(Theme.textPrimary(colorScheme)) // Metadata rows VStack(alignment: .leading, spacing: Theme.Spacing.sm) { if let address = poi.address { metadataRow(icon: "mappin.circle.fill", text: address) } metadataRow( icon: "figure.walk", text: "\(poi.formattedDistance) away", highlight: true ) if let url = poi.mapItem?.url { Link(destination: url) { metadataRow(icon: "globe", text: url.host ?? "Website", isLink: true) } } } } .frame(maxWidth: .infinity, alignment: .leading) .padding(Theme.Spacing.lg) .background(Theme.cardBackground(colorScheme)) .clipShape(RoundedRectangle(cornerRadius: Theme.CornerRadius.large)) .overlay { RoundedRectangle(cornerRadius: Theme.CornerRadius.large) .strokeBorder(Theme.surfaceGlow(colorScheme), lineWidth: 1) } .shadow(color: Theme.cardShadow(colorScheme), radius: 8, y: 4) // Action buttons VStack(spacing: Theme.Spacing.sm) { Button { onAddToDay(poi) dismiss() } label: { Label("Add to Day \(day)", systemImage: "plus.circle.fill") .font(.body.weight(.semibold)) .frame(maxWidth: .infinity) .padding(.vertical, Theme.Spacing.md) } .buttonStyle(.borderedProminent) .tint(Theme.warmOrange) .accessibilityLabel("Add \(poi.name) to Day \(day)") if poi.mapItem != nil { Button { poi.mapItem?.openInMaps() } label: { Label("Open in Apple Maps", systemImage: "map.fill") .font(.body.weight(.medium)) .frame(maxWidth: .infinity) .padding(.vertical, Theme.Spacing.md) } .buttonStyle(.bordered) .tint(Theme.textSecondary(colorScheme)) .accessibilityLabel("Open \(poi.name) in Apple Maps") } } } .padding(Theme.Spacing.lg) } // MARK: - Helpers private func metadataRow(icon: String, text: String, highlight: Bool = false, isLink: Bool = false) -> some View { HStack(spacing: Theme.Spacing.sm) { Image(systemName: icon) .font(.caption) .foregroundStyle(highlight ? Theme.warmOrange : Theme.textMuted(colorScheme)) .frame(width: 20) .accessibilityHidden(true) Text(text) .font(.subheadline) .foregroundStyle(isLink ? Theme.warmOrange : Theme.textSecondary(colorScheme)) .lineLimit(2) } } private var categoryColor: Color { switch poi.category { case .restaurant: return .orange case .bar: return .indigo case .coffee: return .brown case .hotel: return .blue case .parking: return .green case .attraction: return .yellow case .entertainment: return .purple } } }