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

@@ -50,74 +50,109 @@ class RecommendationNotifier extends StateNotifier<AsyncValue<Restaurant?>> {
: super(const AsyncValue.data(null));
/// 랜덤 추천 실행
Future<void> getRandomRecommendation({
Future<Restaurant?> getRandomRecommendation({
required double maxDistance,
required List<String> selectedCategories,
List<String> excludedRestaurantIds = const [],
bool shouldSaveRecord = true,
}) async {
state = const AsyncValue.loading();
try {
// 현재 위치 가져오기
final location = await _ref.read(currentLocationProvider.future);
if (location == null) {
throw Exception('위치 정보를 가져올 수 없습니다');
}
// 날씨 정보 가져오기
final weather = await _ref.read(weatherProvider.future);
// 사용자 설정 가져오기
final userSettings = await _ref.read(userSettingsProvider.future);
// 모든 식당 가져오기
final allRestaurants = await _ref.read(restaurantListProvider.future);
// 방문 기록 가져오기
final allVisitRecords = await _ref.read(visitRecordsProvider.future);
// 추천 설정 구성
final config = RecommendationConfig(
userLatitude: location.latitude,
userLongitude: location.longitude,
final selectedRestaurant = await _generateCandidate(
maxDistance: maxDistance,
selectedCategories: selectedCategories,
userSettings: userSettings,
weather: weather,
excludedRestaurantIds: excludedRestaurantIds,
);
// 추천 엔진 사용
final selectedRestaurant = await _recommendationEngine
.generateRecommendation(
allRestaurants: allRestaurants,
recentVisits: allVisitRecords,
config: config,
);
if (selectedRestaurant == null) {
state = const AsyncValue.data(null);
return;
return null;
}
// 추천 기록 저장
await _saveRecommendationRecord(selectedRestaurant);
if (shouldSaveRecord) {
await saveRecommendationRecord(selectedRestaurant);
}
state = AsyncValue.data(selectedRestaurant);
return selectedRestaurant;
} catch (e, stack) {
state = AsyncValue.error(e, stack);
return null;
}
}
Future<Restaurant?> _generateCandidate({
required double maxDistance,
required List<String> selectedCategories,
List<String> excludedRestaurantIds = const [],
}) async {
// 현재 위치 가져오기
final location = await _ref.read(currentLocationProvider.future);
if (location == null) {
throw Exception('위치 정보를 가져올 수 없습니다');
}
// 날씨 정보 가져오기
final weather = await _ref.read(weatherProvider.future);
// 사용자 설정 가져오기
final userSettings = await _ref.read(userSettingsProvider.future);
// 모든 식당 가져오기
final allRestaurants = await _ref.read(restaurantListProvider.future);
// 방문 기록 가져오기
final allVisitRecords = await _ref.read(visitRecordsProvider.future);
// 제외된 식당 제거
final availableRestaurants = excludedRestaurantIds.isEmpty
? allRestaurants
: allRestaurants
.where(
(restaurant) => !excludedRestaurantIds.contains(restaurant.id),
)
.toList();
if (availableRestaurants.isEmpty) {
return null;
}
// 추천 설정 구성
final config = RecommendationConfig(
userLatitude: location.latitude,
userLongitude: location.longitude,
maxDistance: maxDistance,
selectedCategories: selectedCategories,
userSettings: userSettings,
weather: weather,
);
// 추천 엔진 사용
return _recommendationEngine.generateRecommendation(
allRestaurants: availableRestaurants,
recentVisits: allVisitRecords,
config: config,
);
}
/// 추천 기록 저장
Future<void> _saveRecommendationRecord(Restaurant restaurant) async {
Future<RecommendationRecord> saveRecommendationRecord(
Restaurant restaurant, {
DateTime? recommendationTime,
}) async {
final now = DateTime.now();
final record = RecommendationRecord(
id: const Uuid().v4(),
restaurantId: restaurant.id,
recommendationDate: DateTime.now(),
recommendationDate: recommendationTime ?? now,
visited: false,
createdAt: DateTime.now(),
createdAt: now,
);
await _repository.addRecommendationRecord(record);
return record;
}
/// 추천 후 방문 확인