import 'package:flutter/foundation.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:geolocator/geolocator.dart'; import 'package:lunchpick/core/utils/app_logger.dart'; import 'package:permission_handler/permission_handler.dart'; const double _defaultLatitude = 37.5666805; const double _defaultLongitude = 126.9784147; /// 위치 정보를 사용할 수 없을 때 활용하는 기본 좌표(서울 시청). Position defaultPosition() { return Position( latitude: _defaultLatitude, longitude: _defaultLongitude, timestamp: DateTime.now(), accuracy: 0, altitude: 0, altitudeAccuracy: 0, heading: 0, headingAccuracy: 0, speed: 0, speedAccuracy: 0, isMocked: false, ); } /// 위치 권한 상태 Provider final locationPermissionProvider = FutureProvider(( ref, ) async { return await Permission.location.status; }); /// 현재 위치 Provider final currentLocationProvider = FutureProvider((ref) async { // 위치 권한 확인 final permissionStatus = await Permission.location.status; if (!permissionStatus.isGranted) { // 권한이 없으면 요청 final result = await Permission.location.request(); if (!result.isGranted) { AppLogger.debug('위치 권한 거부됨, 기본 좌표(서울 시청) 사용'); return defaultPosition(); } } // 위치 서비스 활성화 확인 final serviceEnabled = await Geolocator.isLocationServiceEnabled(); if (!serviceEnabled) { AppLogger.debug('위치 서비스 비활성화, 기본 좌표(서울 시청) 사용'); return defaultPosition(); } // 현재 위치 가져오기 try { return await Geolocator.getCurrentPosition( desiredAccuracy: LocationAccuracy.high, timeLimit: const Duration(seconds: 10), ); } catch (e) { // 타임아웃이나 오류 발생 시 마지막 알려진 위치 반환 final lastPosition = await Geolocator.getLastKnownPosition(); if (lastPosition != null) { return lastPosition; } AppLogger.debug('현재 위치를 가져오지 못해 기본 좌표(서울 시청)를 반환'); return defaultPosition(); } }); /// 위치 스트림 Provider final locationStreamProvider = StreamProvider((ref) async* { if (kIsWeb) { AppLogger.debug('[location] web detected, emit fallback immediately'); yield defaultPosition(); return; } final status = await Permission.location.status; if (!status.isGranted) { AppLogger.debug('[location] permission not granted, emit fallback'); yield defaultPosition(); return; } try { yield* Geolocator.getPositionStream( locationSettings: const LocationSettings( accuracy: LocationAccuracy.high, distanceFilter: 10, // 10미터 이상 이동 시 업데이트 ), ); } catch (_) { AppLogger.error('[location] position stream failed, emit fallback'); yield defaultPosition(); } }); /// 초기 3초 내 위치를 가져오지 못하면 기본 좌표를 우선 반환하고, /// 이후 실제 위치 스트림이 들어오면 업데이트하는 Provider. final currentLocationWithFallbackProvider = StreamProvider(( ref, ) async* { AppLogger.debug('[location] emit fallback immediately (safe start)'); // 웹/권한 거부 상황에서는 즉시 기본 좌표를 먼저 흘려보내 리스트 로딩을 막는다. final fallback = defaultPosition(); yield fallback; final initial = await Future.any([ ref.watch(currentLocationProvider.future).then((pos) => pos ?? fallback), Future.delayed(const Duration(seconds: 3), () => fallback), ]).catchError((_) => fallback); if (initial.latitude != fallback.latitude || initial.longitude != fallback.longitude) { AppLogger.debug( '[location] resolved initial position: ' '${initial.latitude}, ${initial.longitude}', ); yield initial; } else { AppLogger.debug('[location] initial resolved to fallback'); } yield* ref.watch(locationStreamProvider.stream).handleError((_) { // 스트림 오류는 무시하고 마지막 위치를 유지 AppLogger.error('[location] stream error, keeping last position'); }); }); /// 위치 관리 StateNotifier class LocationNotifier extends StateNotifier> { LocationNotifier() : super(const AsyncValue.loading()); /// 위치 권한 요청 Future requestLocationPermission() async { try { final status = await Permission.location.request(); return status.isGranted; } catch (e) { return false; } } /// 위치 서비스 활성화 요청 Future requestLocationService() async { try { return await Geolocator.openLocationSettings(); } catch (e) { return false; } } /// 현재 위치 가져오기 Future getCurrentLocation() async { state = const AsyncValue.loading(); try { // 권한 확인 final permissionStatus = await Permission.location.status; if (!permissionStatus.isGranted) { final granted = await requestLocationPermission(); if (!granted) { AppLogger.debug('위치 권한 거부됨, 기본 좌표(서울 시청)로 대체'); state = AsyncValue.data(defaultPosition()); return; } } // 위치 서비스 확인 final serviceEnabled = await Geolocator.isLocationServiceEnabled(); if (!serviceEnabled) { AppLogger.debug('위치 서비스 비활성화, 기본 좌표(서울 시청)로 대체'); state = AsyncValue.data(defaultPosition()); return; } // 위치 가져오기 final position = await Geolocator.getCurrentPosition( desiredAccuracy: LocationAccuracy.high, timeLimit: const Duration(seconds: 10), ); state = AsyncValue.data(position); } catch (e) { // 오류 발생 시 마지막 알려진 위치 시도 try { final lastPosition = await Geolocator.getLastKnownPosition(); if (lastPosition != null) { state = AsyncValue.data(lastPosition); } else { AppLogger.debug('마지막 위치도 없어 기본 좌표(서울 시청)로 대체'); state = AsyncValue.data(defaultPosition()); } } catch (_) { state = AsyncValue.data(defaultPosition()); } } } /// 두 지점 간의 거리 계산 (미터 단위) double calculateDistance( double startLatitude, double startLongitude, double endLatitude, double endLongitude, ) { return Geolocator.distanceBetween( startLatitude, startLongitude, endLatitude, endLongitude, ); } } /// LocationNotifier Provider final locationNotifierProvider = StateNotifierProvider>((ref) { return LocationNotifier(); });