feat(app): finalize ad gated flows and weather

- 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
This commit is contained in:
JiWoong Sul
2025-11-22 00:10:51 +09:00
parent 947fe59486
commit 2a01fa50c6
43 changed files with 1777 additions and 571 deletions

View File

@@ -1,7 +1,28 @@
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,
@@ -18,14 +39,16 @@ final currentLocationProvider = FutureProvider<Position?>((ref) async {
// 권한이 없으면 요청
final result = await Permission.location.request();
if (!result.isGranted) {
return null;
AppLogger.debug('위치 권한 거부됨, 기본 좌표(서울 시청) 사용');
return defaultPosition();
}
}
// 위치 서비스 활성화 확인
final serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) {
throw Exception('위치 서비스 비활성화되어 있습니다');
AppLogger.debug('위치 서비스 비활성화, 기본 좌표(서울 시청) 사용');
return defaultPosition();
}
// 현재 위치 가져오기
@@ -36,7 +59,12 @@ final currentLocationProvider = FutureProvider<Position?>((ref) async {
);
} catch (e) {
// 타임아웃이나 오류 발생 시 마지막 알려진 위치 반환
return await Geolocator.getLastKnownPosition();
final lastPosition = await Geolocator.getLastKnownPosition();
if (lastPosition != null) {
return lastPosition;
}
AppLogger.debug('현재 위치를 가져오지 못해 기본 좌표(서울 시청)를 반환');
return defaultPosition();
}
});
@@ -83,7 +111,8 @@ class LocationNotifier extends StateNotifier<AsyncValue<Position?>> {
if (!permissionStatus.isGranted) {
final granted = await requestLocationPermission();
if (!granted) {
state = const AsyncValue.data(null);
AppLogger.debug('위치 권한 거부됨, 기본 좌표(서울 시청)로 대체');
state = AsyncValue.data(defaultPosition());
return;
}
}
@@ -91,7 +120,8 @@ class LocationNotifier extends StateNotifier<AsyncValue<Position?>> {
// 위치 서비스 확인
final serviceEnabled = await Geolocator.isLocationServiceEnabled();
if (!serviceEnabled) {
state = AsyncValue.error('위치 서비스 비활성화되어 있습니다', StackTrace.current);
AppLogger.debug('위치 서비스 비활성화, 기본 좌표(서울 시청)로 대체');
state = AsyncValue.data(defaultPosition());
return;
}
@@ -102,13 +132,18 @@ class LocationNotifier extends StateNotifier<AsyncValue<Position?>> {
);
state = AsyncValue.data(position);
} catch (e, stack) {
} catch (e) {
// 오류 발생 시 마지막 알려진 위치 시도
try {
final lastPosition = await Geolocator.getLastKnownPosition();
state = AsyncValue.data(lastPosition);
if (lastPosition != null) {
state = AsyncValue.data(lastPosition);
} else {
AppLogger.debug('마지막 위치도 없어 기본 좌표(서울 시청)로 대체');
state = AsyncValue.data(defaultPosition());
}
} catch (_) {
state = AsyncValue.error(e, stack);
state = AsyncValue.data(defaultPosition());
}
}
}