import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; import 'package:get/get.dart'; import 'package:uuid/uuid.dart'; import '../models/meal_entry.dart'; import '../services/database_service.dart'; import '../services/sync_service.dart'; const _green = Color(0xFF456C52); const _ink = Color(0xFF17251E); const _muted = Color(0xFF778078); class MealJournalScreen extends StatefulWidget { const MealJournalScreen({super.key}); @override State createState() => _MealJournalScreenState(); } class _MealJournalScreenState extends State { final _db = Get.find(); final _sync = Get.find(); final _name = TextEditingController(); final _calories = TextEditingController(); final _protein = TextEditingController(text: '0'); final _carbs = TextEditingController(text: '0'); final _fat = TextEditingController(text: '0'); final _notes = TextEditingController(); DateTime _day = DateTime.now(); DateTime _consumedAt = DateTime.now(); MealType _type = MealType.lunch; MealEntry? _editing; List _entries = const []; bool _loading = true; bool _saving = false; String? _error; @override void initState() { super.initState(); _loadDay(); _syncAndRefresh(); } Future _syncAndRefresh() async { await _sync.syncMealsNow(); if (mounted) await _loadDay(); } @override void dispose() { _name.dispose(); _calories.dispose(); _protein.dispose(); _carbs.dispose(); _fat.dispose(); _notes.dispose(); super.dispose(); } Future _loadDay() async { setState(() => _loading = true); final entries = await _db.getMealsForDay(_day); if (!mounted) return; setState(() { _entries = entries; _loading = false; }); } void _clearForm() { _name.clear(); _calories.clear(); _protein.text = '0'; _carbs.text = '0'; _fat.text = '0'; _notes.clear(); setState(() { _editing = null; _type = MealType.lunch; final now = DateTime.now(); _consumedAt = DateTime(_day.year, _day.month, _day.day, now.hour, now.minute); _error = null; }); } void _edit(MealEntry entry) { _name.text = entry.name; _calories.text = _number(entry.calories); _protein.text = _number(entry.proteinGrams); _carbs.text = _number(entry.carbohydrateGrams); _fat.text = _number(entry.fatGrams); _notes.text = entry.notes; setState(() { _editing = entry; _type = entry.mealType; _consumedAt = entry.consumedAt; _error = null; }); } Future _save() async { final name = _name.text.trim(); final calories = _parseNumber(_calories.text); final protein = _parseNumber(_protein.text); final carbs = _parseNumber(_carbs.text); final fat = _parseNumber(_fat.text); if (name.isEmpty || calories == null || calories > 10000 || protein == null || protein > 1000 || carbs == null || carbs > 1000 || fat == null || fat > 1000) { setState( () => _error = 'أدخل اسم الوجبة وأرقامًا صالحة ضمن الحدود الموضحة.'); return; } setState(() { _saving = true; _error = null; }); final entry = MealEntry( id: _editing?.id ?? const Uuid().v4(), name: name, mealType: _type, calories: calories, proteinGrams: protein, carbohydrateGrams: carbs, fatGrams: fat, consumedAt: _consumedAt, notes: _notes.text.trim(), ); try { await _sync.queueMeal(entry); if (!mounted) return; _clearForm(); await _loadDay(); } catch (error) { if (mounted) { setState(() => _error = 'تعذر حفظ الوجبة على الجهاز: $error'); } } finally { if (mounted) setState(() => _saving = false); } } Future _chooseDay() async { final chosen = await showDatePicker( context: context, initialDate: _day, firstDate: DateTime(2020), lastDate: DateTime.now().add(const Duration(days: 1)), helpText: 'اختر يوم السجل', ); if (chosen != null) { final now = DateTime.now(); setState(() { _day = chosen; if (_editing == null) { _consumedAt = DateTime( chosen.year, chosen.month, chosen.day, now.hour, now.minute); } }); await _loadDay(); } } Future _chooseTime() async { final chosen = await showTimePicker( context: context, initialTime: TimeOfDay.fromDateTime(_consumedAt), helpText: 'اختر وقت الوجبة', ); if (chosen != null) { setState(() => _consumedAt = DateTime( _day.year, _day.month, _day.day, chosen.hour, chosen.minute)); } } @override Widget build(BuildContext context) { final caloriesTotal = _entries.fold(0, (sum, item) => sum + item.calories); final proteinTotal = _entries.fold(0, (sum, item) => sum + item.proteinGrams); final carbsTotal = _entries.fold(0, (sum, item) => sum + item.carbohydrateGrams); final fatTotal = _entries.fold(0, (sum, item) => sum + item.fatGrams); return Scaffold( appBar: AppBar( title: const Text('سجل الطعام'), actions: [ IconButton( tooltip: 'مزامنة الوجبات', onPressed: _sync.syncMealsNow, icon: const Icon(Icons.sync), ), ], ), body: ListView( padding: const EdgeInsets.fromLTRB(18, 8, 18, 28), children: [ Row(children: [ const Expanded( child: Text('ملخص اليوم المختار', style: TextStyle( color: _ink, fontSize: 21, fontWeight: FontWeight.w600)), ), TextButton.icon( onPressed: _chooseDay, icon: const Icon(Icons.calendar_today_outlined, size: 16), label: Text('${_day.day}/${_day.month}/${_day.year}'), ), ]), const SizedBox(height: 8), Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: _green, borderRadius: BorderRadius.circular(18)), child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(_number(caloriesTotal), style: const TextStyle( color: Colors.white, fontSize: 30, fontWeight: FontWeight.w700)), const Text('سعرة مقدّرة • مجموع ما أدخلته', style: TextStyle(color: Color(0xFFDCE8DA), fontSize: 11)), const SizedBox(height: 12), Wrap(spacing: 8, runSpacing: 6, children: [ _MacroChip('بروتين ${_number(proteinTotal)}غ'), _MacroChip('كربوهيدرات ${_number(carbsTotal)}غ'), _MacroChip('دهون ${_number(fatTotal)}غ'), ]), ]), ), const SizedBox(height: 22), Row(children: [ Expanded( child: Text(_editing == null ? 'إضافة وجبة' : 'تصحيح الوجبة', style: const TextStyle( color: _ink, fontSize: 17, fontWeight: FontWeight.w600))), if (_editing != null) TextButton( onPressed: _clearForm, child: const Text('إلغاء التعديل')), ]), Align( alignment: AlignmentDirectional.centerStart, child: TextButton.icon( onPressed: _chooseTime, icon: const Icon(Icons.schedule, size: 17), label: Text( 'وقت الوجبة: ${TimeOfDay.fromDateTime(_consumedAt).format(context)}'), ), ), const SizedBox(height: 10), _input(_name, 'اسم الوجبة أو الطعام', maxLength: 180, textInputAction: TextInputAction.next), const SizedBox(height: 10), DropdownButtonFormField( key: ValueKey(_type), // `value` keeps compatibility with the project's Flutter 3.13 floor. // ignore: deprecated_member_use value: _type, decoration: const InputDecoration( labelText: 'نوع الوجبة', border: OutlineInputBorder()), items: MealType.values .map((type) => DropdownMenuItem( value: type, child: Text(_mealTypeLabel(type)))) .toList(), onChanged: (value) { if (value != null) setState(() => _type = value); }, ), const SizedBox(height: 10), _input(_calories, 'السعرات (تقدير من الملصق أو إدخالك)', number: true), const SizedBox(height: 10), Row(children: [ Expanded(child: _input(_protein, 'بروتين غ', number: true)), const SizedBox(width: 8), Expanded(child: _input(_carbs, 'كربوهيدرات غ', number: true)), const SizedBox(width: 8), Expanded(child: _input(_fat, 'دهون غ', number: true)), ]), const SizedBox(height: 10), _input(_notes, 'ملاحظات أو حجم الحصة (اختياري)', maxLines: 2, maxLength: 1000), if (_error != null) ...[ const SizedBox(height: 8), Text(_error!, style: const TextStyle(color: Colors.red, fontSize: 12)), ], const SizedBox(height: 12), FilledButton.icon( onPressed: _saving ? null : _save, icon: Icon(_editing == null ? Icons.add : Icons.save_outlined), label: Text(_saving ? 'جارٍ الحفظ…' : _editing == null ? 'حفظ على الجهاز' : 'حفظ التصحيح'), ), const Padding( padding: EdgeInsets.only(top: 9), child: Text( 'هذه تقديرات أدخلتها أنت وليست تحليلًا آليًا للصورة. تبقى محليًا دون اتصال وتُزامن عند توفر الدخول والخادم.', style: TextStyle(color: _muted, fontSize: 10, height: 1.6)), ), const SizedBox(height: 23), const Text('الوجبات المسجلة', style: TextStyle( color: _ink, fontSize: 17, fontWeight: FontWeight.w600)), const SizedBox(height: 9), if (_loading) const Center( child: Padding( padding: EdgeInsets.all(20), child: CircularProgressIndicator())) else if (_entries.isEmpty) const Padding( padding: EdgeInsets.symmetric(vertical: 15), child: Text('لا توجد وجبات مسجلة لهذا اليوم.', style: TextStyle(color: _muted)), ) else for (final entry in _entries) _MealCard(entry: entry, onTap: () => _edit(entry)), ], ), ); } } class _MealCard extends StatelessWidget { const _MealCard({required this.entry, required this.onTap}); final MealEntry entry; final VoidCallback onTap; @override Widget build(BuildContext context) => Card( margin: const EdgeInsets.only(bottom: 8), child: ListTile( onTap: onTap, leading: const CircleAvatar( backgroundColor: Color(0xFFE8EFE5), child: Icon(Icons.restaurant_outlined, color: _green)), title: Text(entry.name, style: const TextStyle(color: _ink, fontWeight: FontWeight.w600)), subtitle: Text( '${_mealTypeLabel(entry.mealType)} • ${entry.calories.round()} سعرة • ${_syncLabel(entry.syncStatus)}', style: const TextStyle(color: _muted, fontSize: 11)), trailing: const Icon(Icons.edit_outlined, size: 18), ), ); } class _MacroChip extends StatelessWidget { const _MacroChip(this.label); final String label; @override Widget build(BuildContext context) => Container( padding: const EdgeInsets.symmetric(horizontal: 9, vertical: 5), decoration: BoxDecoration( color: Colors.white24, borderRadius: BorderRadius.circular(20)), child: Text(label, style: const TextStyle(color: Colors.white, fontSize: 10)), ); } Widget _input(TextEditingController controller, String label, {bool number = false, int maxLines = 1, int? maxLength, TextInputAction? textInputAction}) => TextField( controller: controller, maxLength: maxLength, keyboardType: number ? const TextInputType.numberWithOptions(decimal: true) : TextInputType.text, inputFormatters: number ? [ FilteringTextInputFormatter.allow( RegExp(r'^\d{0,5}(?:[.,]\d{0,2})?')) ] : null, maxLines: maxLines, textInputAction: textInputAction, decoration: InputDecoration(labelText: label, border: const OutlineInputBorder()), ); double? _parseNumber(String value) => double.tryParse(value.trim().replaceAll(',', '.')); String _number(double value) => value == value.roundToDouble() ? value.toInt().toString() : value.toStringAsFixed(1); String _mealTypeLabel(MealType type) => switch (type) { MealType.breakfast => 'فطور', MealType.lunch => 'غداء', MealType.dinner => 'عشاء', MealType.snack => 'وجبة خفيفة', }; String _syncLabel(MealSyncStatus status) => switch (status) { MealSyncStatus.pending => 'بانتظار المزامنة', MealSyncStatus.synced => 'تمت المزامنة', MealSyncStatus.failed => 'تعذرت المزامنة', };