// // LocationPermissionManager.swift // SportsTime // // Manages location permission requests and status // import Foundation import CoreLocation import SwiftUI @MainActor @Observable final class LocationPermissionManager: NSObject { static let shared = LocationPermissionManager() private(set) var authorizationStatus: CLAuthorizationStatus = .notDetermined private(set) var currentLocation: CLLocation? private(set) var isRequestingPermission = false private let locationManager = CLLocationManager() override private init() { super.init() locationManager.delegate = self authorizationStatus = locationManager.authorizationStatus } // MARK: - Computed Properties var isAuthorized: Bool { authorizationStatus == .authorizedWhenInUse || authorizationStatus == .authorizedAlways } var needsPermission: Bool { authorizationStatus == .notDetermined } var isDenied: Bool { authorizationStatus == .denied || authorizationStatus == .restricted } var statusMessage: String { switch authorizationStatus { case .notDetermined: return "Location access helps find nearby stadiums and optimize your route." case .restricted: return "Location access is restricted on this device." case .denied: return "Location access was denied. Enable it in Settings to use this feature." case .authorizedAlways, .authorizedWhenInUse: return "Location access granted." @unknown default: return "Unknown location status." } } // MARK: - Actions func requestPermission() { guard authorizationStatus == .notDetermined else { return } isRequestingPermission = true locationManager.requestWhenInUseAuthorization() } func requestCurrentLocation() { guard isAuthorized else { return } locationManager.requestLocation() } func openSettings() { guard let settingsURL = URL(string: UIApplication.openSettingsURLString) else { return } UIApplication.shared.open(settingsURL) } } // MARK: - CLLocationManagerDelegate extension LocationPermissionManager: CLLocationManagerDelegate { nonisolated func locationManagerDidChangeAuthorization(_ manager: CLLocationManager) { Task { @MainActor in self.authorizationStatus = manager.authorizationStatus self.isRequestingPermission = false // Auto-request location if newly authorized if self.isAuthorized { self.requestCurrentLocation() } } } nonisolated func locationManager(_ manager: CLLocationManager, didUpdateLocations locations: [CLLocation]) { Task { @MainActor in self.currentLocation = locations.last } } nonisolated func locationManager(_ manager: CLLocationManager, didFailWithError error: Error) { // Location error handled silently } } // MARK: - Location Permission View struct LocationPermissionView: View { @Bindable var manager = LocationPermissionManager.shared var body: some View { VStack(spacing: 16) { Image(systemName: "location.circle.fill") .font(.system(size: 60)) .foregroundStyle(.blue) Text("Enable Location") .font(.title2) .fontWeight(.bold) Text(manager.statusMessage) .font(.subheadline) .foregroundStyle(.secondary) .multilineTextAlignment(.center) .padding(.horizontal) if manager.needsPermission { Button { manager.requestPermission() } label: { Text("Allow Location Access") .fontWeight(.semibold) .frame(maxWidth: .infinity) .padding() .background(Color.blue) .foregroundStyle(.white) .clipShape(RoundedRectangle(cornerRadius: 12)) } .padding(.horizontal) } else if manager.isDenied { Button { manager.openSettings() } label: { Text("Open Settings") .fontWeight(.semibold) .frame(maxWidth: .infinity) .padding() .background(Color.blue) .foregroundStyle(.white) .clipShape(RoundedRectangle(cornerRadius: 12)) } .padding(.horizontal) } else if manager.isAuthorized { Label("Location Enabled", systemImage: "checkmark.circle.fill") .foregroundStyle(.green) } } .padding() } } // MARK: - Location Permission Banner struct LocationPermissionBanner: View { @Bindable var manager = LocationPermissionManager.shared @Binding var isPresented: Bool var body: some View { if manager.needsPermission || manager.isDenied { HStack(spacing: 12) { Image(systemName: "location.slash") .foregroundStyle(.orange) VStack(alignment: .leading, spacing: 2) { Text("Location Not Available") .font(.subheadline) .fontWeight(.medium) Text(manager.needsPermission ? "Enable for better route planning" : "Tap to enable in Settings") .font(.caption) .foregroundStyle(.secondary) } Spacer() Button { if manager.needsPermission { manager.requestPermission() } else { manager.openSettings() } } label: { Text(manager.needsPermission ? "Enable" : "Settings") .font(.caption) .fontWeight(.semibold) .padding(.horizontal, 12) .padding(.vertical, 6) .background(Color.blue) .foregroundStyle(.white) .clipShape(Capsule()) } Button { isPresented = false } label: { Image(systemName: "xmark") .font(.caption) .foregroundStyle(.secondary) } } .padding() .background(Color(.secondarySystemBackground)) .clipShape(RoundedRectangle(cornerRadius: 12)) } } } #Preview { LocationPermissionView() }