fix: unify date limits, fix ISO week calculation, refactor home screen plan button
- Fix _isoWeek: correct Sunday shift (-3 instead of +4), use floor+1 formula, match jan1 timezone to input — planned meals now appear correctly for UTC+ users - Add kPlanningHorizonDays=28 / kMenuPastWeeks=8 constants; apply to home date strip, plan picker (strip + calendar), and menu screen prev/next navigation - Menu screen week nav: disable arrows at min/max limits using compareTo - Home screen: replace _GenerateActionCard/_WeekPlannedChip conditional with always-visible _FutureDayPlanButton(dateString); show _DayPlannedChip only when the specific day has planned meals; remove standalone _PlanMenuButton - _FutureDayPlanButton uses selected date as defaultStart instead of lastPlanned+1 - Rename weekPlannedLabel -> dayPlannedLabel across all 12 locales Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -12,6 +12,7 @@ import '../../core/storage/local_preferences_provider.dart';
|
|||||||
import '../../core/theme/app_colors.dart';
|
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/constants/date_limits.dart';
|
||||||
import '../../shared/models/meal_type.dart';
|
import '../../shared/models/meal_type.dart';
|
||||||
import '../../shared/models/menu.dart';
|
import '../../shared/models/menu.dart';
|
||||||
import '../diary/food_search_sheet.dart';
|
import '../diary/food_search_sheet.dart';
|
||||||
@@ -128,8 +129,6 @@ class HomeScreen extends ConsumerWidget {
|
|||||||
],
|
],
|
||||||
const SizedBox(height: 16),
|
const SizedBox(height: 16),
|
||||||
_QuickActionsRow(),
|
_QuickActionsRow(),
|
||||||
const SizedBox(height: 8),
|
|
||||||
_PlanMenuButton(),
|
|
||||||
if (!isFutureDate && recommendations.isNotEmpty) ...[
|
if (!isFutureDate && recommendations.isNotEmpty) ...[
|
||||||
const SizedBox(height: 20),
|
const SizedBox(height: 20),
|
||||||
_SectionTitle(l10n.recommendCook),
|
_SectionTitle(l10n.recommendCook),
|
||||||
@@ -191,10 +190,10 @@ class _DateSelector extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _DateSelectorState extends State<_DateSelector> {
|
class _DateSelectorState extends State<_DateSelector> {
|
||||||
// Strip covers 7 future days + today + 364 past days = 372 items total.
|
// Strip covers kPlanningHorizonDays future days + today + 364 past days.
|
||||||
// With reverse: true, index 0 is rendered at the RIGHT edge (newest).
|
// With reverse: true, index 0 is rendered at the RIGHT edge (newest).
|
||||||
// index 0 = today + 7, index 7 = today, index 371 = today - 364.
|
// index 0 = today + kPlanningHorizonDays, index kPlanningHorizonDays = today.
|
||||||
static const _futureDays = 7;
|
static const _futureDays = kPlanningHorizonDays;
|
||||||
static const _pastDays = 364;
|
static const _pastDays = 364;
|
||||||
static const _totalDays = _futureDays + 1 + _pastDays; // 372
|
static const _totalDays = _futureDays + 1 + _pastDays; // 372
|
||||||
static const _pillWidth = 48.0;
|
static const _pillWidth = 48.0;
|
||||||
@@ -1156,125 +1155,26 @@ class _FutureDayHeader extends ConsumerWidget {
|
|||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
final l10n = AppLocalizations.of(context)!;
|
final l10n = AppLocalizations.of(context)!;
|
||||||
final date = DateTime.parse(dateString);
|
final plannedMeals = ref.watch(plannedMealsProvider(dateString));
|
||||||
final weekString = isoWeekString(date);
|
|
||||||
final menuState = ref.watch(menuProvider(weekString));
|
|
||||||
|
|
||||||
return Column(
|
return Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||||
children: [
|
children: [
|
||||||
_PlanningBanner(dateString: dateString),
|
_PlanningBanner(dateString: dateString),
|
||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
menuState.when(
|
_FutureDayPlanButton(dateString: dateString),
|
||||||
loading: () => _GenerateLoadingCard(l10n: l10n),
|
if (plannedMeals.isNotEmpty) ...[
|
||||||
error: (_, __) => _GenerateActionCard(
|
const SizedBox(height: 8),
|
||||||
l10n: l10n,
|
_DayPlannedChip(l10n: l10n),
|
||||||
onGenerate: () =>
|
],
|
||||||
ref.read(menuProvider(weekString).notifier).generate(),
|
|
||||||
),
|
|
||||||
data: (plan) => plan == null
|
|
||||||
? _GenerateActionCard(
|
|
||||||
l10n: l10n,
|
|
||||||
onGenerate: () =>
|
|
||||||
ref.read(menuProvider(weekString).notifier).generate(),
|
|
||||||
)
|
|
||||||
: _WeekPlannedChip(l10n: l10n),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class _GenerateActionCard extends StatelessWidget {
|
class _DayPlannedChip extends StatelessWidget {
|
||||||
final AppLocalizations l10n;
|
final AppLocalizations l10n;
|
||||||
final VoidCallback onGenerate;
|
const _DayPlannedChip({required this.l10n});
|
||||||
const _GenerateActionCard({required this.l10n, required this.onGenerate});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final theme = Theme.of(context);
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.all(16),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: theme.colorScheme.surfaceContainerLow,
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
),
|
|
||||||
child: Column(
|
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
|
||||||
children: [
|
|
||||||
Row(
|
|
||||||
children: [
|
|
||||||
Icon(Icons.auto_awesome,
|
|
||||||
color: theme.colorScheme.primary, size: 20),
|
|
||||||
const SizedBox(width: 8),
|
|
||||||
Expanded(
|
|
||||||
child: Text(
|
|
||||||
l10n.generateWeekLabel,
|
|
||||||
style: theme.textTheme.titleSmall?.copyWith(
|
|
||||||
color: theme.colorScheme.onSurface,
|
|
||||||
fontWeight: FontWeight.w600,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
const SizedBox(height: 4),
|
|
||||||
Text(
|
|
||||||
l10n.generateWeekSubtitle,
|
|
||||||
style: theme.textTheme.bodySmall?.copyWith(
|
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(height: 12),
|
|
||||||
FilledButton(
|
|
||||||
onPressed: onGenerate,
|
|
||||||
child: Text(l10n.generateWeekLabel),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _GenerateLoadingCard extends StatelessWidget {
|
|
||||||
final AppLocalizations l10n;
|
|
||||||
const _GenerateLoadingCard({required this.l10n});
|
|
||||||
|
|
||||||
@override
|
|
||||||
Widget build(BuildContext context) {
|
|
||||||
final theme = Theme.of(context);
|
|
||||||
return Container(
|
|
||||||
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14),
|
|
||||||
decoration: BoxDecoration(
|
|
||||||
color: theme.colorScheme.surfaceContainerLow,
|
|
||||||
borderRadius: BorderRadius.circular(12),
|
|
||||||
),
|
|
||||||
child: Row(
|
|
||||||
children: [
|
|
||||||
SizedBox(
|
|
||||||
width: 18,
|
|
||||||
height: 18,
|
|
||||||
child: CircularProgressIndicator.adaptive(
|
|
||||||
strokeWidth: 2,
|
|
||||||
valueColor: AlwaysStoppedAnimation(theme.colorScheme.primary),
|
|
||||||
),
|
|
||||||
),
|
|
||||||
const SizedBox(width: 12),
|
|
||||||
Text(
|
|
||||||
l10n.generatingMenu,
|
|
||||||
style: theme.textTheme.bodyMedium?.copyWith(
|
|
||||||
color: theme.colorScheme.onSurfaceVariant,
|
|
||||||
),
|
|
||||||
),
|
|
||||||
],
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class _WeekPlannedChip extends StatelessWidget {
|
|
||||||
final AppLocalizations l10n;
|
|
||||||
const _WeekPlannedChip({required this.l10n});
|
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -1292,7 +1192,7 @@ class _WeekPlannedChip extends StatelessWidget {
|
|||||||
color: theme.colorScheme.onSecondaryContainer, size: 18),
|
color: theme.colorScheme.onSecondaryContainer, size: 18),
|
||||||
const SizedBox(width: 8),
|
const SizedBox(width: 8),
|
||||||
Text(
|
Text(
|
||||||
l10n.weekPlannedLabel,
|
l10n.dayPlannedLabel,
|
||||||
style: theme.textTheme.bodySmall?.copyWith(
|
style: theme.textTheme.bodySmall?.copyWith(
|
||||||
color: theme.colorScheme.onSecondaryContainer,
|
color: theme.colorScheme.onSecondaryContainer,
|
||||||
fontWeight: FontWeight.w600,
|
fontWeight: FontWeight.w600,
|
||||||
@@ -1647,8 +1547,9 @@ class _ActionButton extends StatelessWidget {
|
|||||||
|
|
||||||
// ── Plan menu button ──────────────────────────────────────────
|
// ── Plan menu button ──────────────────────────────────────────
|
||||||
|
|
||||||
class _PlanMenuButton extends ConsumerWidget {
|
class _FutureDayPlanButton extends ConsumerWidget {
|
||||||
const _PlanMenuButton();
|
final String dateString;
|
||||||
|
const _FutureDayPlanButton({required this.dateString});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context, WidgetRef ref) {
|
Widget build(BuildContext context, WidgetRef ref) {
|
||||||
@@ -1682,16 +1583,13 @@ class _PlanMenuButton extends ConsumerWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _openPlanSheet(BuildContext context, WidgetRef ref) {
|
void _openPlanSheet(BuildContext context, WidgetRef ref) {
|
||||||
|
final defaultStart = DateTime.parse(dateString);
|
||||||
showModalBottomSheet<void>(
|
showModalBottomSheet<void>(
|
||||||
context: context,
|
context: context,
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
useSafeArea: true,
|
useSafeArea: true,
|
||||||
builder: (_) => PlanMenuSheet(
|
builder: (_) => PlanMenuSheet(
|
||||||
onModeSelected: (mode) {
|
onModeSelected: (mode) {
|
||||||
final lastPlanned = ref.read(lastPlannedDateProvider);
|
|
||||||
final defaultStart = lastPlanned != null
|
|
||||||
? lastPlanned.add(const Duration(days: 1))
|
|
||||||
: DateTime.now().add(const Duration(days: 1));
|
|
||||||
showModalBottomSheet<void>(
|
showModalBottomSheet<void>(
|
||||||
context: context,
|
context: context,
|
||||||
isScrollControlled: true,
|
isScrollControlled: true,
|
||||||
|
|||||||
@@ -16,22 +16,27 @@ final menuServiceProvider = Provider<MenuService>((ref) {
|
|||||||
|
|
||||||
/// The ISO week string for the currently displayed week, e.g. "2026-W08".
|
/// The ISO week string for the currently displayed week, e.g. "2026-W08".
|
||||||
final currentWeekProvider = StateProvider<String>((ref) {
|
final currentWeekProvider = StateProvider<String>((ref) {
|
||||||
final now = DateTime.now().toUtc();
|
final now = DateTime.now();
|
||||||
final (y, w) = _isoWeek(now);
|
final (y, w) = _isoWeek(now);
|
||||||
return '$y-W${w.toString().padLeft(2, '0')}';
|
return '$y-W${w.toString().padLeft(2, '0')}';
|
||||||
});
|
});
|
||||||
|
|
||||||
(int year, int week) _isoWeek(DateTime dt) {
|
(int year, int week) _isoWeek(DateTime dt) {
|
||||||
// Shift to Thursday to get ISO week year.
|
// Shift to Thursday of the same ISO week.
|
||||||
final thu = dt.add(Duration(days: 4 - (dt.weekday == 7 ? 0 : dt.weekday)));
|
// Monday=1…Saturday=6 → add (4 - weekday) days; Sunday=7 → subtract 3 days.
|
||||||
final jan1 = DateTime.utc(thu.year, 1, 1);
|
final int shift = dt.weekday == 7 ? -3 : 4 - dt.weekday;
|
||||||
final week = ((thu.difference(jan1).inDays) / 7).ceil();
|
final thu = dt.add(Duration(days: shift));
|
||||||
|
// Use the same timezone as the input to avoid offset drift on the difference.
|
||||||
|
final jan1 = dt.isUtc
|
||||||
|
? DateTime.utc(thu.year, 1, 1)
|
||||||
|
: DateTime(thu.year, 1, 1);
|
||||||
|
final week = (thu.difference(jan1).inDays ~/ 7) + 1;
|
||||||
return (thu.year, week);
|
return (thu.year, week);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns the ISO 8601 week string for [date], e.g. "2026-W12".
|
/// Returns the ISO 8601 week string for [date], e.g. "2026-W12".
|
||||||
String isoWeekString(DateTime date) {
|
String isoWeekString(DateTime date) {
|
||||||
final (year, week) = _isoWeek(date.toUtc());
|
final (year, week) = _isoWeek(date);
|
||||||
return '$year-W${week.toString().padLeft(2, '0')}';
|
return '$year-W${week.toString().padLeft(2, '0')}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'package:flutter/material.dart';
|
|||||||
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
||||||
import 'package:go_router/go_router.dart';
|
import 'package:go_router/go_router.dart';
|
||||||
|
|
||||||
|
import '../../shared/constants/date_limits.dart';
|
||||||
import '../../shared/models/menu.dart';
|
import '../../shared/models/menu.dart';
|
||||||
import 'menu_provider.dart';
|
import 'menu_provider.dart';
|
||||||
|
|
||||||
@@ -95,31 +96,48 @@ class _WeekNavBar extends StatelessWidget implements PreferredSizeWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
(int, int) _isoWeekOf(DateTime dt) {
|
(int, int) _isoWeekOf(DateTime dt) {
|
||||||
final thu = dt.add(Duration(days: 4 - (dt.weekday == 7 ? 0 : dt.weekday)));
|
final int shift = dt.weekday == 7 ? -3 : 4 - dt.weekday;
|
||||||
final jan1 = DateTime.utc(thu.year, 1, 1);
|
final thu = dt.add(Duration(days: shift));
|
||||||
final w = ((thu.difference(jan1).inDays) / 7).ceil();
|
final jan1 = dt.isUtc
|
||||||
|
? DateTime.utc(thu.year, 1, 1)
|
||||||
|
: DateTime(thu.year, 1, 1);
|
||||||
|
final w = (thu.difference(jan1).inDays ~/ 7) + 1;
|
||||||
return (thu.year, w);
|
return (thu.year, w);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
String _currentWeekString(DateTime date) {
|
||||||
|
final (y, w) = _isoWeekOf(date);
|
||||||
|
return '$y-W${w.toString().padLeft(2, '0')}';
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final now = DateTime.now();
|
||||||
|
final maxWeek = _currentWeekString(
|
||||||
|
now.add(const Duration(days: kPlanningHorizonDays)));
|
||||||
|
final minWeek = _currentWeekString(
|
||||||
|
now.subtract(Duration(days: kMenuPastWeeks * 7)));
|
||||||
return Row(
|
return Row(
|
||||||
mainAxisAlignment: MainAxisAlignment.center,
|
mainAxisAlignment: MainAxisAlignment.center,
|
||||||
children: [
|
children: [
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.chevron_left),
|
icon: const Icon(Icons.chevron_left),
|
||||||
onPressed: () {
|
onPressed: week.compareTo(minWeek) <= 0
|
||||||
ref.read(currentWeekProvider.notifier).state =
|
? null
|
||||||
_offsetWeek(week, -1);
|
: () {
|
||||||
},
|
ref.read(currentWeekProvider.notifier).state =
|
||||||
|
_offsetWeek(week, -1);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
Text(_weekLabel(week), style: Theme.of(context).textTheme.bodyMedium),
|
Text(_weekLabel(week), style: Theme.of(context).textTheme.bodyMedium),
|
||||||
IconButton(
|
IconButton(
|
||||||
icon: const Icon(Icons.chevron_right),
|
icon: const Icon(Icons.chevron_right),
|
||||||
onPressed: () {
|
onPressed: week.compareTo(maxWeek) >= 0
|
||||||
ref.read(currentWeekProvider.notifier).state =
|
? null
|
||||||
_offsetWeek(week, 1);
|
: () {
|
||||||
},
|
ref.read(currentWeekProvider.notifier).state =
|
||||||
|
_offsetWeek(week, 1);
|
||||||
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|||||||
import 'package:food_ai/l10n/app_localizations.dart';
|
import 'package:food_ai/l10n/app_localizations.dart';
|
||||||
import 'package:intl/intl.dart';
|
import 'package:intl/intl.dart';
|
||||||
|
|
||||||
|
import '../../shared/constants/date_limits.dart';
|
||||||
import '../../shared/models/meal_type.dart';
|
import '../../shared/models/meal_type.dart';
|
||||||
import '../profile/profile_provider.dart';
|
import '../profile/profile_provider.dart';
|
||||||
import 'menu_provider.dart';
|
import 'menu_provider.dart';
|
||||||
@@ -279,8 +280,8 @@ class _DateStripSelector extends StatefulWidget {
|
|||||||
|
|
||||||
class _DateStripSelectorState extends State<_DateStripSelector> {
|
class _DateStripSelectorState extends State<_DateStripSelector> {
|
||||||
late final ScrollController _scrollController;
|
late final ScrollController _scrollController;
|
||||||
// Show 30 upcoming days (today excluded, starts tomorrow).
|
// Show upcoming days up to the planning horizon (today excluded, starts tomorrow).
|
||||||
static const _futureDays = 30;
|
static const _futureDays = kPlanningHorizonDays;
|
||||||
static const _itemWidth = 64.0;
|
static const _itemWidth = 64.0;
|
||||||
|
|
||||||
DateTime get _tomorrow =>
|
DateTime get _tomorrow =>
|
||||||
@@ -407,12 +408,22 @@ class _CalendarRangePickerState extends State<_CalendarRangePicker> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void _nextMonth() {
|
void _nextMonth() {
|
||||||
|
final horizon = DateTime.now().add(const Duration(days: kPlanningHorizonDays));
|
||||||
|
final limitMonth = DateTime(horizon.year, horizon.month);
|
||||||
|
if (!DateTime(_displayMonth.year, _displayMonth.month).isBefore(limitMonth)) return;
|
||||||
setState(() {
|
setState(() {
|
||||||
_displayMonth =
|
_displayMonth =
|
||||||
DateTime(_displayMonth.year, _displayMonth.month + 1);
|
DateTime(_displayMonth.year, _displayMonth.month + 1);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool _isBeyondHorizon(DateTime date) {
|
||||||
|
final today = DateTime.now();
|
||||||
|
final horizon = DateTime(today.year, today.month, today.day)
|
||||||
|
.add(const Duration(days: kPlanningHorizonDays));
|
||||||
|
return date.isAfter(horizon);
|
||||||
|
}
|
||||||
|
|
||||||
bool _isInRange(DateTime date) {
|
bool _isInRange(DateTime date) {
|
||||||
final dayOnly = DateTime(date.year, date.month, date.day);
|
final dayOnly = DateTime(date.year, date.month, date.day);
|
||||||
final start =
|
final start =
|
||||||
@@ -510,11 +521,13 @@ class _CalendarRangePickerState extends State<_CalendarRangePicker> {
|
|||||||
final isStart = _isRangeStart(date);
|
final isStart = _isRangeStart(date);
|
||||||
final isEnd = _isRangeEnd(date);
|
final isEnd = _isRangeEnd(date);
|
||||||
final isPast = _isPast(date);
|
final isPast = _isPast(date);
|
||||||
|
final isBeyond = _isBeyondHorizon(date);
|
||||||
|
final isDisabled = isPast || isBeyond;
|
||||||
|
|
||||||
Color bgColor = Colors.transparent;
|
Color bgColor = Colors.transparent;
|
||||||
Color textColor = theme.colorScheme.onSurface;
|
Color textColor = theme.colorScheme.onSurface;
|
||||||
|
|
||||||
if (isPast) {
|
if (isDisabled) {
|
||||||
// ignore: deprecated_member_use
|
// ignore: deprecated_member_use
|
||||||
textColor = theme.colorScheme.onSurface.withOpacity(0.3);
|
textColor = theme.colorScheme.onSurface.withOpacity(0.3);
|
||||||
} else if (isStart || isEnd) {
|
} else if (isStart || isEnd) {
|
||||||
@@ -526,7 +539,7 @@ class _CalendarRangePickerState extends State<_CalendarRangePicker> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
return GestureDetector(
|
return GestureDetector(
|
||||||
onTap: isPast ? null : () => widget.onDayTapped(date),
|
onTap: isDisabled ? null : () => widget.onDayTapped(date),
|
||||||
child: Container(
|
child: Container(
|
||||||
decoration: BoxDecoration(
|
decoration: BoxDecoration(
|
||||||
color: bgColor,
|
color: bgColor,
|
||||||
|
|||||||
@@ -135,7 +135,7 @@
|
|||||||
"generateWeekLabel": "تخطيط الأسبوع",
|
"generateWeekLabel": "تخطيط الأسبوع",
|
||||||
"generateWeekSubtitle": "سيقوم الذكاء الاصطناعي بإنشاء قائمة طعام تشمل الإفطار والغداء والعشاء لكامل الأسبوع",
|
"generateWeekSubtitle": "سيقوم الذكاء الاصطناعي بإنشاء قائمة طعام تشمل الإفطار والغداء والعشاء لكامل الأسبوع",
|
||||||
"generatingMenu": "جارٍ إنشاء القائمة...",
|
"generatingMenu": "جارٍ إنشاء القائمة...",
|
||||||
"weekPlannedLabel": "تم تخطيط الأسبوع",
|
"dayPlannedLabel": "تم تخطيط اليوم",
|
||||||
|
|
||||||
"planMenuButton": "تخطيط الوجبات",
|
"planMenuButton": "تخطيط الوجبات",
|
||||||
"planMenuTitle": "ماذا تريد تخطيطه؟",
|
"planMenuTitle": "ماذا تريد تخطيطه؟",
|
||||||
|
|||||||
@@ -135,7 +135,7 @@
|
|||||||
"generateWeekLabel": "Woche planen",
|
"generateWeekLabel": "Woche planen",
|
||||||
"generateWeekSubtitle": "KI erstellt einen Menüplan mit Frühstück, Mittagessen und Abendessen für die ganze Woche",
|
"generateWeekSubtitle": "KI erstellt einen Menüplan mit Frühstück, Mittagessen und Abendessen für die ganze Woche",
|
||||||
"generatingMenu": "Menü wird erstellt...",
|
"generatingMenu": "Menü wird erstellt...",
|
||||||
"weekPlannedLabel": "Woche geplant",
|
"dayPlannedLabel": "Tag geplant",
|
||||||
|
|
||||||
"planMenuButton": "Mahlzeiten planen",
|
"planMenuButton": "Mahlzeiten planen",
|
||||||
"planMenuTitle": "Was planen?",
|
"planMenuTitle": "Was planen?",
|
||||||
|
|||||||
@@ -133,7 +133,7 @@
|
|||||||
"generateWeekLabel": "Plan the week",
|
"generateWeekLabel": "Plan the week",
|
||||||
"generateWeekSubtitle": "AI will create a menu with breakfast, lunch and dinner for the whole week",
|
"generateWeekSubtitle": "AI will create a menu with breakfast, lunch and dinner for the whole week",
|
||||||
"generatingMenu": "Generating menu...",
|
"generatingMenu": "Generating menu...",
|
||||||
"weekPlannedLabel": "Week planned",
|
"dayPlannedLabel": "Day planned",
|
||||||
|
|
||||||
"planMenuButton": "Plan meals",
|
"planMenuButton": "Plan meals",
|
||||||
"planMenuTitle": "What to plan?",
|
"planMenuTitle": "What to plan?",
|
||||||
|
|||||||
@@ -135,7 +135,7 @@
|
|||||||
"generateWeekLabel": "Planificar la semana",
|
"generateWeekLabel": "Planificar la semana",
|
||||||
"generateWeekSubtitle": "La IA creará un menú con desayuno, comida y cena para toda la semana",
|
"generateWeekSubtitle": "La IA creará un menú con desayuno, comida y cena para toda la semana",
|
||||||
"generatingMenu": "Generando menú...",
|
"generatingMenu": "Generando menú...",
|
||||||
"weekPlannedLabel": "Semana planificada",
|
"dayPlannedLabel": "Día planificado",
|
||||||
|
|
||||||
"planMenuButton": "Planificar comidas",
|
"planMenuButton": "Planificar comidas",
|
||||||
"planMenuTitle": "¿Qué planificar?",
|
"planMenuTitle": "¿Qué planificar?",
|
||||||
|
|||||||
@@ -135,7 +135,7 @@
|
|||||||
"generateWeekLabel": "Planifier la semaine",
|
"generateWeekLabel": "Planifier la semaine",
|
||||||
"generateWeekSubtitle": "L'IA créera un menu avec petit-déjeuner, déjeuner et dîner pour toute la semaine",
|
"generateWeekSubtitle": "L'IA créera un menu avec petit-déjeuner, déjeuner et dîner pour toute la semaine",
|
||||||
"generatingMenu": "Génération du menu...",
|
"generatingMenu": "Génération du menu...",
|
||||||
"weekPlannedLabel": "Semaine planifiée",
|
"dayPlannedLabel": "Jour planifié",
|
||||||
|
|
||||||
"planMenuButton": "Planifier les repas",
|
"planMenuButton": "Planifier les repas",
|
||||||
"planMenuTitle": "Que planifier ?",
|
"planMenuTitle": "Que planifier ?",
|
||||||
|
|||||||
@@ -135,7 +135,7 @@
|
|||||||
"generateWeekLabel": "सप्ताह की योजना बनाएं",
|
"generateWeekLabel": "सप्ताह की योजना बनाएं",
|
||||||
"generateWeekSubtitle": "AI पूरे सप्ताह के लिए नाश्ता, दोपहर का खाना और रात के खाने के साथ मेनू बनाएगा",
|
"generateWeekSubtitle": "AI पूरे सप्ताह के लिए नाश्ता, दोपहर का खाना और रात के खाने के साथ मेनू बनाएगा",
|
||||||
"generatingMenu": "मेनू बना रहे हैं...",
|
"generatingMenu": "मेनू बना रहे हैं...",
|
||||||
"weekPlannedLabel": "सप्ताह की योजना बनाई गई",
|
"dayPlannedLabel": "दिन की योजना बनाई गई",
|
||||||
|
|
||||||
"planMenuButton": "भोजन की योजना बनाएं",
|
"planMenuButton": "भोजन की योजना बनाएं",
|
||||||
"planMenuTitle": "क्या योजना बनानी है?",
|
"planMenuTitle": "क्या योजना बनानी है?",
|
||||||
|
|||||||
@@ -135,7 +135,7 @@
|
|||||||
"generateWeekLabel": "Pianifica la settimana",
|
"generateWeekLabel": "Pianifica la settimana",
|
||||||
"generateWeekSubtitle": "L'AI creerà un menu con colazione, pranzo e cena per tutta la settimana",
|
"generateWeekSubtitle": "L'AI creerà un menu con colazione, pranzo e cena per tutta la settimana",
|
||||||
"generatingMenu": "Generazione menu...",
|
"generatingMenu": "Generazione menu...",
|
||||||
"weekPlannedLabel": "Settimana pianificata",
|
"dayPlannedLabel": "Giorno pianificato",
|
||||||
|
|
||||||
"planMenuButton": "Pianifica i pasti",
|
"planMenuButton": "Pianifica i pasti",
|
||||||
"planMenuTitle": "Cosa pianificare?",
|
"planMenuTitle": "Cosa pianificare?",
|
||||||
|
|||||||
@@ -135,7 +135,7 @@
|
|||||||
"generateWeekLabel": "週を計画する",
|
"generateWeekLabel": "週を計画する",
|
||||||
"generateWeekSubtitle": "AIが一週間の朝食・昼食・夕食のメニューを作成します",
|
"generateWeekSubtitle": "AIが一週間の朝食・昼食・夕食のメニューを作成します",
|
||||||
"generatingMenu": "メニューを生成中...",
|
"generatingMenu": "メニューを生成中...",
|
||||||
"weekPlannedLabel": "週の計画済み",
|
"dayPlannedLabel": "日の計画済み",
|
||||||
|
|
||||||
"planMenuButton": "食事を計画する",
|
"planMenuButton": "食事を計画する",
|
||||||
"planMenuTitle": "何を計画する?",
|
"planMenuTitle": "何を計画する?",
|
||||||
|
|||||||
@@ -135,7 +135,7 @@
|
|||||||
"generateWeekLabel": "주간 계획하기",
|
"generateWeekLabel": "주간 계획하기",
|
||||||
"generateWeekSubtitle": "AI가 한 주 동안 아침, 점심, 저녁 식사 메뉴를 만들어 드립니다",
|
"generateWeekSubtitle": "AI가 한 주 동안 아침, 점심, 저녁 식사 메뉴를 만들어 드립니다",
|
||||||
"generatingMenu": "메뉴 생성 중...",
|
"generatingMenu": "메뉴 생성 중...",
|
||||||
"weekPlannedLabel": "주간 계획 완료",
|
"dayPlannedLabel": "일일 계획 완료",
|
||||||
|
|
||||||
"planMenuButton": "식사 계획하기",
|
"planMenuButton": "식사 계획하기",
|
||||||
"planMenuTitle": "무엇을 계획하시겠어요?",
|
"planMenuTitle": "무엇을 계획하시겠어요?",
|
||||||
|
|||||||
@@ -826,11 +826,11 @@ abstract class AppLocalizations {
|
|||||||
/// **'Generating menu...'**
|
/// **'Generating menu...'**
|
||||||
String get generatingMenu;
|
String get generatingMenu;
|
||||||
|
|
||||||
/// No description provided for @weekPlannedLabel.
|
/// No description provided for @dayPlannedLabel.
|
||||||
///
|
///
|
||||||
/// In en, this message translates to:
|
/// In en, this message translates to:
|
||||||
/// **'Week planned'**
|
/// **'Day planned'**
|
||||||
String get weekPlannedLabel;
|
String get dayPlannedLabel;
|
||||||
|
|
||||||
/// No description provided for @planMenuButton.
|
/// No description provided for @planMenuButton.
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -371,7 +371,7 @@ class AppLocalizationsAr extends AppLocalizations {
|
|||||||
String get generatingMenu => 'جارٍ إنشاء القائمة...';
|
String get generatingMenu => 'جارٍ إنشاء القائمة...';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get weekPlannedLabel => 'تم تخطيط الأسبوع';
|
String get dayPlannedLabel => 'تم تخطيط اليوم';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get planMenuButton => 'تخطيط الوجبات';
|
String get planMenuButton => 'تخطيط الوجبات';
|
||||||
|
|||||||
@@ -373,7 +373,7 @@ class AppLocalizationsDe extends AppLocalizations {
|
|||||||
String get generatingMenu => 'Menü wird erstellt...';
|
String get generatingMenu => 'Menü wird erstellt...';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get weekPlannedLabel => 'Woche geplant';
|
String get dayPlannedLabel => 'Tag geplant';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get planMenuButton => 'Mahlzeiten planen';
|
String get planMenuButton => 'Mahlzeiten planen';
|
||||||
|
|||||||
@@ -371,7 +371,7 @@ class AppLocalizationsEn extends AppLocalizations {
|
|||||||
String get generatingMenu => 'Generating menu...';
|
String get generatingMenu => 'Generating menu...';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get weekPlannedLabel => 'Week planned';
|
String get dayPlannedLabel => 'Day planned';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get planMenuButton => 'Plan meals';
|
String get planMenuButton => 'Plan meals';
|
||||||
|
|||||||
@@ -373,7 +373,7 @@ class AppLocalizationsEs extends AppLocalizations {
|
|||||||
String get generatingMenu => 'Generando menú...';
|
String get generatingMenu => 'Generando menú...';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get weekPlannedLabel => 'Semana planificada';
|
String get dayPlannedLabel => 'Día planificado';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get planMenuButton => 'Planificar comidas';
|
String get planMenuButton => 'Planificar comidas';
|
||||||
|
|||||||
@@ -374,7 +374,7 @@ class AppLocalizationsFr extends AppLocalizations {
|
|||||||
String get generatingMenu => 'Génération du menu...';
|
String get generatingMenu => 'Génération du menu...';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get weekPlannedLabel => 'Semaine planifiée';
|
String get dayPlannedLabel => 'Jour planifié';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get planMenuButton => 'Planifier les repas';
|
String get planMenuButton => 'Planifier les repas';
|
||||||
|
|||||||
@@ -372,7 +372,7 @@ class AppLocalizationsHi extends AppLocalizations {
|
|||||||
String get generatingMenu => 'मेनू बना रहे हैं...';
|
String get generatingMenu => 'मेनू बना रहे हैं...';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get weekPlannedLabel => 'सप्ताह की योजना बनाई गई';
|
String get dayPlannedLabel => 'दिन की योजना बनाई गई';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get planMenuButton => 'भोजन की योजना बनाएं';
|
String get planMenuButton => 'भोजन की योजना बनाएं';
|
||||||
|
|||||||
@@ -373,7 +373,7 @@ class AppLocalizationsIt extends AppLocalizations {
|
|||||||
String get generatingMenu => 'Generazione menu...';
|
String get generatingMenu => 'Generazione menu...';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get weekPlannedLabel => 'Settimana pianificata';
|
String get dayPlannedLabel => 'Giorno pianificato';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get planMenuButton => 'Pianifica i pasti';
|
String get planMenuButton => 'Pianifica i pasti';
|
||||||
|
|||||||
@@ -369,7 +369,7 @@ class AppLocalizationsJa extends AppLocalizations {
|
|||||||
String get generatingMenu => 'メニューを生成中...';
|
String get generatingMenu => 'メニューを生成中...';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get weekPlannedLabel => '週の計画済み';
|
String get dayPlannedLabel => '日の計画済み';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get planMenuButton => '食事を計画する';
|
String get planMenuButton => '食事を計画する';
|
||||||
|
|||||||
@@ -369,7 +369,7 @@ class AppLocalizationsKo extends AppLocalizations {
|
|||||||
String get generatingMenu => '메뉴 생성 중...';
|
String get generatingMenu => '메뉴 생성 중...';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get weekPlannedLabel => '주간 계획 완료';
|
String get dayPlannedLabel => '일일 계획 완료';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get planMenuButton => '식사 계획하기';
|
String get planMenuButton => '식사 계획하기';
|
||||||
|
|||||||
@@ -373,7 +373,7 @@ class AppLocalizationsPt extends AppLocalizations {
|
|||||||
String get generatingMenu => 'Gerando menu...';
|
String get generatingMenu => 'Gerando menu...';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get weekPlannedLabel => 'Semana planejada';
|
String get dayPlannedLabel => 'Dia planejado';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get planMenuButton => 'Planejar refeições';
|
String get planMenuButton => 'Planejar refeições';
|
||||||
|
|||||||
@@ -371,7 +371,7 @@ class AppLocalizationsRu extends AppLocalizations {
|
|||||||
String get generatingMenu => 'Генерируем меню...';
|
String get generatingMenu => 'Генерируем меню...';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get weekPlannedLabel => 'Неделя запланирована';
|
String get dayPlannedLabel => 'День запланирован';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get planMenuButton => 'Спланировать меню';
|
String get planMenuButton => 'Спланировать меню';
|
||||||
|
|||||||
@@ -369,7 +369,7 @@ class AppLocalizationsZh extends AppLocalizations {
|
|||||||
String get generatingMenu => '正在生成菜单...';
|
String get generatingMenu => '正在生成菜单...';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get weekPlannedLabel => '本周已规划';
|
String get dayPlannedLabel => '今日已规划';
|
||||||
|
|
||||||
@override
|
@override
|
||||||
String get planMenuButton => '规划餐食';
|
String get planMenuButton => '规划餐食';
|
||||||
|
|||||||
@@ -135,7 +135,7 @@
|
|||||||
"generateWeekLabel": "Planejar a semana",
|
"generateWeekLabel": "Planejar a semana",
|
||||||
"generateWeekSubtitle": "A IA criará um menu com café da manhã, almoço e jantar para a semana inteira",
|
"generateWeekSubtitle": "A IA criará um menu com café da manhã, almoço e jantar para a semana inteira",
|
||||||
"generatingMenu": "Gerando menu...",
|
"generatingMenu": "Gerando menu...",
|
||||||
"weekPlannedLabel": "Semana planejada",
|
"dayPlannedLabel": "Dia planejado",
|
||||||
|
|
||||||
"planMenuButton": "Planejar refeições",
|
"planMenuButton": "Planejar refeições",
|
||||||
"planMenuTitle": "O que planejar?",
|
"planMenuTitle": "O que planejar?",
|
||||||
|
|||||||
@@ -133,7 +133,7 @@
|
|||||||
"generateWeekLabel": "Запланировать неделю",
|
"generateWeekLabel": "Запланировать неделю",
|
||||||
"generateWeekSubtitle": "AI составит меню с завтраком, обедом и ужином на всю неделю",
|
"generateWeekSubtitle": "AI составит меню с завтраком, обедом и ужином на всю неделю",
|
||||||
"generatingMenu": "Генерируем меню...",
|
"generatingMenu": "Генерируем меню...",
|
||||||
"weekPlannedLabel": "Неделя запланирована",
|
"dayPlannedLabel": "День запланирован",
|
||||||
|
|
||||||
"planMenuButton": "Спланировать меню",
|
"planMenuButton": "Спланировать меню",
|
||||||
"planMenuTitle": "Что запланировать?",
|
"planMenuTitle": "Что запланировать?",
|
||||||
|
|||||||
@@ -135,7 +135,7 @@
|
|||||||
"generateWeekLabel": "规划本周",
|
"generateWeekLabel": "规划本周",
|
||||||
"generateWeekSubtitle": "AI将为整周创建含早餐、午餐和晚餐的菜单",
|
"generateWeekSubtitle": "AI将为整周创建含早餐、午餐和晚餐的菜单",
|
||||||
"generatingMenu": "正在生成菜单...",
|
"generatingMenu": "正在生成菜单...",
|
||||||
"weekPlannedLabel": "本周已规划",
|
"dayPlannedLabel": "今日已规划",
|
||||||
|
|
||||||
"planMenuButton": "规划餐食",
|
"planMenuButton": "规划餐食",
|
||||||
"planMenuTitle": "规划什么?",
|
"planMenuTitle": "规划什么?",
|
||||||
|
|||||||
5
client/lib/shared/constants/date_limits.dart
Normal file
5
client/lib/shared/constants/date_limits.dart
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
/// How many days into the future the app allows planning and browsing.
|
||||||
|
const int kPlanningHorizonDays = 28;
|
||||||
|
|
||||||
|
/// How many weeks into the past the menu screen allows backward navigation.
|
||||||
|
const int kMenuPastWeeks = 8;
|
||||||
Reference in New Issue
Block a user