feat(app): add manual entry and sharing flows
This commit is contained in:
@@ -12,29 +12,36 @@ final visitRecordsProvider = StreamProvider<List<VisitRecord>>((ref) {
|
||||
});
|
||||
|
||||
/// 날짜별 방문 기록 Provider
|
||||
final visitRecordsByDateProvider = FutureProvider.family<List<VisitRecord>, DateTime>((ref, date) async {
|
||||
final repository = ref.watch(visitRepositoryProvider);
|
||||
return repository.getVisitRecordsByDate(date);
|
||||
});
|
||||
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);
|
||||
});
|
||||
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);
|
||||
});
|
||||
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));
|
||||
VisitNotifier(this._repository, this._ref)
|
||||
: super(const AsyncValue.data(null));
|
||||
|
||||
/// 방문 기록 추가
|
||||
Future<void> addVisitRecord({
|
||||
@@ -43,7 +50,7 @@ class VisitNotifier extends StateNotifier<AsyncValue<void>> {
|
||||
bool isConfirmed = false,
|
||||
}) async {
|
||||
state = const AsyncValue.loading();
|
||||
|
||||
|
||||
try {
|
||||
final visitRecord = VisitRecord(
|
||||
id: const Uuid().v4(),
|
||||
@@ -54,11 +61,11 @@ class VisitNotifier extends StateNotifier<AsyncValue<void>> {
|
||||
);
|
||||
|
||||
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);
|
||||
@@ -68,7 +75,7 @@ class VisitNotifier extends StateNotifier<AsyncValue<void>> {
|
||||
/// 방문 확인
|
||||
Future<void> confirmVisit(String visitRecordId) async {
|
||||
state = const AsyncValue.loading();
|
||||
|
||||
|
||||
try {
|
||||
await _repository.confirmVisit(visitRecordId);
|
||||
state = const AsyncValue.data(null);
|
||||
@@ -80,7 +87,7 @@ class VisitNotifier extends StateNotifier<AsyncValue<void>> {
|
||||
/// 방문 기록 삭제
|
||||
Future<void> deleteVisitRecord(String id) async {
|
||||
state = const AsyncValue.loading();
|
||||
|
||||
|
||||
try {
|
||||
await _repository.deleteVisitRecord(id);
|
||||
state = const AsyncValue.data(null);
|
||||
@@ -96,7 +103,7 @@ class VisitNotifier extends StateNotifier<AsyncValue<void>> {
|
||||
}) async {
|
||||
// 추천 시간으로부터 1.5시간 후를 방문 시간으로 설정
|
||||
final visitTime = recommendationTime.add(const Duration(minutes: 90));
|
||||
|
||||
|
||||
await addVisitRecord(
|
||||
restaurantId: restaurantId,
|
||||
visitDate: visitTime,
|
||||
@@ -106,109 +113,138 @@ class VisitNotifier extends StateNotifier<AsyncValue<void>> {
|
||||
}
|
||||
|
||||
/// VisitNotifier Provider
|
||||
final visitNotifierProvider = StateNotifierProvider<VisitNotifier, AsyncValue<void>>((ref) {
|
||||
final repository = ref.watch(visitRepositoryProvider);
|
||||
return VisitNotifier(repository, ref);
|
||||
});
|
||||
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 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));
|
||||
});
|
||||
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 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;
|
||||
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();
|
||||
});
|
||||
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, // 오래된순
|
||||
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),
|
||||
);
|
||||
});
|
||||
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 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;
|
||||
final restaurant = restaurantsAsync
|
||||
.where((r) => r.id == record.restaurantId)
|
||||
.firstOrNull;
|
||||
if (restaurant != null) {
|
||||
categoryCount[restaurant.category] = (categoryCount[restaurant.category] ?? 0) + 1;
|
||||
categoryCount[restaurant.category] =
|
||||
(categoryCount[restaurant.category] ?? 0) + 1;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return categoryCount;
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user