feat: unified food calendar — extend home screen to future dates + planned meals

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>
This commit is contained in:
dbastrikin
2026-03-21 22:56:17 +02:00
parent bf8dce36c5
commit 9306d59d36
28 changed files with 500 additions and 41 deletions

View File

@@ -2,6 +2,8 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../../features/scan/recognition_service.dart'; import '../../features/scan/recognition_service.dart';
import '../../shared/models/home_summary.dart'; import '../../shared/models/home_summary.dart';
import '../../shared/models/menu.dart';
import '../menu/menu_provider.dart';
import 'home_service.dart'; import 'home_service.dart';
// ── Selected date (persists while app is open) ──────────────── // ── Selected date (persists while app is open) ────────────────
@@ -76,3 +78,21 @@ final allJobsProvider =
StateNotifierProvider<AllJobsNotifier, AsyncValue<List<DishJobSummary>>>( StateNotifierProvider<AllJobsNotifier, AsyncValue<List<DishJobSummary>>>(
(ref) => AllJobsNotifier(ref.read(recognitionServiceProvider)), (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 [];
});

View File

@@ -13,6 +13,7 @@ import '../../core/theme/app_colors.dart';
import '../../shared/models/diary_entry.dart'; import '../../shared/models/diary_entry.dart';
import '../../shared/models/home_summary.dart'; import '../../shared/models/home_summary.dart';
import '../../shared/models/meal_type.dart'; import '../../shared/models/meal_type.dart';
import '../../shared/models/menu.dart';
import '../diary/food_search_sheet.dart'; import '../diary/food_search_sheet.dart';
import '../menu/menu_provider.dart'; import '../menu/menu_provider.dart';
import '../profile/profile_provider.dart'; import '../profile/profile_provider.dart';
@@ -20,6 +21,22 @@ import '../scan/dish_result_screen.dart';
import '../scan/recognition_service.dart'; import '../scan/recognition_service.dart';
import 'home_provider.dart'; import 'home_provider.dart';
// ── Date context ───────────────────────────────────────────────
enum _DateContext { past, today, future }
_DateContext _contextFor(DateTime selected) {
final now = DateTime.now();
final todayNormalized = DateTime(now.year, now.month, now.day);
final selectedNormalized =
DateTime(selected.year, selected.month, selected.day);
if (selectedNormalized.isBefore(todayNormalized)) return _DateContext.past;
if (selectedNormalized.isAtSameMomentAs(todayNormalized)) {
return _DateContext.today;
}
return _DateContext.future;
}
// ── Root screen ─────────────────────────────────────────────── // ── Root screen ───────────────────────────────────────────────
class HomeScreen extends ConsumerWidget { class HomeScreen extends ConsumerWidget {
@@ -37,6 +54,9 @@ class HomeScreen extends ConsumerWidget {
final selectedDate = ref.watch(selectedDateProvider); final selectedDate = ref.watch(selectedDateProvider);
final dateString = formatDateForDiary(selectedDate); final dateString = formatDateForDiary(selectedDate);
final dateContext = _contextFor(selectedDate);
final isFutureDate = dateContext == _DateContext.future;
final diaryState = ref.watch(diaryProvider(dateString)); final diaryState = ref.watch(diaryProvider(dateString));
final entries = diaryState.valueOrNull ?? []; final entries = diaryState.valueOrNull ?? [];
@@ -75,18 +95,22 @@ class HomeScreen extends ConsumerWidget {
ref.read(selectedDateProvider.notifier).state = date, ref.read(selectedDateProvider.notifier).state = date,
), ),
const SizedBox(height: 16), const SizedBox(height: 16),
_CaloriesCard( if (isFutureDate)
loggedCalories: loggedCalories, _PlanningBanner(dateString: dateString)
dailyGoal: dailyGoal, else ...[
goalType: goalType, _CaloriesCard(
), loggedCalories: loggedCalories,
const SizedBox(height: 12), dailyGoal: dailyGoal,
_MacrosRow( goalType: goalType,
proteinG: loggedProtein, ),
fatG: loggedFat, const SizedBox(height: 12),
carbsG: loggedCarbs, _MacrosRow(
), proteinG: loggedProtein,
if (todayJobs.isNotEmpty) ...[ fatG: loggedFat,
carbsG: loggedCarbs,
),
],
if (!isFutureDate && todayJobs.isNotEmpty) ...[
const SizedBox(height: 16), const SizedBox(height: 16),
_TodayJobsWidget(jobs: todayJobs), _TodayJobsWidget(jobs: todayJobs),
], ],
@@ -96,13 +120,13 @@ class HomeScreen extends ConsumerWidget {
entries: entries, entries: entries,
dateString: dateString, dateString: dateString,
), ),
if (expiringSoon.isNotEmpty) ...[ if (!isFutureDate && expiringSoon.isNotEmpty) ...[
const SizedBox(height: 16), const SizedBox(height: 16),
_ExpiringBanner(items: expiringSoon), _ExpiringBanner(items: expiringSoon),
], ],
const SizedBox(height: 16), const SizedBox(height: 16),
_QuickActionsRow(), _QuickActionsRow(),
if (recommendations.isNotEmpty) ...[ if (!isFutureDate && recommendations.isNotEmpty) ...[
const SizedBox(height: 20), const SizedBox(height: 20),
_SectionTitle(l10n.recommendCook), _SectionTitle(l10n.recommendCook),
const SizedBox(height: 12), const SizedBox(height: 12),
@@ -163,8 +187,12 @@ class _DateSelector extends StatefulWidget {
} }
class _DateSelectorState extends State<_DateSelector> { class _DateSelectorState extends State<_DateSelector> {
// Total days available in the past (index 0 = today, index N-1 = oldest) // Strip covers 7 future days + today + 364 past days = 372 items total.
static const _totalDays = 365; // With reverse: true, index 0 is rendered at the RIGHT edge (newest).
// index 0 = today + 7, index 7 = today, index 371 = today - 364.
static const _futureDays = 7;
static const _pastDays = 364;
static const _totalDays = _futureDays + 1 + _pastDays; // 372
static const _pillWidth = 48.0; static const _pillWidth = 48.0;
static const _pillSpacing = 6.0; static const _pillSpacing = 6.0;
@@ -176,12 +204,16 @@ class _DateSelectorState extends State<_DateSelector> {
return DateFormat('EEE, d MMMM', localeCode).format(date) + yearSuffix; return DateFormat('EEE, d MMMM', localeCode).format(date) + yearSuffix;
} }
// Index in the reversed list: 0 = today, 1 = yesterday, … // Maps a date to its index in the reversed ListView.
// today → _futureDays, tomorrow → _futureDays - 1, … , +7 → 0.
// yesterday → _futureDays + 1, … , -364 → _futureDays + 364.
int _indexForDate(DateTime date) { int _indexForDate(DateTime date) {
final today = DateTime.now(); final today = DateTime.now();
final todayNormalized = DateTime(today.year, today.month, today.day); final todayNormalized = DateTime(today.year, today.month, today.day);
final dateNormalized = DateTime(date.year, date.month, date.day); final dateNormalized = DateTime(date.year, date.month, date.day);
return todayNormalized.difference(dateNormalized).inDays.clamp(0, _totalDays - 1); final daysFromToday =
dateNormalized.difference(todayNormalized).inDays;
return (_futureDays - daysFromToday).clamp(0, _totalDays - 1);
} }
double _offsetForIndex(int index) => index * (_pillWidth + _pillSpacing); double _offsetForIndex(int index) => index * (_pillWidth + _pillSpacing);
@@ -192,7 +224,7 @@ class _DateSelectorState extends State<_DateSelector> {
DateTime(previousDay.year, previousDay.month, previousDay.day); DateTime(previousDay.year, previousDay.month, previousDay.day);
final today = DateTime.now(); final today = DateTime.now();
final oldestAllowed = DateTime(today.year, today.month, today.day) final oldestAllowed = DateTime(today.year, today.month, today.day)
.subtract(const Duration(days: _totalDays - 1)); .subtract(const Duration(days: _pastDays));
if (!previousDayNormalized.isBefore(oldestAllowed)) { if (!previousDayNormalized.isBefore(oldestAllowed)) {
widget.onDateSelected(previousDayNormalized); widget.onDateSelected(previousDayNormalized);
} }
@@ -200,11 +232,12 @@ class _DateSelectorState extends State<_DateSelector> {
void _selectNextDay() { void _selectNextDay() {
final today = DateTime.now(); final today = DateTime.now();
final todayNormalized = DateTime(today.year, today.month, today.day); final futureLimitDate = DateTime(today.year, today.month, today.day)
.add(const Duration(days: _futureDays));
final nextDay = widget.selectedDate.add(const Duration(days: 1)); final nextDay = widget.selectedDate.add(const Duration(days: 1));
final nextDayNormalized = final nextDayNormalized =
DateTime(nextDay.year, nextDay.month, nextDay.day); DateTime(nextDay.year, nextDay.month, nextDay.day);
if (!nextDayNormalized.isAfter(todayNormalized)) { if (!nextDayNormalized.isAfter(futureLimitDate)) {
widget.onDateSelected(nextDayNormalized); widget.onDateSelected(nextDayNormalized);
} }
} }
@@ -214,7 +247,7 @@ class _DateSelectorState extends State<_DateSelector> {
widget.onDateSelected(DateTime(today.year, today.month, today.day)); widget.onDateSelected(DateTime(today.year, today.month, today.day));
if (_scrollController.hasClients) { if (_scrollController.hasClients) {
_scrollController.animateTo( _scrollController.animateTo(
0, _offsetForIndex(_futureDays),
duration: const Duration(milliseconds: 300), duration: const Duration(milliseconds: 300),
curve: Curves.easeInOut, curve: Curves.easeInOut,
); );
@@ -257,6 +290,8 @@ class _DateSelectorState extends State<_DateSelector> {
final selectedNormalized = DateTime( final selectedNormalized = DateTime(
widget.selectedDate.year, widget.selectedDate.month, widget.selectedDate.day); widget.selectedDate.year, widget.selectedDate.month, widget.selectedDate.day);
final isToday = selectedNormalized == todayNormalized; final isToday = selectedNormalized == todayNormalized;
final futureLimitDate =
todayNormalized.add(const Duration(days: _futureDays));
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
@@ -298,16 +333,19 @@ class _DateSelectorState extends State<_DateSelector> {
icon: const Icon(Icons.chevron_right), icon: const Icon(Icons.chevron_right),
iconSize: 20, iconSize: 20,
visualDensity: VisualDensity.compact, visualDensity: VisualDensity.compact,
onPressed: null, onPressed: selectedNormalized.isBefore(futureLimitDate)
? _selectNextDay
: null,
), ),
], ],
), ),
), ),
const SizedBox(height: 8), const SizedBox(height: 8),
// ── Day strip ──────────────────────────────────────────── // ── Day strip ────────────────────────────────────────────
// reverse: true → index 0 (7 days from now) at the right edge;
// index _futureDays = today; past dates have higher indices.
SizedBox( SizedBox(
height: 56, height: 56,
// reverse: true → index 0 (today) sits at the right edge
child: ListView.separated( child: ListView.separated(
controller: _scrollController, controller: _scrollController,
scrollDirection: Axis.horizontal, scrollDirection: Axis.horizontal,
@@ -315,9 +353,11 @@ class _DateSelectorState extends State<_DateSelector> {
itemCount: _totalDays, itemCount: _totalDays,
separatorBuilder: (_, __) => const SizedBox(width: _pillSpacing), separatorBuilder: (_, __) => const SizedBox(width: _pillSpacing),
itemBuilder: (listContext, index) { itemBuilder: (listContext, index) {
final date = todayNormalized.subtract(Duration(days: index)); final date = todayNormalized
.add(Duration(days: _futureDays - index));
final isSelected = date == selectedNormalized; final isSelected = date == selectedNormalized;
final isDayToday = date == todayNormalized; final isDayToday = date == todayNormalized;
final isDayFuture = date.isAfter(todayNormalized);
return GestureDetector( return GestureDetector(
onTap: () => widget.onDateSelected(date), onTap: () => widget.onDateSelected(date),
@@ -327,8 +367,17 @@ class _DateSelectorState extends State<_DateSelector> {
decoration: BoxDecoration( decoration: BoxDecoration(
color: isSelected color: isSelected
? theme.colorScheme.primary ? theme.colorScheme.primary
: theme.colorScheme.surfaceContainerHighest, : isDayFuture
? theme.colorScheme.surfaceContainerLow
: theme.colorScheme.surfaceContainerHighest,
borderRadius: BorderRadius.circular(12), borderRadius: BorderRadius.circular(12),
border: isDayFuture && !isSelected
? Border.all(
color: theme.colorScheme.outline
.withValues(alpha: 0.3),
width: 1,
)
: null,
), ),
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
@@ -338,7 +387,10 @@ class _DateSelectorState extends State<_DateSelector> {
style: theme.textTheme.labelSmall?.copyWith( style: theme.textTheme.labelSmall?.copyWith(
color: isSelected color: isSelected
? theme.colorScheme.onPrimary ? theme.colorScheme.onPrimary
: theme.colorScheme.onSurfaceVariant, : isDayFuture
? theme.colorScheme.onSurfaceVariant
.withValues(alpha: 0.7)
: theme.colorScheme.onSurfaceVariant,
), ),
), ),
const SizedBox(height: 2), const SizedBox(height: 2),
@@ -352,7 +404,10 @@ class _DateSelectorState extends State<_DateSelector> {
? theme.colorScheme.onPrimary ? theme.colorScheme.onPrimary
: isDayToday : isDayToday
? theme.colorScheme.primary ? theme.colorScheme.primary
: null, : isDayFuture
? theme.colorScheme.onSurface
.withValues(alpha: 0.5)
: null,
), ),
), ),
], ],
@@ -714,6 +769,8 @@ class _DailyMealsSection extends ConsumerWidget {
Widget build(BuildContext context, WidgetRef ref) { Widget build(BuildContext context, WidgetRef ref) {
final l10n = AppLocalizations.of(context)!; final l10n = AppLocalizations.of(context)!;
final theme = Theme.of(context); final theme = Theme.of(context);
final plannedSlots = ref.watch(plannedMealsProvider(dateString));
return Column( return Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
children: [ children: [
@@ -725,12 +782,16 @@ class _DailyMealsSection extends ConsumerWidget {
final mealEntries = entries final mealEntries = entries
.where((entry) => entry.mealType == mealTypeId) .where((entry) => entry.mealType == mealTypeId)
.toList(); .toList();
final mealPlannedSlots = plannedSlots
.where((slot) => slot.mealType == mealTypeId)
.toList();
return Padding( return Padding(
padding: const EdgeInsets.only(bottom: 8), padding: const EdgeInsets.only(bottom: 8),
child: _MealCard( child: _MealCard(
mealTypeOption: mealTypeOption, mealTypeOption: mealTypeOption,
entries: mealEntries, entries: mealEntries,
dateString: dateString, dateString: dateString,
plannedSlots: mealPlannedSlots,
), ),
); );
}), }),
@@ -928,11 +989,13 @@ class _MealCard extends ConsumerWidget {
final MealTypeOption mealTypeOption; final MealTypeOption mealTypeOption;
final List<DiaryEntry> entries; final List<DiaryEntry> entries;
final String dateString; final String dateString;
final List<MealSlot> plannedSlots;
const _MealCard({ const _MealCard({
required this.mealTypeOption, required this.mealTypeOption,
required this.entries, required this.entries,
required this.dateString, required this.dateString,
this.plannedSlots = const [],
}); });
@override @override
@@ -942,6 +1005,16 @@ class _MealCard extends ConsumerWidget {
final totalCalories = entries.fold<double>( final totalCalories = entries.fold<double>(
0.0, (sum, entry) => sum + (entry.calories ?? 0)); 0.0, (sum, entry) => sum + (entry.calories ?? 0));
// Recipe IDs that are already confirmed in the diary — don't show ghost.
final confirmedRecipeIds =
entries.map((entry) => entry.recipeId).whereType<String>().toSet();
final unconfirmedSlots = plannedSlots
.where((slot) =>
slot.recipe != null &&
!confirmedRecipeIds.contains(slot.recipe!.id))
.toList();
return Card( return Card(
child: Column( child: Column(
children: [ children: [
@@ -986,7 +1059,23 @@ class _MealCard extends ConsumerWidget {
], ],
), ),
), ),
// Diary entries // Planned (ghost) slots from the menu
if (unconfirmedSlots.isNotEmpty) ...[
const Divider(height: 1, indent: 16),
...unconfirmedSlots.map((slot) => _PlannedSlotTile(
slot: slot,
onConfirm: () =>
ref.read(diaryProvider(dateString).notifier).add({
'date': dateString,
'meal_type': mealTypeOption.id,
'recipe_id': slot.recipe!.id,
'name': slot.recipe!.title,
'portions': 1,
'source': 'menu_plan',
}),
)),
],
// Confirmed diary entries
if (entries.isNotEmpty) ...[ if (entries.isNotEmpty) ...[
const Divider(height: 1, indent: 16), const Divider(height: 1, indent: 16),
...entries.map((entry) => _DiaryEntryTile( ...entries.map((entry) => _DiaryEntryTile(
@@ -1054,6 +1143,104 @@ class _DiaryEntryTile extends StatelessWidget {
} }
} }
// ── Planning banner (future dates) ────────────────────────────
class _PlanningBanner extends StatelessWidget {
final String dateString;
const _PlanningBanner({required this.dateString});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final theme = Theme.of(context);
final localeCode = Localizations.localeOf(context).toString();
String formattedDate;
try {
final date = DateTime.parse(dateString);
formattedDate = DateFormat('EEE, d MMMM', localeCode).format(date);
} catch (_) {
formattedDate = dateString;
}
return Container(
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12),
decoration: BoxDecoration(
color: theme.colorScheme.primaryContainer.withValues(alpha: 0.5),
borderRadius: BorderRadius.circular(12),
),
child: Row(
children: [
Icon(Icons.calendar_today_outlined,
color: theme.colorScheme.onPrimaryContainer, size: 20),
const SizedBox(width: 10),
Text(
l10n.planningForDate(formattedDate),
style: theme.textTheme.bodyMedium?.copyWith(
color: theme.colorScheme.onPrimaryContainer,
fontWeight: FontWeight.w600,
),
),
],
),
);
}
}
// ── Planned slot tile (ghost entry from menu) ──────────────────
class _PlannedSlotTile extends StatelessWidget {
final MealSlot slot;
final VoidCallback onConfirm;
const _PlannedSlotTile({required this.slot, required this.onConfirm});
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
final theme = Theme.of(context);
final recipe = slot.recipe;
if (recipe == null) return const SizedBox.shrink();
final calories = recipe.nutrition?.calories.toInt();
return Opacity(
opacity: 0.75,
child: ListTile(
dense: true,
title: Text(recipe.title, style: theme.textTheme.bodyMedium),
subtitle: Text(
l10n.plannedMealLabel,
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.primary.withValues(alpha: 0.8),
),
),
trailing: Row(
mainAxisSize: MainAxisSize.min,
children: [
if (calories != null)
Text(
'$calories ${l10n.caloriesUnit}',
style: theme.textTheme.bodySmall?.copyWith(
color: theme.colorScheme.onSurfaceVariant,
),
),
const SizedBox(width: 4),
IconButton(
icon: Icon(Icons.check_circle_outline,
size: 20, color: theme.colorScheme.primary),
visualDensity: VisualDensity.compact,
tooltip: l10n.markAsEaten,
onPressed: onConfirm,
),
],
),
),
);
}
}
// ── Expiring banner ─────────────────────────────────────────── // ── Expiring banner ───────────────────────────────────────────
class _ExpiringBanner extends StatelessWidget { class _ExpiringBanner extends StatelessWidget {

View File

@@ -29,6 +29,12 @@ final currentWeekProvider = StateProvider<String>((ref) {
return (thu.year, week); return (thu.year, week);
} }
/// Returns the ISO 8601 week string for [date], e.g. "2026-W12".
String isoWeekString(DateTime date) {
final (year, week) = _isoWeek(date.toUtc());
return '$year-W${week.toString().padLeft(2, '0')}';
}
// ── Menu notifier ───────────────────────────────────────────── // ── Menu notifier ─────────────────────────────────────────────
class MenuNotifier extends StateNotifier<AsyncValue<MenuPlan?>> { class MenuNotifier extends StateNotifier<AsyncValue<MenuPlan?>> {

View File

@@ -123,5 +123,13 @@
}, },
"servingsLabel": "حصص", "servingsLabel": "حصص",
"addToDiary": "إضافة إلى اليومية", "addToDiary": "إضافة إلى اليومية",
"scanDishPhoto": "مسح الصورة" "scanDishPhoto": "مسح الصورة",
"planningForDate": "",
"@planningForDate": {
"placeholders": {
"date": { "type": "String" }
}
},
"markAsEaten": "",
"plannedMealLabel": ""
} }

View File

@@ -123,5 +123,13 @@
}, },
"servingsLabel": "Portionen", "servingsLabel": "Portionen",
"addToDiary": "Zum Tagebuch hinzufügen", "addToDiary": "Zum Tagebuch hinzufügen",
"scanDishPhoto": "Foto scannen" "scanDishPhoto": "Foto scannen",
"planningForDate": "",
"@planningForDate": {
"placeholders": {
"date": { "type": "String" }
}
},
"markAsEaten": "",
"plannedMealLabel": ""
} }

View File

@@ -121,5 +121,13 @@
}, },
"servingsLabel": "Servings", "servingsLabel": "Servings",
"addToDiary": "Add to diary", "addToDiary": "Add to diary",
"scanDishPhoto": "Scan photo" "scanDishPhoto": "Scan photo",
"planningForDate": "Planning for {date}",
"@planningForDate": {
"placeholders": {
"date": { "type": "String" }
}
},
"markAsEaten": "Mark as eaten",
"plannedMealLabel": "Planned"
} }

View File

@@ -123,5 +123,13 @@
}, },
"servingsLabel": "Porciones", "servingsLabel": "Porciones",
"addToDiary": "Añadir al diario", "addToDiary": "Añadir al diario",
"scanDishPhoto": "Escanear foto" "scanDishPhoto": "Escanear foto",
"planningForDate": "",
"@planningForDate": {
"placeholders": {
"date": { "type": "String" }
}
},
"markAsEaten": "",
"plannedMealLabel": ""
} }

View File

@@ -123,5 +123,13 @@
}, },
"servingsLabel": "Portions", "servingsLabel": "Portions",
"addToDiary": "Ajouter au journal", "addToDiary": "Ajouter au journal",
"scanDishPhoto": "Scanner une photo" "scanDishPhoto": "Scanner une photo",
"planningForDate": "",
"@planningForDate": {
"placeholders": {
"date": { "type": "String" }
}
},
"markAsEaten": "",
"plannedMealLabel": ""
} }

View File

@@ -123,5 +123,13 @@
}, },
"servingsLabel": "सर्विंग", "servingsLabel": "सर्विंग",
"addToDiary": "डायरी में जोड़ें", "addToDiary": "डायरी में जोड़ें",
"scanDishPhoto": "फ़ोटो स्कैन करें" "scanDishPhoto": "फ़ोटो स्कैन करें",
"planningForDate": "",
"@planningForDate": {
"placeholders": {
"date": { "type": "String" }
}
},
"markAsEaten": "",
"plannedMealLabel": ""
} }

View File

@@ -123,5 +123,13 @@
}, },
"servingsLabel": "Porzioni", "servingsLabel": "Porzioni",
"addToDiary": "Aggiungi al diario", "addToDiary": "Aggiungi al diario",
"scanDishPhoto": "Scansiona foto" "scanDishPhoto": "Scansiona foto",
"planningForDate": "",
"@planningForDate": {
"placeholders": {
"date": { "type": "String" }
}
},
"markAsEaten": "",
"plannedMealLabel": ""
} }

View File

@@ -123,5 +123,13 @@
}, },
"servingsLabel": "人前", "servingsLabel": "人前",
"addToDiary": "日記に追加", "addToDiary": "日記に追加",
"scanDishPhoto": "写真をスキャン" "scanDishPhoto": "写真をスキャン",
"planningForDate": "",
"@planningForDate": {
"placeholders": {
"date": { "type": "String" }
}
},
"markAsEaten": "",
"plannedMealLabel": ""
} }

View File

@@ -123,5 +123,13 @@
}, },
"servingsLabel": "인분", "servingsLabel": "인분",
"addToDiary": "일기에 추가", "addToDiary": "일기에 추가",
"scanDishPhoto": "사진 스캔" "scanDishPhoto": "사진 스캔",
"planningForDate": "",
"@planningForDate": {
"placeholders": {
"date": { "type": "String" }
}
},
"markAsEaten": "",
"plannedMealLabel": ""
} }

View File

@@ -789,6 +789,24 @@ abstract class AppLocalizations {
/// In en, this message translates to: /// In en, this message translates to:
/// **'Scan photo'** /// **'Scan photo'**
String get scanDishPhoto; String get scanDishPhoto;
/// No description provided for @planningForDate.
///
/// In en, this message translates to:
/// **'Planning for {date}'**
String planningForDate(String date);
/// No description provided for @markAsEaten.
///
/// In en, this message translates to:
/// **'Mark as eaten'**
String get markAsEaten;
/// No description provided for @plannedMealLabel.
///
/// In en, this message translates to:
/// **'Planned'**
String get plannedMealLabel;
} }
class _AppLocalizationsDelegate class _AppLocalizationsDelegate

View File

@@ -348,4 +348,15 @@ class AppLocalizationsAr extends AppLocalizations {
@override @override
String get scanDishPhoto => 'مسح الصورة'; String get scanDishPhoto => 'مسح الصورة';
@override
String planningForDate(String date) {
return '';
}
@override
String get markAsEaten => '';
@override
String get plannedMealLabel => '';
} }

View File

@@ -350,4 +350,15 @@ class AppLocalizationsDe extends AppLocalizations {
@override @override
String get scanDishPhoto => 'Foto scannen'; String get scanDishPhoto => 'Foto scannen';
@override
String planningForDate(String date) {
return '';
}
@override
String get markAsEaten => '';
@override
String get plannedMealLabel => '';
} }

View File

@@ -348,4 +348,15 @@ class AppLocalizationsEn extends AppLocalizations {
@override @override
String get scanDishPhoto => 'Scan photo'; String get scanDishPhoto => 'Scan photo';
@override
String planningForDate(String date) {
return 'Planning for $date';
}
@override
String get markAsEaten => 'Mark as eaten';
@override
String get plannedMealLabel => 'Planned';
} }

View File

@@ -350,4 +350,15 @@ class AppLocalizationsEs extends AppLocalizations {
@override @override
String get scanDishPhoto => 'Escanear foto'; String get scanDishPhoto => 'Escanear foto';
@override
String planningForDate(String date) {
return '';
}
@override
String get markAsEaten => '';
@override
String get plannedMealLabel => '';
} }

View File

@@ -351,4 +351,15 @@ class AppLocalizationsFr extends AppLocalizations {
@override @override
String get scanDishPhoto => 'Scanner une photo'; String get scanDishPhoto => 'Scanner une photo';
@override
String planningForDate(String date) {
return '';
}
@override
String get markAsEaten => '';
@override
String get plannedMealLabel => '';
} }

View File

@@ -349,4 +349,15 @@ class AppLocalizationsHi extends AppLocalizations {
@override @override
String get scanDishPhoto => 'फ़ोटो स्कैन करें'; String get scanDishPhoto => 'फ़ोटो स्कैन करें';
@override
String planningForDate(String date) {
return '';
}
@override
String get markAsEaten => '';
@override
String get plannedMealLabel => '';
} }

View File

@@ -350,4 +350,15 @@ class AppLocalizationsIt extends AppLocalizations {
@override @override
String get scanDishPhoto => 'Scansiona foto'; String get scanDishPhoto => 'Scansiona foto';
@override
String planningForDate(String date) {
return '';
}
@override
String get markAsEaten => '';
@override
String get plannedMealLabel => '';
} }

View File

@@ -347,4 +347,15 @@ class AppLocalizationsJa extends AppLocalizations {
@override @override
String get scanDishPhoto => '写真をスキャン'; String get scanDishPhoto => '写真をスキャン';
@override
String planningForDate(String date) {
return '';
}
@override
String get markAsEaten => '';
@override
String get plannedMealLabel => '';
} }

View File

@@ -347,4 +347,15 @@ class AppLocalizationsKo extends AppLocalizations {
@override @override
String get scanDishPhoto => '사진 스캔'; String get scanDishPhoto => '사진 스캔';
@override
String planningForDate(String date) {
return '';
}
@override
String get markAsEaten => '';
@override
String get plannedMealLabel => '';
} }

View File

@@ -350,4 +350,15 @@ class AppLocalizationsPt extends AppLocalizations {
@override @override
String get scanDishPhoto => 'Escanear foto'; String get scanDishPhoto => 'Escanear foto';
@override
String planningForDate(String date) {
return '';
}
@override
String get markAsEaten => '';
@override
String get plannedMealLabel => '';
} }

View File

@@ -348,4 +348,15 @@ class AppLocalizationsRu extends AppLocalizations {
@override @override
String get scanDishPhoto => 'Сканировать фото'; String get scanDishPhoto => 'Сканировать фото';
@override
String planningForDate(String date) {
return 'Планирование на $date';
}
@override
String get markAsEaten => 'Отметить как съеденное';
@override
String get plannedMealLabel => 'Запланировано';
} }

View File

@@ -347,4 +347,15 @@ class AppLocalizationsZh extends AppLocalizations {
@override @override
String get scanDishPhoto => '扫描照片'; String get scanDishPhoto => '扫描照片';
@override
String planningForDate(String date) {
return '';
}
@override
String get markAsEaten => '';
@override
String get plannedMealLabel => '';
} }

View File

@@ -123,5 +123,13 @@
}, },
"servingsLabel": "Porções", "servingsLabel": "Porções",
"addToDiary": "Adicionar ao diário", "addToDiary": "Adicionar ao diário",
"scanDishPhoto": "Escanear foto" "scanDishPhoto": "Escanear foto",
"planningForDate": "",
"@planningForDate": {
"placeholders": {
"date": { "type": "String" }
}
},
"markAsEaten": "",
"plannedMealLabel": ""
} }

View File

@@ -121,5 +121,13 @@
}, },
"servingsLabel": "Порций", "servingsLabel": "Порций",
"addToDiary": "Добавить в дневник", "addToDiary": "Добавить в дневник",
"scanDishPhoto": "Сканировать фото" "scanDishPhoto": "Сканировать фото",
"planningForDate": "Планирование на {date}",
"@planningForDate": {
"placeholders": {
"date": { "type": "String" }
}
},
"markAsEaten": "Отметить как съеденное",
"plannedMealLabel": "Запланировано"
} }

View File

@@ -123,5 +123,13 @@
}, },
"servingsLabel": "份数", "servingsLabel": "份数",
"addToDiary": "添加到日记", "addToDiary": "添加到日记",
"scanDishPhoto": "扫描照片" "scanDishPhoto": "扫描照片",
"planningForDate": "",
"@planningForDate": {
"placeholders": {
"date": { "type": "String" }
}
},
"markAsEaten": "",
"plannedMealLabel": ""
} }