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:
@@ -2,6 +2,8 @@ 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) ────────────────
|
||||
@@ -76,3 +78,21 @@ 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 [];
|
||||
});
|
||||
|
||||
@@ -13,6 +13,7 @@ import '../../core/theme/app_colors.dart';
|
||||
import '../../shared/models/diary_entry.dart';
|
||||
import '../../shared/models/home_summary.dart';
|
||||
import '../../shared/models/meal_type.dart';
|
||||
import '../../shared/models/menu.dart';
|
||||
import '../diary/food_search_sheet.dart';
|
||||
import '../menu/menu_provider.dart';
|
||||
import '../profile/profile_provider.dart';
|
||||
@@ -20,6 +21,22 @@ import '../scan/dish_result_screen.dart';
|
||||
import '../scan/recognition_service.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 ───────────────────────────────────────────────
|
||||
|
||||
class HomeScreen extends ConsumerWidget {
|
||||
@@ -37,6 +54,9 @@ class HomeScreen extends ConsumerWidget {
|
||||
|
||||
final selectedDate = ref.watch(selectedDateProvider);
|
||||
final dateString = formatDateForDiary(selectedDate);
|
||||
final dateContext = _contextFor(selectedDate);
|
||||
final isFutureDate = dateContext == _DateContext.future;
|
||||
|
||||
final diaryState = ref.watch(diaryProvider(dateString));
|
||||
final entries = diaryState.valueOrNull ?? [];
|
||||
|
||||
@@ -75,18 +95,22 @@ class HomeScreen extends ConsumerWidget {
|
||||
ref.read(selectedDateProvider.notifier).state = date,
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
_CaloriesCard(
|
||||
loggedCalories: loggedCalories,
|
||||
dailyGoal: dailyGoal,
|
||||
goalType: goalType,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_MacrosRow(
|
||||
proteinG: loggedProtein,
|
||||
fatG: loggedFat,
|
||||
carbsG: loggedCarbs,
|
||||
),
|
||||
if (todayJobs.isNotEmpty) ...[
|
||||
if (isFutureDate)
|
||||
_PlanningBanner(dateString: dateString)
|
||||
else ...[
|
||||
_CaloriesCard(
|
||||
loggedCalories: loggedCalories,
|
||||
dailyGoal: dailyGoal,
|
||||
goalType: goalType,
|
||||
),
|
||||
const SizedBox(height: 12),
|
||||
_MacrosRow(
|
||||
proteinG: loggedProtein,
|
||||
fatG: loggedFat,
|
||||
carbsG: loggedCarbs,
|
||||
),
|
||||
],
|
||||
if (!isFutureDate && todayJobs.isNotEmpty) ...[
|
||||
const SizedBox(height: 16),
|
||||
_TodayJobsWidget(jobs: todayJobs),
|
||||
],
|
||||
@@ -96,13 +120,13 @@ class HomeScreen extends ConsumerWidget {
|
||||
entries: entries,
|
||||
dateString: dateString,
|
||||
),
|
||||
if (expiringSoon.isNotEmpty) ...[
|
||||
if (!isFutureDate && expiringSoon.isNotEmpty) ...[
|
||||
const SizedBox(height: 16),
|
||||
_ExpiringBanner(items: expiringSoon),
|
||||
],
|
||||
const SizedBox(height: 16),
|
||||
_QuickActionsRow(),
|
||||
if (recommendations.isNotEmpty) ...[
|
||||
if (!isFutureDate && recommendations.isNotEmpty) ...[
|
||||
const SizedBox(height: 20),
|
||||
_SectionTitle(l10n.recommendCook),
|
||||
const SizedBox(height: 12),
|
||||
@@ -163,8 +187,12 @@ class _DateSelector extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _DateSelectorState extends State<_DateSelector> {
|
||||
// Total days available in the past (index 0 = today, index N-1 = oldest)
|
||||
static const _totalDays = 365;
|
||||
// Strip covers 7 future days + today + 364 past days = 372 items total.
|
||||
// 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 _pillSpacing = 6.0;
|
||||
|
||||
@@ -176,12 +204,16 @@ class _DateSelectorState extends State<_DateSelector> {
|
||||
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) {
|
||||
final today = DateTime.now();
|
||||
final todayNormalized = DateTime(today.year, today.month, today.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);
|
||||
@@ -192,7 +224,7 @@ class _DateSelectorState extends State<_DateSelector> {
|
||||
DateTime(previousDay.year, previousDay.month, previousDay.day);
|
||||
final today = DateTime.now();
|
||||
final oldestAllowed = DateTime(today.year, today.month, today.day)
|
||||
.subtract(const Duration(days: _totalDays - 1));
|
||||
.subtract(const Duration(days: _pastDays));
|
||||
if (!previousDayNormalized.isBefore(oldestAllowed)) {
|
||||
widget.onDateSelected(previousDayNormalized);
|
||||
}
|
||||
@@ -200,11 +232,12 @@ class _DateSelectorState extends State<_DateSelector> {
|
||||
|
||||
void _selectNextDay() {
|
||||
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 nextDayNormalized =
|
||||
DateTime(nextDay.year, nextDay.month, nextDay.day);
|
||||
if (!nextDayNormalized.isAfter(todayNormalized)) {
|
||||
if (!nextDayNormalized.isAfter(futureLimitDate)) {
|
||||
widget.onDateSelected(nextDayNormalized);
|
||||
}
|
||||
}
|
||||
@@ -214,7 +247,7 @@ class _DateSelectorState extends State<_DateSelector> {
|
||||
widget.onDateSelected(DateTime(today.year, today.month, today.day));
|
||||
if (_scrollController.hasClients) {
|
||||
_scrollController.animateTo(
|
||||
0,
|
||||
_offsetForIndex(_futureDays),
|
||||
duration: const Duration(milliseconds: 300),
|
||||
curve: Curves.easeInOut,
|
||||
);
|
||||
@@ -257,6 +290,8 @@ class _DateSelectorState extends State<_DateSelector> {
|
||||
final selectedNormalized = DateTime(
|
||||
widget.selectedDate.year, widget.selectedDate.month, widget.selectedDate.day);
|
||||
final isToday = selectedNormalized == todayNormalized;
|
||||
final futureLimitDate =
|
||||
todayNormalized.add(const Duration(days: _futureDays));
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
@@ -298,16 +333,19 @@ class _DateSelectorState extends State<_DateSelector> {
|
||||
icon: const Icon(Icons.chevron_right),
|
||||
iconSize: 20,
|
||||
visualDensity: VisualDensity.compact,
|
||||
onPressed: null,
|
||||
onPressed: selectedNormalized.isBefore(futureLimitDate)
|
||||
? _selectNextDay
|
||||
: null,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
// ── Day strip ────────────────────────────────────────────
|
||||
// reverse: true → index 0 (7 days from now) at the right edge;
|
||||
// index _futureDays = today; past dates have higher indices.
|
||||
SizedBox(
|
||||
height: 56,
|
||||
// reverse: true → index 0 (today) sits at the right edge
|
||||
child: ListView.separated(
|
||||
controller: _scrollController,
|
||||
scrollDirection: Axis.horizontal,
|
||||
@@ -315,9 +353,11 @@ class _DateSelectorState extends State<_DateSelector> {
|
||||
itemCount: _totalDays,
|
||||
separatorBuilder: (_, __) => const SizedBox(width: _pillSpacing),
|
||||
itemBuilder: (listContext, index) {
|
||||
final date = todayNormalized.subtract(Duration(days: index));
|
||||
final date = todayNormalized
|
||||
.add(Duration(days: _futureDays - index));
|
||||
final isSelected = date == selectedNormalized;
|
||||
final isDayToday = date == todayNormalized;
|
||||
final isDayFuture = date.isAfter(todayNormalized);
|
||||
|
||||
return GestureDetector(
|
||||
onTap: () => widget.onDateSelected(date),
|
||||
@@ -327,8 +367,17 @@ class _DateSelectorState extends State<_DateSelector> {
|
||||
decoration: BoxDecoration(
|
||||
color: isSelected
|
||||
? theme.colorScheme.primary
|
||||
: theme.colorScheme.surfaceContainerHighest,
|
||||
: isDayFuture
|
||||
? theme.colorScheme.surfaceContainerLow
|
||||
: theme.colorScheme.surfaceContainerHighest,
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: isDayFuture && !isSelected
|
||||
? Border.all(
|
||||
color: theme.colorScheme.outline
|
||||
.withValues(alpha: 0.3),
|
||||
width: 1,
|
||||
)
|
||||
: null,
|
||||
),
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
@@ -338,7 +387,10 @@ class _DateSelectorState extends State<_DateSelector> {
|
||||
style: theme.textTheme.labelSmall?.copyWith(
|
||||
color: isSelected
|
||||
? theme.colorScheme.onPrimary
|
||||
: theme.colorScheme.onSurfaceVariant,
|
||||
: isDayFuture
|
||||
? theme.colorScheme.onSurfaceVariant
|
||||
.withValues(alpha: 0.7)
|
||||
: theme.colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 2),
|
||||
@@ -352,7 +404,10 @@ class _DateSelectorState extends State<_DateSelector> {
|
||||
? theme.colorScheme.onPrimary
|
||||
: isDayToday
|
||||
? 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) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final theme = Theme.of(context);
|
||||
final plannedSlots = ref.watch(plannedMealsProvider(dateString));
|
||||
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
@@ -725,12 +782,16 @@ class _DailyMealsSection extends ConsumerWidget {
|
||||
final mealEntries = entries
|
||||
.where((entry) => entry.mealType == mealTypeId)
|
||||
.toList();
|
||||
final mealPlannedSlots = plannedSlots
|
||||
.where((slot) => slot.mealType == mealTypeId)
|
||||
.toList();
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: _MealCard(
|
||||
mealTypeOption: mealTypeOption,
|
||||
entries: mealEntries,
|
||||
dateString: dateString,
|
||||
plannedSlots: mealPlannedSlots,
|
||||
),
|
||||
);
|
||||
}),
|
||||
@@ -928,11 +989,13 @@ class _MealCard extends ConsumerWidget {
|
||||
final MealTypeOption mealTypeOption;
|
||||
final List<DiaryEntry> entries;
|
||||
final String dateString;
|
||||
final List<MealSlot> plannedSlots;
|
||||
|
||||
const _MealCard({
|
||||
required this.mealTypeOption,
|
||||
required this.entries,
|
||||
required this.dateString,
|
||||
this.plannedSlots = const [],
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -942,6 +1005,16 @@ class _MealCard extends ConsumerWidget {
|
||||
final totalCalories = entries.fold<double>(
|
||||
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(
|
||||
child: Column(
|
||||
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) ...[
|
||||
const Divider(height: 1, indent: 16),
|
||||
...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 ───────────────────────────────────────────
|
||||
|
||||
class _ExpiringBanner extends StatelessWidget {
|
||||
|
||||
@@ -29,6 +29,12 @@ final currentWeekProvider = StateProvider<String>((ref) {
|
||||
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 ─────────────────────────────────────────────
|
||||
|
||||
class MenuNotifier extends StateNotifier<AsyncValue<MenuPlan?>> {
|
||||
|
||||
@@ -123,5 +123,13 @@
|
||||
},
|
||||
"servingsLabel": "حصص",
|
||||
"addToDiary": "إضافة إلى اليومية",
|
||||
"scanDishPhoto": "مسح الصورة"
|
||||
"scanDishPhoto": "مسح الصورة",
|
||||
"planningForDate": "",
|
||||
"@planningForDate": {
|
||||
"placeholders": {
|
||||
"date": { "type": "String" }
|
||||
}
|
||||
},
|
||||
"markAsEaten": "",
|
||||
"plannedMealLabel": ""
|
||||
}
|
||||
|
||||
@@ -123,5 +123,13 @@
|
||||
},
|
||||
"servingsLabel": "Portionen",
|
||||
"addToDiary": "Zum Tagebuch hinzufügen",
|
||||
"scanDishPhoto": "Foto scannen"
|
||||
"scanDishPhoto": "Foto scannen",
|
||||
"planningForDate": "",
|
||||
"@planningForDate": {
|
||||
"placeholders": {
|
||||
"date": { "type": "String" }
|
||||
}
|
||||
},
|
||||
"markAsEaten": "",
|
||||
"plannedMealLabel": ""
|
||||
}
|
||||
|
||||
@@ -121,5 +121,13 @@
|
||||
},
|
||||
"servingsLabel": "Servings",
|
||||
"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"
|
||||
}
|
||||
|
||||
@@ -123,5 +123,13 @@
|
||||
},
|
||||
"servingsLabel": "Porciones",
|
||||
"addToDiary": "Añadir al diario",
|
||||
"scanDishPhoto": "Escanear foto"
|
||||
"scanDishPhoto": "Escanear foto",
|
||||
"planningForDate": "",
|
||||
"@planningForDate": {
|
||||
"placeholders": {
|
||||
"date": { "type": "String" }
|
||||
}
|
||||
},
|
||||
"markAsEaten": "",
|
||||
"plannedMealLabel": ""
|
||||
}
|
||||
|
||||
@@ -123,5 +123,13 @@
|
||||
},
|
||||
"servingsLabel": "Portions",
|
||||
"addToDiary": "Ajouter au journal",
|
||||
"scanDishPhoto": "Scanner une photo"
|
||||
"scanDishPhoto": "Scanner une photo",
|
||||
"planningForDate": "",
|
||||
"@planningForDate": {
|
||||
"placeholders": {
|
||||
"date": { "type": "String" }
|
||||
}
|
||||
},
|
||||
"markAsEaten": "",
|
||||
"plannedMealLabel": ""
|
||||
}
|
||||
|
||||
@@ -123,5 +123,13 @@
|
||||
},
|
||||
"servingsLabel": "सर्विंग",
|
||||
"addToDiary": "डायरी में जोड़ें",
|
||||
"scanDishPhoto": "फ़ोटो स्कैन करें"
|
||||
"scanDishPhoto": "फ़ोटो स्कैन करें",
|
||||
"planningForDate": "",
|
||||
"@planningForDate": {
|
||||
"placeholders": {
|
||||
"date": { "type": "String" }
|
||||
}
|
||||
},
|
||||
"markAsEaten": "",
|
||||
"plannedMealLabel": ""
|
||||
}
|
||||
|
||||
@@ -123,5 +123,13 @@
|
||||
},
|
||||
"servingsLabel": "Porzioni",
|
||||
"addToDiary": "Aggiungi al diario",
|
||||
"scanDishPhoto": "Scansiona foto"
|
||||
"scanDishPhoto": "Scansiona foto",
|
||||
"planningForDate": "",
|
||||
"@planningForDate": {
|
||||
"placeholders": {
|
||||
"date": { "type": "String" }
|
||||
}
|
||||
},
|
||||
"markAsEaten": "",
|
||||
"plannedMealLabel": ""
|
||||
}
|
||||
|
||||
@@ -123,5 +123,13 @@
|
||||
},
|
||||
"servingsLabel": "人前",
|
||||
"addToDiary": "日記に追加",
|
||||
"scanDishPhoto": "写真をスキャン"
|
||||
"scanDishPhoto": "写真をスキャン",
|
||||
"planningForDate": "",
|
||||
"@planningForDate": {
|
||||
"placeholders": {
|
||||
"date": { "type": "String" }
|
||||
}
|
||||
},
|
||||
"markAsEaten": "",
|
||||
"plannedMealLabel": ""
|
||||
}
|
||||
|
||||
@@ -123,5 +123,13 @@
|
||||
},
|
||||
"servingsLabel": "인분",
|
||||
"addToDiary": "일기에 추가",
|
||||
"scanDishPhoto": "사진 스캔"
|
||||
"scanDishPhoto": "사진 스캔",
|
||||
"planningForDate": "",
|
||||
"@planningForDate": {
|
||||
"placeholders": {
|
||||
"date": { "type": "String" }
|
||||
}
|
||||
},
|
||||
"markAsEaten": "",
|
||||
"plannedMealLabel": ""
|
||||
}
|
||||
|
||||
@@ -789,6 +789,24 @@ abstract class AppLocalizations {
|
||||
/// In en, this message translates to:
|
||||
/// **'Scan photo'**
|
||||
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
|
||||
|
||||
@@ -348,4 +348,15 @@ class AppLocalizationsAr extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get scanDishPhoto => 'مسح الصورة';
|
||||
|
||||
@override
|
||||
String planningForDate(String date) {
|
||||
return '';
|
||||
}
|
||||
|
||||
@override
|
||||
String get markAsEaten => '';
|
||||
|
||||
@override
|
||||
String get plannedMealLabel => '';
|
||||
}
|
||||
|
||||
@@ -350,4 +350,15 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get scanDishPhoto => 'Foto scannen';
|
||||
|
||||
@override
|
||||
String planningForDate(String date) {
|
||||
return '';
|
||||
}
|
||||
|
||||
@override
|
||||
String get markAsEaten => '';
|
||||
|
||||
@override
|
||||
String get plannedMealLabel => '';
|
||||
}
|
||||
|
||||
@@ -348,4 +348,15 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
|
||||
@override
|
||||
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';
|
||||
}
|
||||
|
||||
@@ -350,4 +350,15 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get scanDishPhoto => 'Escanear foto';
|
||||
|
||||
@override
|
||||
String planningForDate(String date) {
|
||||
return '';
|
||||
}
|
||||
|
||||
@override
|
||||
String get markAsEaten => '';
|
||||
|
||||
@override
|
||||
String get plannedMealLabel => '';
|
||||
}
|
||||
|
||||
@@ -351,4 +351,15 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get scanDishPhoto => 'Scanner une photo';
|
||||
|
||||
@override
|
||||
String planningForDate(String date) {
|
||||
return '';
|
||||
}
|
||||
|
||||
@override
|
||||
String get markAsEaten => '';
|
||||
|
||||
@override
|
||||
String get plannedMealLabel => '';
|
||||
}
|
||||
|
||||
@@ -349,4 +349,15 @@ class AppLocalizationsHi extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get scanDishPhoto => 'फ़ोटो स्कैन करें';
|
||||
|
||||
@override
|
||||
String planningForDate(String date) {
|
||||
return '';
|
||||
}
|
||||
|
||||
@override
|
||||
String get markAsEaten => '';
|
||||
|
||||
@override
|
||||
String get plannedMealLabel => '';
|
||||
}
|
||||
|
||||
@@ -350,4 +350,15 @@ class AppLocalizationsIt extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get scanDishPhoto => 'Scansiona foto';
|
||||
|
||||
@override
|
||||
String planningForDate(String date) {
|
||||
return '';
|
||||
}
|
||||
|
||||
@override
|
||||
String get markAsEaten => '';
|
||||
|
||||
@override
|
||||
String get plannedMealLabel => '';
|
||||
}
|
||||
|
||||
@@ -347,4 +347,15 @@ class AppLocalizationsJa extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get scanDishPhoto => '写真をスキャン';
|
||||
|
||||
@override
|
||||
String planningForDate(String date) {
|
||||
return '';
|
||||
}
|
||||
|
||||
@override
|
||||
String get markAsEaten => '';
|
||||
|
||||
@override
|
||||
String get plannedMealLabel => '';
|
||||
}
|
||||
|
||||
@@ -347,4 +347,15 @@ class AppLocalizationsKo extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get scanDishPhoto => '사진 스캔';
|
||||
|
||||
@override
|
||||
String planningForDate(String date) {
|
||||
return '';
|
||||
}
|
||||
|
||||
@override
|
||||
String get markAsEaten => '';
|
||||
|
||||
@override
|
||||
String get plannedMealLabel => '';
|
||||
}
|
||||
|
||||
@@ -350,4 +350,15 @@ class AppLocalizationsPt extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get scanDishPhoto => 'Escanear foto';
|
||||
|
||||
@override
|
||||
String planningForDate(String date) {
|
||||
return '';
|
||||
}
|
||||
|
||||
@override
|
||||
String get markAsEaten => '';
|
||||
|
||||
@override
|
||||
String get plannedMealLabel => '';
|
||||
}
|
||||
|
||||
@@ -348,4 +348,15 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get scanDishPhoto => 'Сканировать фото';
|
||||
|
||||
@override
|
||||
String planningForDate(String date) {
|
||||
return 'Планирование на $date';
|
||||
}
|
||||
|
||||
@override
|
||||
String get markAsEaten => 'Отметить как съеденное';
|
||||
|
||||
@override
|
||||
String get plannedMealLabel => 'Запланировано';
|
||||
}
|
||||
|
||||
@@ -347,4 +347,15 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||
|
||||
@override
|
||||
String get scanDishPhoto => '扫描照片';
|
||||
|
||||
@override
|
||||
String planningForDate(String date) {
|
||||
return '';
|
||||
}
|
||||
|
||||
@override
|
||||
String get markAsEaten => '';
|
||||
|
||||
@override
|
||||
String get plannedMealLabel => '';
|
||||
}
|
||||
|
||||
@@ -123,5 +123,13 @@
|
||||
},
|
||||
"servingsLabel": "Porções",
|
||||
"addToDiary": "Adicionar ao diário",
|
||||
"scanDishPhoto": "Escanear foto"
|
||||
"scanDishPhoto": "Escanear foto",
|
||||
"planningForDate": "",
|
||||
"@planningForDate": {
|
||||
"placeholders": {
|
||||
"date": { "type": "String" }
|
||||
}
|
||||
},
|
||||
"markAsEaten": "",
|
||||
"plannedMealLabel": ""
|
||||
}
|
||||
|
||||
@@ -121,5 +121,13 @@
|
||||
},
|
||||
"servingsLabel": "Порций",
|
||||
"addToDiary": "Добавить в дневник",
|
||||
"scanDishPhoto": "Сканировать фото"
|
||||
"scanDishPhoto": "Сканировать фото",
|
||||
"planningForDate": "Планирование на {date}",
|
||||
"@planningForDate": {
|
||||
"placeholders": {
|
||||
"date": { "type": "String" }
|
||||
}
|
||||
},
|
||||
"markAsEaten": "Отметить как съеденное",
|
||||
"plannedMealLabel": "Запланировано"
|
||||
}
|
||||
|
||||
@@ -123,5 +123,13 @@
|
||||
},
|
||||
"servingsLabel": "份数",
|
||||
"addToDiary": "添加到日记",
|
||||
"scanDishPhoto": "扫描照片"
|
||||
"scanDishPhoto": "扫描照片",
|
||||
"planningForDate": "",
|
||||
"@planningForDate": {
|
||||
"placeholders": {
|
||||
"date": { "type": "String" }
|
||||
}
|
||||
},
|
||||
"markAsEaten": "",
|
||||
"plannedMealLabel": ""
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user