diet-guard/app/lib/widgets/slot_selector_row.dart
Krzysztof kuhy Rudnicki c43e37b09d Compact LogMealScreen so it fits without scrolling on-screen keyboard
Merge the slot-status bar, slot-selector chips, and "Logging for
HH:00" caption into one selectable, status-colored SlotSelectorRow.
Add an opt-in compact mode to MacroInputRow (single row, abbreviated
labels), AutocompleteSuggestionList (top-3 + "N more" bottom sheet),
and PhotoAttachField (icon-only + badge thumbnail), used only by
LogMealScreen so MealBuilderScreen/EditEntryScreen keep their default
rendering. Verified on-device (BL-9000) that all fields stay visible
with the keyboard open.

Also fixes an unrelated time-bomb in history_screen_test.dart's date
range picker test, which hardcoded an expected "2026-06-01" label
assuming "today" was in June; the picker's displayed month and
selectable range depend on the real current date, so the assertion
now computes its expectation from DateTime.now() instead.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018UorgLvWJ4huH55tmXoUAZ
2026-07-04 05:19:23 +02:00

75 lines
2.5 KiB
Dart

/// A single row that both shows today's slot status (logged/due/upcoming)
/// and lets the user pick which slot they're logging for, replacing what
/// used to be three separate stacked elements.
library;
import 'package:diet_guard_app/models/slot.dart';
import 'package:flutter/material.dart';
/// One row of [ChoiceChip]s: one per today's slot hour plus a fixed
/// "Snack" chip. Each hour chip is simultaneously selectable (tap to log
/// for that slot) and status-colored (green+check = logged, red = due,
/// grey = upcoming), so no separate status bar or caption text is needed.
class SlotSelectorRow extends StatelessWidget {
/// Creates a [SlotSelectorRow].
const SlotSelectorRow({
required this.now,
required this.loggedSlots,
required this.selectedSlot,
required this.onSlotSelected,
super.key,
});
/// Reference time used to decide which slots are due.
final DateTime now;
/// Slot hours already satisfied by today's log.
final Set<int> loggedSlots;
/// The slot currently chosen to log for, or null for "Snack".
final int? selectedSlot;
/// Called with the tapped slot's hour, or null for the "Snack" chip.
final ValueChanged<int?> onSlotSelected;
@override
Widget build(BuildContext context) {
final elapsed = elapsedSlots(now).toSet();
return Wrap(
spacing: 6,
runSpacing: 4,
children: [
...daySlots().map((slot) {
final isLogged = loggedSlots.contains(slot);
final isDue = !isLogged && elapsed.contains(slot);
final color = isLogged
? Colors.green
: isDue
? Colors.red
: Colors.grey;
final isSelected = selectedSlot == slot;
return ChoiceChip(
label: Text(slotLabel(slot)),
selected: isSelected,
avatar: isLogged ? Icon(Icons.check, size: 14, color: color) : null,
backgroundColor: color.withValues(alpha: 0.15),
selectedColor: color.withValues(alpha: 0.35),
labelStyle: TextStyle(color: color),
side: BorderSide(
width: isSelected ? 2 : 1,
color: isSelected ? color : color.withValues(alpha: 0.4),
),
onSelected: (_) => onSlotSelected(slot),
);
}),
ChoiceChip(
label: const Text('Snack'),
avatar: const Icon(Icons.fastfood, size: 14),
selected: selectedSlot == null,
onSelected: (_) => onSlotSelected(null),
),
],
);
}
}