feat: 초기 프로젝트 설정 및 LunchPick 앱 구현
LunchPick(오늘 뭐 먹Z?) Flutter 앱의 초기 구현입니다. 주요 기능: - 네이버 지도 연동 맛집 추가 - 랜덤 메뉴 추천 시스템 - 날씨 기반 거리 조정 - 방문 기록 관리 - Bluetooth 맛집 공유 - 다크모드 지원 기술 스택: - Flutter 3.8.1+ - Riverpod 상태 관리 - Hive 로컬 DB - Clean Architecture 🤖 Generated with Claude Code Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
216
lib/presentation/providers/restaurant_provider.dart
Normal file
216
lib/presentation/providers/restaurant_provider.dart
Normal file
@@ -0,0 +1,216 @@
|
||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||
import 'package:lunchpick/domain/entities/restaurant.dart';
|
||||
import 'package:lunchpick/domain/repositories/restaurant_repository.dart';
|
||||
import 'package:lunchpick/presentation/providers/di_providers.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
/// 맛집 목록 Provider
|
||||
final restaurantListProvider = StreamProvider<List<Restaurant>>((ref) {
|
||||
final repository = ref.watch(restaurantRepositoryProvider);
|
||||
return repository.watchRestaurants();
|
||||
});
|
||||
|
||||
/// 특정 맛집 Provider
|
||||
final restaurantProvider = FutureProvider.family<Restaurant?, String>((ref, id) async {
|
||||
final repository = ref.watch(restaurantRepositoryProvider);
|
||||
return repository.getRestaurantById(id);
|
||||
});
|
||||
|
||||
/// 카테고리 목록 Provider
|
||||
final categoriesProvider = FutureProvider<List<String>>((ref) async {
|
||||
final repository = ref.watch(restaurantRepositoryProvider);
|
||||
return repository.getAllCategories();
|
||||
});
|
||||
|
||||
/// 맛집 관리 StateNotifier
|
||||
class RestaurantNotifier extends StateNotifier<AsyncValue<void>> {
|
||||
final RestaurantRepository _repository;
|
||||
|
||||
RestaurantNotifier(this._repository) : super(const AsyncValue.data(null));
|
||||
|
||||
/// 맛집 추가
|
||||
Future<void> addRestaurant({
|
||||
required String name,
|
||||
required String category,
|
||||
required String subCategory,
|
||||
String? description,
|
||||
String? phoneNumber,
|
||||
required String roadAddress,
|
||||
required String jibunAddress,
|
||||
required double latitude,
|
||||
required double longitude,
|
||||
required DataSource source,
|
||||
}) async {
|
||||
state = const AsyncValue.loading();
|
||||
|
||||
try {
|
||||
final restaurant = Restaurant(
|
||||
id: const Uuid().v4(),
|
||||
name: name,
|
||||
category: category,
|
||||
subCategory: subCategory,
|
||||
description: description,
|
||||
phoneNumber: phoneNumber,
|
||||
roadAddress: roadAddress,
|
||||
jibunAddress: jibunAddress,
|
||||
latitude: latitude,
|
||||
longitude: longitude,
|
||||
source: source,
|
||||
createdAt: DateTime.now(),
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
|
||||
await _repository.addRestaurant(restaurant);
|
||||
state = const AsyncValue.data(null);
|
||||
} catch (e, stack) {
|
||||
state = AsyncValue.error(e, stack);
|
||||
}
|
||||
}
|
||||
|
||||
/// 맛집 수정
|
||||
Future<void> updateRestaurant(Restaurant restaurant) async {
|
||||
state = const AsyncValue.loading();
|
||||
|
||||
try {
|
||||
final updated = Restaurant(
|
||||
id: restaurant.id,
|
||||
name: restaurant.name,
|
||||
category: restaurant.category,
|
||||
subCategory: restaurant.subCategory,
|
||||
description: restaurant.description,
|
||||
phoneNumber: restaurant.phoneNumber,
|
||||
roadAddress: restaurant.roadAddress,
|
||||
jibunAddress: restaurant.jibunAddress,
|
||||
latitude: restaurant.latitude,
|
||||
longitude: restaurant.longitude,
|
||||
lastVisitDate: restaurant.lastVisitDate,
|
||||
source: restaurant.source,
|
||||
createdAt: restaurant.createdAt,
|
||||
updatedAt: DateTime.now(),
|
||||
);
|
||||
|
||||
await _repository.updateRestaurant(updated);
|
||||
state = const AsyncValue.data(null);
|
||||
} catch (e, stack) {
|
||||
state = AsyncValue.error(e, stack);
|
||||
}
|
||||
}
|
||||
|
||||
/// 맛집 삭제
|
||||
Future<void> deleteRestaurant(String id) async {
|
||||
state = const AsyncValue.loading();
|
||||
|
||||
try {
|
||||
await _repository.deleteRestaurant(id);
|
||||
state = const AsyncValue.data(null);
|
||||
} catch (e, stack) {
|
||||
state = AsyncValue.error(e, stack);
|
||||
}
|
||||
}
|
||||
|
||||
/// 마지막 방문일 업데이트
|
||||
Future<void> updateLastVisitDate(String restaurantId, DateTime visitDate) async {
|
||||
try {
|
||||
await _repository.updateLastVisitDate(restaurantId, visitDate);
|
||||
} catch (e, stack) {
|
||||
state = AsyncValue.error(e, stack);
|
||||
}
|
||||
}
|
||||
|
||||
/// 네이버 지도 URL로부터 맛집 추가
|
||||
Future<Restaurant> addRestaurantFromUrl(String url) async {
|
||||
state = const AsyncValue.loading();
|
||||
|
||||
try {
|
||||
final restaurant = await _repository.addRestaurantFromUrl(url);
|
||||
state = const AsyncValue.data(null);
|
||||
return restaurant;
|
||||
} catch (e, stack) {
|
||||
state = AsyncValue.error(e, stack);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
|
||||
/// 미리 생성된 Restaurant 객체를 직접 추가
|
||||
Future<void> addRestaurantDirect(Restaurant restaurant) async {
|
||||
state = const AsyncValue.loading();
|
||||
|
||||
try {
|
||||
await _repository.addRestaurant(restaurant);
|
||||
state = const AsyncValue.data(null);
|
||||
} catch (e, stack) {
|
||||
state = AsyncValue.error(e, stack);
|
||||
rethrow;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// RestaurantNotifier Provider
|
||||
final restaurantNotifierProvider = StateNotifierProvider<RestaurantNotifier, AsyncValue<void>>((ref) {
|
||||
final repository = ref.watch(restaurantRepositoryProvider);
|
||||
return RestaurantNotifier(repository);
|
||||
});
|
||||
|
||||
/// 거리 내 맛집 Provider
|
||||
final restaurantsWithinDistanceProvider = FutureProvider.family<List<Restaurant>, ({double latitude, double longitude, double maxDistance})>((ref, params) async {
|
||||
final repository = ref.watch(restaurantRepositoryProvider);
|
||||
return repository.getRestaurantsWithinDistance(
|
||||
userLatitude: params.latitude,
|
||||
userLongitude: params.longitude,
|
||||
maxDistanceInMeters: params.maxDistance,
|
||||
);
|
||||
});
|
||||
|
||||
/// n일 이내 방문하지 않은 맛집 Provider
|
||||
final restaurantsNotVisitedInDaysProvider = FutureProvider.family<List<Restaurant>, int>((ref, days) async {
|
||||
final repository = ref.watch(restaurantRepositoryProvider);
|
||||
return repository.getRestaurantsNotVisitedInDays(days);
|
||||
});
|
||||
|
||||
/// 검색어로 맛집 검색 Provider
|
||||
final searchRestaurantsProvider = FutureProvider.family<List<Restaurant>, String>((ref, query) async {
|
||||
final repository = ref.watch(restaurantRepositoryProvider);
|
||||
return repository.searchRestaurants(query);
|
||||
});
|
||||
|
||||
/// 카테고리별 맛집 Provider
|
||||
final restaurantsByCategoryProvider = FutureProvider.family<List<Restaurant>, String>((ref, category) async {
|
||||
final repository = ref.watch(restaurantRepositoryProvider);
|
||||
return repository.getRestaurantsByCategory(category);
|
||||
});
|
||||
|
||||
/// 검색 쿼리 상태 Provider
|
||||
final searchQueryProvider = StateProvider<String>((ref) => '');
|
||||
|
||||
/// 선택된 카테고리 상태 Provider
|
||||
final selectedCategoryProvider = StateProvider<String?>((ref) => null);
|
||||
|
||||
/// 필터링된 맛집 목록 Provider (검색 + 카테고리)
|
||||
final filteredRestaurantsProvider = StreamProvider<List<Restaurant>>((ref) async* {
|
||||
final searchQuery = ref.watch(searchQueryProvider);
|
||||
final selectedCategory = ref.watch(selectedCategoryProvider);
|
||||
final restaurantsStream = ref.watch(restaurantListProvider.stream);
|
||||
|
||||
await for (final restaurants in restaurantsStream) {
|
||||
var filtered = restaurants;
|
||||
|
||||
// 검색 필터 적용
|
||||
if (searchQuery.isNotEmpty) {
|
||||
final lowercaseQuery = searchQuery.toLowerCase();
|
||||
filtered = filtered.where((restaurant) {
|
||||
return restaurant.name.toLowerCase().contains(lowercaseQuery) ||
|
||||
(restaurant.description?.toLowerCase().contains(lowercaseQuery) ?? false) ||
|
||||
restaurant.category.toLowerCase().contains(lowercaseQuery);
|
||||
}).toList();
|
||||
}
|
||||
|
||||
// 카테고리 필터 적용
|
||||
if (selectedCategory != null) {
|
||||
filtered = filtered.where((restaurant) {
|
||||
return restaurant.category == selectedCategory;
|
||||
}).toList();
|
||||
}
|
||||
|
||||
yield filtered;
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user