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:
dbastrikin
2026-03-22 23:25:46 +02:00
parent edf587e798
commit 9a6b7800a3
30 changed files with 106 additions and 167 deletions

View File

@@ -3,6 +3,7 @@ import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:food_ai/l10n/app_localizations.dart';
import 'package:intl/intl.dart';
import '../../shared/constants/date_limits.dart';
import '../../shared/models/meal_type.dart';
import '../profile/profile_provider.dart';
import 'menu_provider.dart';
@@ -279,8 +280,8 @@ class _DateStripSelector extends StatefulWidget {
class _DateStripSelectorState extends State<_DateStripSelector> {
late final ScrollController _scrollController;
// Show 30 upcoming days (today excluded, starts tomorrow).
static const _futureDays = 30;
// Show upcoming days up to the planning horizon (today excluded, starts tomorrow).
static const _futureDays = kPlanningHorizonDays;
static const _itemWidth = 64.0;
DateTime get _tomorrow =>
@@ -407,12 +408,22 @@ class _CalendarRangePickerState extends State<_CalendarRangePicker> {
}
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(() {
_displayMonth =
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) {
final dayOnly = DateTime(date.year, date.month, date.day);
final start =
@@ -510,11 +521,13 @@ class _CalendarRangePickerState extends State<_CalendarRangePicker> {
final isStart = _isRangeStart(date);
final isEnd = _isRangeEnd(date);
final isPast = _isPast(date);
final isBeyond = _isBeyondHorizon(date);
final isDisabled = isPast || isBeyond;
Color bgColor = Colors.transparent;
Color textColor = theme.colorScheme.onSurface;
if (isPast) {
if (isDisabled) {
// ignore: deprecated_member_use
textColor = theme.colorScheme.onSurface.withOpacity(0.3);
} else if (isStart || isEnd) {
@@ -526,7 +539,7 @@ class _CalendarRangePickerState extends State<_CalendarRangePicker> {
}
return GestureDetector(
onTap: isPast ? null : () => widget.onDayTapped(date),
onTap: isDisabled ? null : () => widget.onDayTapped(date),
child: Container(
decoration: BoxDecoration(
color: bgColor,