251 lines
7.8 KiB
Dart
251 lines
7.8 KiB
Dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:lunchpick/domain/entities/visit_record.dart';
|
|
import 'package:lunchpick/domain/repositories/visit_repository.dart';
|
|
import 'package:lunchpick/presentation/providers/di_providers.dart';
|
|
import 'package:lunchpick/presentation/providers/restaurant_provider.dart';
|
|
import 'package:uuid/uuid.dart';
|
|
|
|
/// 방문 기록 목록 Provider
|
|
final visitRecordsProvider = StreamProvider<List<VisitRecord>>((ref) {
|
|
final repository = ref.watch(visitRepositoryProvider);
|
|
return repository.watchVisitRecords();
|
|
});
|
|
|
|
/// 날짜별 방문 기록 Provider
|
|
final visitRecordsByDateProvider =
|
|
FutureProvider.family<List<VisitRecord>, DateTime>((ref, date) async {
|
|
final repository = ref.watch(visitRepositoryProvider);
|
|
return repository.getVisitRecordsByDate(date);
|
|
});
|
|
|
|
/// 맛집별 방문 기록 Provider
|
|
final visitRecordsByRestaurantProvider =
|
|
FutureProvider.family<List<VisitRecord>, String>((ref, restaurantId) async {
|
|
final repository = ref.watch(visitRepositoryProvider);
|
|
return repository.getVisitRecordsByRestaurantId(restaurantId);
|
|
});
|
|
|
|
/// 월별 방문 통계 Provider
|
|
final monthlyVisitStatsProvider =
|
|
FutureProvider.family<Map<String, int>, ({int year, int month})>((
|
|
ref,
|
|
params,
|
|
) async {
|
|
final repository = ref.watch(visitRepositoryProvider);
|
|
return repository.getMonthlyVisitStats(params.year, params.month);
|
|
});
|
|
|
|
/// 방문 기록 관리 StateNotifier
|
|
class VisitNotifier extends StateNotifier<AsyncValue<void>> {
|
|
final VisitRepository _repository;
|
|
final Ref _ref;
|
|
|
|
VisitNotifier(this._repository, this._ref)
|
|
: super(const AsyncValue.data(null));
|
|
|
|
/// 방문 기록 추가
|
|
Future<void> addVisitRecord({
|
|
required String restaurantId,
|
|
required DateTime visitDate,
|
|
bool isConfirmed = false,
|
|
}) async {
|
|
state = const AsyncValue.loading();
|
|
|
|
try {
|
|
final visitRecord = VisitRecord(
|
|
id: const Uuid().v4(),
|
|
restaurantId: restaurantId,
|
|
visitDate: visitDate,
|
|
isConfirmed: isConfirmed,
|
|
createdAt: DateTime.now(),
|
|
);
|
|
|
|
await _repository.addVisitRecord(visitRecord);
|
|
|
|
// 맛집의 마지막 방문일도 업데이트
|
|
final restaurantNotifier = _ref.read(restaurantNotifierProvider.notifier);
|
|
await restaurantNotifier.updateLastVisitDate(restaurantId, visitDate);
|
|
|
|
state = const AsyncValue.data(null);
|
|
} catch (e, stack) {
|
|
state = AsyncValue.error(e, stack);
|
|
}
|
|
}
|
|
|
|
/// 방문 확인
|
|
Future<void> confirmVisit(String visitRecordId) async {
|
|
state = const AsyncValue.loading();
|
|
|
|
try {
|
|
await _repository.confirmVisit(visitRecordId);
|
|
state = const AsyncValue.data(null);
|
|
} catch (e, stack) {
|
|
state = AsyncValue.error(e, stack);
|
|
}
|
|
}
|
|
|
|
/// 방문 기록 삭제
|
|
Future<void> deleteVisitRecord(String id) async {
|
|
state = const AsyncValue.loading();
|
|
|
|
try {
|
|
await _repository.deleteVisitRecord(id);
|
|
state = const AsyncValue.data(null);
|
|
} catch (e, stack) {
|
|
state = AsyncValue.error(e, stack);
|
|
}
|
|
}
|
|
|
|
/// 추천 후 자동 방문 기록 생성
|
|
Future<void> createVisitFromRecommendation({
|
|
required String restaurantId,
|
|
required DateTime recommendationTime,
|
|
}) async {
|
|
// 추천 시간으로부터 1.5시간 후를 방문 시간으로 설정
|
|
final visitTime = recommendationTime.add(const Duration(minutes: 90));
|
|
|
|
await addVisitRecord(
|
|
restaurantId: restaurantId,
|
|
visitDate: visitTime,
|
|
isConfirmed: false, // 나중에 확인 필요
|
|
);
|
|
}
|
|
}
|
|
|
|
/// VisitNotifier Provider
|
|
final visitNotifierProvider =
|
|
StateNotifierProvider<VisitNotifier, AsyncValue<void>>((ref) {
|
|
final repository = ref.watch(visitRepositoryProvider);
|
|
return VisitNotifier(repository, ref);
|
|
});
|
|
|
|
/// 특정 맛집의 마지막 방문일 Provider
|
|
final lastVisitDateProvider = FutureProvider.family<DateTime?, String>((
|
|
ref,
|
|
restaurantId,
|
|
) async {
|
|
final repository = ref.watch(visitRepositoryProvider);
|
|
return repository.getLastVisitDate(restaurantId);
|
|
});
|
|
|
|
/// 기간별 방문 기록 Provider
|
|
final visitRecordsByPeriodProvider =
|
|
FutureProvider.family<
|
|
List<VisitRecord>,
|
|
({DateTime startDate, DateTime endDate})
|
|
>((ref, params) async {
|
|
final allRecords = await ref.watch(visitRecordsProvider.future);
|
|
return allRecords.where((record) {
|
|
return record.visitDate.isAfter(params.startDate) &&
|
|
record.visitDate.isBefore(
|
|
params.endDate.add(const Duration(days: 1)),
|
|
);
|
|
}).toList()..sort((a, b) => b.visitDate.compareTo(a.visitDate));
|
|
});
|
|
|
|
/// 주간 방문 통계 Provider (최근 7일)
|
|
final weeklyVisitStatsProvider = FutureProvider<Map<String, int>>((ref) async {
|
|
final now = DateTime.now();
|
|
final startOfWeek = DateTime(
|
|
now.year,
|
|
now.month,
|
|
now.day,
|
|
).subtract(const Duration(days: 6));
|
|
final records = await ref.watch(
|
|
visitRecordsByPeriodProvider((startDate: startOfWeek, endDate: now)).future,
|
|
);
|
|
|
|
final stats = <String, int>{};
|
|
for (var i = 0; i < 7; i++) {
|
|
final date = startOfWeek.add(Duration(days: i));
|
|
final dateKey = '${date.month}/${date.day}';
|
|
stats[dateKey] = records
|
|
.where(
|
|
(r) =>
|
|
r.visitDate.year == date.year &&
|
|
r.visitDate.month == date.month &&
|
|
r.visitDate.day == date.day,
|
|
)
|
|
.length;
|
|
}
|
|
return stats;
|
|
});
|
|
|
|
/// 자주 방문하는 맛집 Provider (상위 10개)
|
|
final frequentRestaurantsProvider =
|
|
FutureProvider<List<({String restaurantId, int visitCount})>>((ref) async {
|
|
final allRecords = await ref.watch(visitRecordsProvider.future);
|
|
|
|
final visitCounts = <String, int>{};
|
|
for (final record in allRecords) {
|
|
visitCounts[record.restaurantId] =
|
|
(visitCounts[record.restaurantId] ?? 0) + 1;
|
|
}
|
|
|
|
final sorted = visitCounts.entries.toList()
|
|
..sort((a, b) => b.value.compareTo(a.value));
|
|
|
|
return sorted
|
|
.take(10)
|
|
.map((e) => (restaurantId: e.key, visitCount: e.value))
|
|
.toList();
|
|
});
|
|
|
|
/// 방문 기록 정렬 옵션
|
|
enum VisitSortOption {
|
|
dateDesc, // 최신순
|
|
dateAsc, // 오래된순
|
|
restaurant, // 맛집별
|
|
}
|
|
|
|
/// 정렬된 방문 기록 Provider
|
|
final sortedVisitRecordsProvider =
|
|
Provider.family<AsyncValue<List<VisitRecord>>, VisitSortOption>((
|
|
ref,
|
|
sortOption,
|
|
) {
|
|
final recordsAsync = ref.watch(visitRecordsProvider);
|
|
|
|
return recordsAsync.when(
|
|
data: (records) {
|
|
final sorted = List<VisitRecord>.from(records);
|
|
switch (sortOption) {
|
|
case VisitSortOption.dateDesc:
|
|
sorted.sort((a, b) => b.visitDate.compareTo(a.visitDate));
|
|
break;
|
|
case VisitSortOption.dateAsc:
|
|
sorted.sort((a, b) => a.visitDate.compareTo(b.visitDate));
|
|
break;
|
|
case VisitSortOption.restaurant:
|
|
sorted.sort((a, b) => a.restaurantId.compareTo(b.restaurantId));
|
|
break;
|
|
}
|
|
return AsyncValue.data(sorted);
|
|
},
|
|
loading: () => const AsyncValue.loading(),
|
|
error: (error, stack) => AsyncValue.error(error, stack),
|
|
);
|
|
});
|
|
|
|
/// 카테고리별 방문 통계 Provider
|
|
final categoryVisitStatsProvider = FutureProvider<Map<String, int>>((
|
|
ref,
|
|
) async {
|
|
final allRecords = await ref.watch(visitRecordsProvider.future);
|
|
final restaurantsAsync = await ref.watch(restaurantListProvider.future);
|
|
|
|
final categoryCount = <String, int>{};
|
|
|
|
for (final record in allRecords) {
|
|
final restaurant = restaurantsAsync
|
|
.where((r) => r.id == record.restaurantId)
|
|
.firstOrNull;
|
|
if (restaurant != null) {
|
|
categoryCount[restaurant.category] =
|
|
(categoryCount[restaurant.category] ?? 0) + 1;
|
|
}
|
|
}
|
|
|
|
return categoryCount;
|
|
});
|