Phase 1: date strip now covers today + 7 future days; right chevron enabled; future pills rendered in lighter style. Phase 2: home screen shows DateContext (past/today/future): - future dates: hide calorie ring + macros, show PlanningBanner - plannedMealsProvider derives from cached menuProvider (no extra API call) - _MealCard shows ghost PlannedSlotTile for unconfirmed menu slots - "Mark as eaten" creates a diary entry (source: menu_plan) via existing API New l10n keys (12 locales): planningForDate, markAsEaten, plannedMealLabel Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
99 lines
3.4 KiB
Dart
99 lines
3.4 KiB
Dart
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
|
|
import '../../features/scan/recognition_service.dart';
|
|
import '../../shared/models/home_summary.dart';
|
|
import '../../shared/models/menu.dart';
|
|
import '../menu/menu_provider.dart';
|
|
import 'home_service.dart';
|
|
|
|
// ── Selected date (persists while app is open) ────────────────
|
|
|
|
/// The date currently viewed on the home screen.
|
|
/// Defaults to today; can be changed via the date selector.
|
|
final selectedDateProvider = StateProvider<DateTime>((ref) => DateTime.now());
|
|
|
|
/// Formats a [DateTime] to the 'YYYY-MM-DD' string expected by the diary API.
|
|
String formatDateForDiary(DateTime date) =>
|
|
'${date.year}-${date.month.toString().padLeft(2, '0')}-'
|
|
'${date.day.toString().padLeft(2, '0')}';
|
|
|
|
// ── Home summary ──────────────────────────────────────────────
|
|
|
|
class HomeNotifier extends StateNotifier<AsyncValue<HomeSummary>> {
|
|
final HomeService _service;
|
|
|
|
HomeNotifier(this._service) : super(const AsyncValue.loading()) {
|
|
load();
|
|
}
|
|
|
|
Future<void> load() async {
|
|
state = const AsyncValue.loading();
|
|
state = await AsyncValue.guard(() => _service.getSummary());
|
|
}
|
|
}
|
|
|
|
final homeProvider =
|
|
StateNotifierProvider<HomeNotifier, AsyncValue<HomeSummary>>(
|
|
(ref) => HomeNotifier(ref.read(homeServiceProvider)),
|
|
);
|
|
|
|
// ── Today's unlinked recognition jobs ─────────────────────────
|
|
|
|
class TodayJobsNotifier
|
|
extends StateNotifier<AsyncValue<List<DishJobSummary>>> {
|
|
final RecognitionService _service;
|
|
|
|
TodayJobsNotifier(this._service) : super(const AsyncValue.loading()) {
|
|
load();
|
|
}
|
|
|
|
Future<void> load() async {
|
|
state = const AsyncValue.loading();
|
|
state =
|
|
await AsyncValue.guard(() => _service.listTodayUnlinkedJobs());
|
|
}
|
|
}
|
|
|
|
final todayJobsProvider =
|
|
StateNotifierProvider<TodayJobsNotifier, AsyncValue<List<DishJobSummary>>>(
|
|
(ref) => TodayJobsNotifier(ref.read(recognitionServiceProvider)),
|
|
);
|
|
|
|
// ── All recognition jobs (history screen) ─────────────────────
|
|
|
|
class AllJobsNotifier extends StateNotifier<AsyncValue<List<DishJobSummary>>> {
|
|
final RecognitionService _service;
|
|
|
|
AllJobsNotifier(this._service) : super(const AsyncValue.loading()) {
|
|
load();
|
|
}
|
|
|
|
Future<void> load() async {
|
|
state = const AsyncValue.loading();
|
|
state = await AsyncValue.guard(() => _service.listAllJobs());
|
|
}
|
|
}
|
|
|
|
final allJobsProvider =
|
|
StateNotifierProvider<AllJobsNotifier, AsyncValue<List<DishJobSummary>>>(
|
|
(ref) => AllJobsNotifier(ref.read(recognitionServiceProvider)),
|
|
);
|
|
|
|
// ── Planned meals from menu ────────────────────────────────────
|
|
|
|
/// Returns planned [MealSlot]s from the menu plan for [dateString].
|
|
/// Derives from the already-cached weekly menu — no extra network call.
|
|
/// Returns an empty list when no plan exists for that week.
|
|
final plannedMealsProvider =
|
|
Provider.family<List<MealSlot>, String>((ref, dateString) {
|
|
final date = DateTime.parse(dateString);
|
|
final weekString = isoWeekString(date);
|
|
final menuState = ref.watch(menuProvider(weekString));
|
|
final plan = menuState.valueOrNull;
|
|
if (plan == null) return [];
|
|
for (final day in plan.days) {
|
|
if (day.date == dateString) return day.meals;
|
|
}
|
|
return [];
|
|
});
|