feat(app): add manual entry and sharing flows
This commit is contained in:
@@ -6,19 +6,19 @@ part 'recommendation_record.g.dart';
|
||||
class RecommendationRecord extends HiveObject {
|
||||
@HiveField(0)
|
||||
final String id;
|
||||
|
||||
|
||||
@HiveField(1)
|
||||
final String restaurantId;
|
||||
|
||||
|
||||
@HiveField(2)
|
||||
final DateTime recommendationDate;
|
||||
|
||||
|
||||
@HiveField(3)
|
||||
final bool visited;
|
||||
|
||||
|
||||
@HiveField(4)
|
||||
final DateTime createdAt;
|
||||
|
||||
|
||||
RecommendationRecord({
|
||||
required this.id,
|
||||
required this.restaurantId,
|
||||
@@ -26,4 +26,4 @@ class RecommendationRecord extends HiveObject {
|
||||
required this.visited,
|
||||
required this.createdAt,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,61 +6,61 @@ part 'restaurant.g.dart';
|
||||
class Restaurant extends HiveObject {
|
||||
@HiveField(0)
|
||||
final String id;
|
||||
|
||||
|
||||
@HiveField(1)
|
||||
final String name;
|
||||
|
||||
|
||||
@HiveField(2)
|
||||
final String category;
|
||||
|
||||
|
||||
@HiveField(3)
|
||||
final String subCategory;
|
||||
|
||||
|
||||
@HiveField(4)
|
||||
final String? description;
|
||||
|
||||
|
||||
@HiveField(5)
|
||||
final String? phoneNumber;
|
||||
|
||||
|
||||
@HiveField(6)
|
||||
final String roadAddress;
|
||||
|
||||
|
||||
@HiveField(7)
|
||||
final String jibunAddress;
|
||||
|
||||
|
||||
@HiveField(8)
|
||||
final double latitude;
|
||||
|
||||
|
||||
@HiveField(9)
|
||||
final double longitude;
|
||||
|
||||
|
||||
@HiveField(10)
|
||||
final DateTime? lastVisitDate;
|
||||
|
||||
|
||||
@HiveField(11)
|
||||
final DataSource source;
|
||||
|
||||
|
||||
@HiveField(12)
|
||||
final DateTime createdAt;
|
||||
|
||||
|
||||
@HiveField(13)
|
||||
final DateTime updatedAt;
|
||||
|
||||
|
||||
@HiveField(14)
|
||||
final String? naverPlaceId;
|
||||
|
||||
|
||||
@HiveField(15)
|
||||
final String? naverUrl;
|
||||
|
||||
|
||||
@HiveField(16)
|
||||
final String? businessHours;
|
||||
|
||||
|
||||
@HiveField(17)
|
||||
final DateTime? lastVisited;
|
||||
|
||||
|
||||
@HiveField(18)
|
||||
final int visitCount;
|
||||
|
||||
|
||||
Restaurant({
|
||||
required this.id,
|
||||
required this.name,
|
||||
@@ -132,7 +132,7 @@ class Restaurant extends HiveObject {
|
||||
enum DataSource {
|
||||
@HiveField(0)
|
||||
NAVER,
|
||||
|
||||
|
||||
@HiveField(1)
|
||||
USER_INPUT
|
||||
}
|
||||
USER_INPUT,
|
||||
}
|
||||
|
||||
@@ -2,10 +2,10 @@ class ShareDevice {
|
||||
final String code;
|
||||
final String deviceId;
|
||||
final DateTime discoveredAt;
|
||||
|
||||
|
||||
ShareDevice({
|
||||
required this.code,
|
||||
required this.deviceId,
|
||||
required this.discoveredAt,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,16 +6,16 @@ part 'user_settings.g.dart';
|
||||
class UserSettings {
|
||||
@HiveField(0)
|
||||
final int revisitPreventionDays;
|
||||
|
||||
|
||||
@HiveField(1)
|
||||
final bool notificationEnabled;
|
||||
|
||||
|
||||
@HiveField(2)
|
||||
final String notificationTime;
|
||||
|
||||
|
||||
@HiveField(3)
|
||||
final Map<String, double> categoryWeights;
|
||||
|
||||
|
||||
@HiveField(4)
|
||||
final int notificationDelayMinutes;
|
||||
|
||||
@@ -35,11 +35,13 @@ class UserSettings {
|
||||
int? notificationDelayMinutes,
|
||||
}) {
|
||||
return UserSettings(
|
||||
revisitPreventionDays: revisitPreventionDays ?? this.revisitPreventionDays,
|
||||
revisitPreventionDays:
|
||||
revisitPreventionDays ?? this.revisitPreventionDays,
|
||||
notificationEnabled: notificationEnabled ?? this.notificationEnabled,
|
||||
notificationTime: notificationTime ?? this.notificationTime,
|
||||
categoryWeights: categoryWeights ?? this.categoryWeights,
|
||||
notificationDelayMinutes: notificationDelayMinutes ?? this.notificationDelayMinutes,
|
||||
notificationDelayMinutes:
|
||||
notificationDelayMinutes ?? this.notificationDelayMinutes,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,19 +6,19 @@ part 'visit_record.g.dart';
|
||||
class VisitRecord extends HiveObject {
|
||||
@HiveField(0)
|
||||
final String id;
|
||||
|
||||
|
||||
@HiveField(1)
|
||||
final String restaurantId;
|
||||
|
||||
|
||||
@HiveField(2)
|
||||
final DateTime visitDate;
|
||||
|
||||
|
||||
@HiveField(3)
|
||||
final bool isConfirmed;
|
||||
|
||||
|
||||
@HiveField(4)
|
||||
final DateTime createdAt;
|
||||
|
||||
|
||||
VisitRecord({
|
||||
required this.id,
|
||||
required this.restaurantId,
|
||||
@@ -26,4 +26,4 @@ class VisitRecord extends HiveObject {
|
||||
required this.isConfirmed,
|
||||
required this.createdAt,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,18 @@
|
||||
class WeatherInfo {
|
||||
final WeatherData current;
|
||||
final WeatherData nextHour;
|
||||
|
||||
WeatherInfo({
|
||||
required this.current,
|
||||
required this.nextHour,
|
||||
});
|
||||
|
||||
WeatherInfo({required this.current, required this.nextHour});
|
||||
}
|
||||
|
||||
class WeatherData {
|
||||
final int temperature;
|
||||
final bool isRainy;
|
||||
final String description;
|
||||
|
||||
|
||||
WeatherData({
|
||||
required this.temperature,
|
||||
required this.isRainy,
|
||||
required this.description,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,9 @@ abstract class RecommendationRepository {
|
||||
Future<List<RecommendationRecord>> getAllRecommendationRecords();
|
||||
|
||||
/// 특정 맛집의 추천 기록을 가져옵니다
|
||||
Future<List<RecommendationRecord>> getRecommendationsByRestaurantId(String restaurantId);
|
||||
Future<List<RecommendationRecord>> getRecommendationsByRestaurantId(
|
||||
String restaurantId,
|
||||
);
|
||||
|
||||
/// 날짜별 추천 기록을 가져옵니다
|
||||
Future<List<RecommendationRecord>> getRecommendationsByDate(DateTime date);
|
||||
@@ -36,4 +38,4 @@ abstract class RecommendationRepository {
|
||||
|
||||
/// 월별 추천 통계를 가져옵니다
|
||||
Future<Map<String, int>> getMonthlyRecommendationStats(int year, int month);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -44,6 +44,16 @@ abstract class RestaurantRepository {
|
||||
/// 네이버 지도 URL로부터 맛집을 추가합니다
|
||||
Future<Restaurant> addRestaurantFromUrl(String url);
|
||||
|
||||
/// 네이버 지도 URL로부터 식당 정보를 미리보기로 가져옵니다
|
||||
Future<Restaurant> previewRestaurantFromUrl(String url);
|
||||
|
||||
/// 네이버 로컬 검색에서 식당을 검색합니다
|
||||
Future<List<Restaurant>> searchRestaurantsFromNaver({
|
||||
required String query,
|
||||
double? latitude,
|
||||
double? longitude,
|
||||
});
|
||||
|
||||
/// 네이버 Place ID로 맛집을 찾습니다
|
||||
Future<Restaurant?> getRestaurantByNaverPlaceId(String naverPlaceId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -57,4 +57,4 @@ abstract class SettingsRepository {
|
||||
|
||||
/// UserSettings 변경사항을 스트림으로 감시합니다
|
||||
Stream<UserSettings> watchUserSettings();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -39,4 +39,4 @@ abstract class VisitRepository {
|
||||
|
||||
/// 카테고리별 방문 통계를 가져옵니다
|
||||
Future<Map<String, int>> getCategoryVisitStats();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,4 +18,4 @@ abstract class WeatherRepository {
|
||||
|
||||
/// 날씨 정보 업데이트가 필요한지 확인합니다
|
||||
Future<bool> isWeatherUpdateNeeded();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -49,7 +49,10 @@ class RecommendationEngine {
|
||||
if (eligibleRestaurants.isEmpty) return null;
|
||||
|
||||
// 3단계: 카테고리 필터링
|
||||
final filteredByCategory = _filterByCategory(eligibleRestaurants, config.selectedCategories);
|
||||
final filteredByCategory = _filterByCategory(
|
||||
eligibleRestaurants,
|
||||
config.selectedCategories,
|
||||
);
|
||||
if (filteredByCategory.isEmpty) return null;
|
||||
|
||||
// 4단계: 가중치 계산 및 선택
|
||||
@@ -57,7 +60,10 @@ class RecommendationEngine {
|
||||
}
|
||||
|
||||
/// 거리 기반 필터링
|
||||
List<Restaurant> _filterByDistance(List<Restaurant> restaurants, RecommendationConfig config) {
|
||||
List<Restaurant> _filterByDistance(
|
||||
List<Restaurant> restaurants,
|
||||
RecommendationConfig config,
|
||||
) {
|
||||
// 날씨에 따른 최대 거리 조정
|
||||
double effectiveMaxDistance = config.maxDistance;
|
||||
if (config.weather != null && config.weather!.current.isRainy) {
|
||||
@@ -98,7 +104,10 @@ class RecommendationEngine {
|
||||
}
|
||||
|
||||
/// 카테고리 필터링
|
||||
List<Restaurant> _filterByCategory(List<Restaurant> restaurants, List<String> selectedCategories) {
|
||||
List<Restaurant> _filterByCategory(
|
||||
List<Restaurant> restaurants,
|
||||
List<String> selectedCategories,
|
||||
) {
|
||||
if (selectedCategories.isEmpty) {
|
||||
return restaurants;
|
||||
}
|
||||
@@ -108,7 +117,10 @@ class RecommendationEngine {
|
||||
}
|
||||
|
||||
/// 가중치 기반 선택
|
||||
Restaurant? _selectWithWeights(List<Restaurant> restaurants, RecommendationConfig config) {
|
||||
Restaurant? _selectWithWeights(
|
||||
List<Restaurant> restaurants,
|
||||
RecommendationConfig config,
|
||||
) {
|
||||
if (restaurants.isEmpty) return null;
|
||||
|
||||
// 각 식당에 대한 가중치 계산
|
||||
@@ -116,7 +128,8 @@ class RecommendationEngine {
|
||||
double weight = 1.0;
|
||||
|
||||
// 카테고리 가중치 적용
|
||||
final categoryWeight = config.userSettings.categoryWeights[restaurant.category];
|
||||
final categoryWeight =
|
||||
config.userSettings.categoryWeights[restaurant.category];
|
||||
if (categoryWeight != null) {
|
||||
weight *= categoryWeight;
|
||||
}
|
||||
@@ -159,28 +172,23 @@ class RecommendationEngine {
|
||||
return 0.3;
|
||||
}
|
||||
}
|
||||
|
||||
// 점심 시간대 (11-14시)
|
||||
else if (hour >= 11 && hour < 14) {
|
||||
if (restaurant.category == 'korean' ||
|
||||
restaurant.category == 'chinese' ||
|
||||
if (restaurant.category == 'korean' ||
|
||||
restaurant.category == 'chinese' ||
|
||||
restaurant.category == 'japanese') {
|
||||
return 1.3;
|
||||
}
|
||||
}
|
||||
|
||||
// 저녁 시간대 (17-21시)
|
||||
else if (hour >= 17 && hour < 21) {
|
||||
if (restaurant.category == 'bar' ||
|
||||
restaurant.category == 'western') {
|
||||
if (restaurant.category == 'bar' || restaurant.category == 'western') {
|
||||
return 1.2;
|
||||
}
|
||||
}
|
||||
|
||||
// 늦은 저녁 (21시 이후)
|
||||
else if (hour >= 21) {
|
||||
if (restaurant.category == 'bar' ||
|
||||
restaurant.category == 'fastfood') {
|
||||
if (restaurant.category == 'bar' || restaurant.category == 'fastfood') {
|
||||
return 1.3;
|
||||
}
|
||||
if (restaurant.category == 'cafe') {
|
||||
@@ -196,24 +204,21 @@ class RecommendationEngine {
|
||||
if (weather.current.isRainy) {
|
||||
// 비가 올 때는 가까운 식당 선호
|
||||
// 이미 거리 가중치에서 처리했으므로 여기서는 실내 카테고리 선호
|
||||
if (restaurant.category == 'cafe' ||
|
||||
restaurant.category == 'fastfood') {
|
||||
if (restaurant.category == 'cafe' || restaurant.category == 'fastfood') {
|
||||
return 1.2;
|
||||
}
|
||||
}
|
||||
|
||||
// 더운 날씨 (25도 이상)
|
||||
if (weather.current.temperature >= 25) {
|
||||
if (restaurant.category == 'cafe' ||
|
||||
restaurant.category == 'japanese') {
|
||||
if (restaurant.category == 'cafe' || restaurant.category == 'japanese') {
|
||||
return 1.1;
|
||||
}
|
||||
}
|
||||
|
||||
// 추운 날씨 (10도 이하)
|
||||
if (weather.current.temperature <= 10) {
|
||||
if (restaurant.category == 'korean' ||
|
||||
restaurant.category == 'chinese') {
|
||||
if (restaurant.category == 'korean' || restaurant.category == 'chinese') {
|
||||
return 1.2;
|
||||
}
|
||||
}
|
||||
@@ -222,7 +227,9 @@ class RecommendationEngine {
|
||||
}
|
||||
|
||||
/// 가중치 기반 랜덤 선택
|
||||
Restaurant? _weightedRandomSelection(List<_WeightedRestaurant> weightedRestaurants) {
|
||||
Restaurant? _weightedRandomSelection(
|
||||
List<_WeightedRestaurant> weightedRestaurants,
|
||||
) {
|
||||
if (weightedRestaurants.isEmpty) return null;
|
||||
|
||||
// 전체 가중치 합계 계산
|
||||
@@ -254,4 +261,4 @@ class _WeightedRestaurant {
|
||||
final double weight;
|
||||
|
||||
_WeightedRestaurant(this.restaurant, this.weight);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user