- add AppLogger and replace scattered print logging\n- implement ad-gated recommendation flow with reminder handling and calendar link\n- complete Bluetooth share pipeline with ad gate and merge\n- integrate KMA weather API with caching and dart-define decoding\n- add NaverUrlProcessor refactor and restore restaurant repository tests
172 lines
5.0 KiB
Dart
172 lines
5.0 KiB
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<PermissionStatus>((
|
|
ref,
|
|
) async {
|
|
return await Permission.location.status;
|
|
});
|
|
|
|
/// 현재 위치 Provider
|
|
final currentLocationProvider = FutureProvider<Position?>((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<Position>((ref) {
|
|
return Geolocator.getPositionStream(
|
|
locationSettings: const LocationSettings(
|
|
accuracy: LocationAccuracy.high,
|
|
distanceFilter: 10, // 10미터 이상 이동 시 업데이트
|
|
),
|
|
);
|
|
});
|
|
|
|
/// 위치 관리 StateNotifier
|
|
class LocationNotifier extends StateNotifier<AsyncValue<Position?>> {
|
|
LocationNotifier() : super(const AsyncValue.loading());
|
|
|
|
/// 위치 권한 요청
|
|
Future<bool> requestLocationPermission() async {
|
|
try {
|
|
final status = await Permission.location.request();
|
|
return status.isGranted;
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// 위치 서비스 활성화 요청
|
|
Future<bool> requestLocationService() async {
|
|
try {
|
|
return await Geolocator.openLocationSettings();
|
|
} catch (e) {
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/// 현재 위치 가져오기
|
|
Future<void> 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<LocationNotifier, AsyncValue<Position?>>((ref) {
|
|
return LocationNotifier();
|
|
});
|