// // LocationSearchSheet.swift // SportsTime // // Extracted from TripCreationView - location search sheet for adding cities/places. // import SwiftUI // MARK: - City Input Type enum CityInputType { case mustStop case preferred case homeLocation case startLocation case endLocation } // MARK: - Location Search Sheet struct LocationSearchSheet: View { let inputType: CityInputType let onAdd: (LocationInput) -> Void @Environment(\.dismiss) private var dismiss @Environment(\.colorScheme) private var colorScheme @State private var searchText = "" @State private var searchResults: [LocationSearchResult] = [] @State private var isSearching = false @State private var searchTask: Task? private let locationService = LocationService.shared private var navigationTitle: String { switch inputType { case .mustStop: return "Add Must-Stop" case .preferred: return "Add Preferred Location" case .homeLocation: return "Set Home Location" case .startLocation: return "Set Start Location" case .endLocation: return "Set End Location" } } var body: some View { NavigationStack { VStack(spacing: 0) { // Search field HStack { Image(systemName: "magnifyingglass") .foregroundStyle(Theme.textMuted(colorScheme)) .accessibilityHidden(true) TextField("Search cities, addresses, places...", text: $searchText) .textFieldStyle(.plain) .autocorrectionDisabled() if isSearching { LoadingSpinner(size: .small) } else if !searchText.isEmpty { Button { searchText = "" searchResults = [] } label: { Image(systemName: "xmark.circle.fill") .foregroundStyle(Theme.textMuted(colorScheme)) } .minimumHitTarget() .accessibilityLabel("Clear search") } } .padding() .background(Theme.cardBackgroundElevated(colorScheme)) .clipShape(RoundedRectangle(cornerRadius: 10)) .padding() // Results list if searchResults.isEmpty && !searchText.isEmpty && !isSearching { ContentUnavailableView( "No Results", systemImage: "mappin.slash", description: Text("Try a different search term") ) } else { List(searchResults) { result in Button { onAdd(result.toLocationInput()) dismiss() } label: { HStack { Image(systemName: "mappin.circle.fill") .foregroundStyle(.red) .font(.title2) .accessibilityHidden(true) VStack(alignment: .leading) { Text(result.name) .foregroundStyle(Theme.textPrimary(colorScheme)) if !result.address.isEmpty { Text(result.address) .font(.caption) .foregroundStyle(Theme.textSecondary(colorScheme)) } } Spacer() Image(systemName: "plus.circle") .foregroundStyle(Theme.warmOrange) .accessibilityHidden(true) } } .buttonStyle(.plain) } .listStyle(.plain) .scrollContentBackground(.hidden) } Spacer() } .themedBackground() .navigationTitle(navigationTitle) .navigationBarTitleDisplayMode(.inline) .toolbar { ToolbarItem(placement: .cancellationAction) { Button("Cancel") { dismiss() } } } } .presentationDetents([.large]) .onChange(of: searchText) { _, newValue in // Debounce search searchTask?.cancel() searchTask = Task { try? await Task.sleep(for: .milliseconds(300)) guard !Task.isCancelled else { return } await performSearch(query: newValue) } } } private func performSearch(query: String) async { guard !query.isEmpty else { searchResults = [] return } isSearching = true do { searchResults = try await locationService.searchLocations(query) } catch { searchResults = [] } isSearching = false } } // MARK: - Preview #Preview { LocationSearchSheet(inputType: .mustStop) { _ in } }