import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../../shared/models/diary_entry.dart'; import 'menu_provider.dart'; String formatDate(String d) { try { final dt = DateTime.parse(d); const months = [ 'января', 'февраля', 'марта', 'апреля', 'мая', 'июня', 'июля', 'августа', 'сентября', 'октября', 'ноября', 'декабря', ]; return '${dt.day} ${months[dt.month - 1]}'; } catch (_) { return d; } } class DiaryScreen extends ConsumerWidget { final String date; const DiaryScreen({super.key, required this.date}); @override Widget build(BuildContext context, WidgetRef ref) { final state = ref.watch(diaryProvider(date)); final theme = Theme.of(context); return Scaffold( appBar: AppBar( title: Text('Дневник — ${formatDate(date)}'), ), floatingActionButton: FloatingActionButton( onPressed: () => _showAddSheet(context, ref), child: const Icon(Icons.add), ), body: state.when( loading: () => const Center(child: CircularProgressIndicator()), error: (_, __) => Center( child: FilledButton( onPressed: () => ref.read(diaryProvider(date).notifier).load(), child: const Text('Повторить'), ), ), data: (entries) => entries.isEmpty ? Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon(Icons.book_outlined, size: 72, color: theme.colorScheme.onSurfaceVariant), const SizedBox(height: 16), const Text('Нет записей за этот день'), const SizedBox(height: 8), FilledButton.icon( onPressed: () => _showAddSheet(context, ref), icon: const Icon(Icons.add), label: const Text('Добавить запись'), ), ], ), ) : _DiaryList(entries: entries, date: date), ), ); } void _showAddSheet(BuildContext context, WidgetRef ref) { showModalBottomSheet( context: context, isScrollControlled: true, builder: (_) => _AddEntrySheet(date: date, ref: ref), ); } } class _DiaryList extends ConsumerWidget { final List entries; final String date; const _DiaryList({required this.entries, required this.date}); @override Widget build(BuildContext context, WidgetRef ref) { // Group by meal type. const order = ['breakfast', 'lunch', 'dinner']; final grouped = >{}; for (final e in entries) { grouped.putIfAbsent(e.mealType, () => []).add(e); } // Total calories for the day. final totalCal = entries.fold( 0, (sum, e) => sum + ((e.calories ?? 0) * e.portions), ); return ListView( padding: const EdgeInsets.only(bottom: 100), children: [ if (totalCal > 0) Padding( padding: const EdgeInsets.fromLTRB(16, 12, 16, 0), child: Text( 'Итого за день: ≈${totalCal.toInt()} ккал', style: Theme.of(context).textTheme.titleSmall, ), ), for (final mealType in [...order, ...grouped.keys.where((k) => !order.contains(k))]) if (grouped.containsKey(mealType)) ...[ _MealHeader(mealType: mealType), for (final entry in grouped[mealType]!) _EntryTile(entry: entry, date: date), ], ], ); } } class _MealHeader extends StatelessWidget { final String mealType; const _MealHeader({required this.mealType}); @override Widget build(BuildContext context) { final entry = DiaryEntry( id: '', date: '', mealType: mealType, name: '', portions: 1, source: '', ); return Padding( padding: const EdgeInsets.fromLTRB(16, 16, 16, 4), child: Text( entry.mealLabel, style: Theme.of(context).textTheme.titleSmall?.copyWith( fontWeight: FontWeight.w600, ), ), ); } } class _EntryTile extends ConsumerWidget { final DiaryEntry entry; final String date; const _EntryTile({required this.entry, required this.date}); @override Widget build(BuildContext context, WidgetRef ref) { final cal = entry.calories != null ? '≈${(entry.calories! * entry.portions).toInt()} ккал' : ''; return Dismissible( key: ValueKey(entry.id), direction: DismissDirection.endToStart, background: Container( color: Colors.red, alignment: Alignment.centerRight, padding: const EdgeInsets.only(right: 20), child: const Icon(Icons.delete_outline, color: Colors.white), ), onDismissed: (_) => ref.read(diaryProvider(date).notifier).remove(entry.id), child: ListTile( title: Text(entry.name), subtitle: entry.portions != 1 ? Text('${entry.portions} порций') : null, trailing: cal.isNotEmpty ? Text(cal, style: Theme.of(context) .textTheme .bodySmall ?.copyWith(fontWeight: FontWeight.w600)) : null, ), ); } } // ── Add entry bottom sheet ───────────────────────────────────── class _AddEntrySheet extends StatefulWidget { final String date; final WidgetRef ref; const _AddEntrySheet({required this.date, required this.ref}); @override State<_AddEntrySheet> createState() => _AddEntrySheetState(); } class _AddEntrySheetState extends State<_AddEntrySheet> { final _nameController = TextEditingController(); final _calController = TextEditingController(); String _mealType = 'breakfast'; bool _saving = false; static const _mealTypes = [ ('breakfast', 'Завтрак'), ('lunch', 'Обед'), ('dinner', 'Ужин'), ]; @override void dispose() { _nameController.dispose(); _calController.dispose(); super.dispose(); } @override Widget build(BuildContext context) { final insets = MediaQuery.viewInsetsOf(context); return Padding( padding: EdgeInsets.fromLTRB(16, 16, 16, 16 + insets.bottom), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.stretch, children: [ Text('Добавить запись', style: Theme.of(context).textTheme.titleMedium), const SizedBox(height: 16), DropdownMenu( initialSelection: _mealType, expandedInsets: EdgeInsets.zero, label: const Text('Приём пищи'), dropdownMenuEntries: _mealTypes .map((t) => DropdownMenuEntry(value: t.$1, label: t.$2)) .toList(), onSelected: (v) => setState(() => _mealType = v ?? _mealType), ), const SizedBox(height: 12), TextField( controller: _nameController, decoration: const InputDecoration( labelText: 'Название блюда', border: OutlineInputBorder(), ), ), const SizedBox(height: 12), TextField( controller: _calController, keyboardType: TextInputType.number, decoration: const InputDecoration( labelText: 'Калории (необязательно)', border: OutlineInputBorder(), ), ), const SizedBox(height: 16), FilledButton( onPressed: _saving ? null : _save, child: _saving ? const SizedBox( height: 20, width: 20, child: CircularProgressIndicator(strokeWidth: 2), ) : const Text('Добавить'), ), ], ), ); } Future _save() async { final name = _nameController.text.trim(); if (name.isEmpty) return; setState(() => _saving = true); try { final cal = double.tryParse(_calController.text); await widget.ref.read(diaryProvider(widget.date).notifier).add({ 'date': widget.date, 'meal_type': _mealType, 'name': name, 'portions': 1, if (cal != null) 'calories': cal, 'source': 'manual', }); if (mounted) Navigator.pop(context); } finally { if (mounted) setState(() => _saving = false); } } }