import 'package:fitnote/config/app_config.dart'; import 'package:fitnote/models/health.dart'; import 'package:fitnote/provider/health_provider.dart'; import 'package:fitnote/service/health_service.dart'; import 'package:fitnote/utils/record_utils.dart'; import 'package:fitnote/widget/record/common.dart'; import 'package:fitnote/widget/record/record_form.dart'; import 'package:flutter/material.dart'; import 'package:flutter_common/flutter_common.dart'; import 'package:flutter_common/widget/common_widget.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:getwidget/getwidget.dart'; import 'package:intl/intl.dart'; import 'package:provider/provider.dart'; class RecordDaily extends StatefulWidget { const RecordDaily({super.key}); @override State createState() => _RecordDailyState(); } class _RecordDailyState extends State { final HealthService service = HealthService(); @override Widget build(BuildContext context) { return CommonCard( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ buildCardTitle( context: context, title: '今日记录', icon: FontAwesomeIcons.calendarCheck, ), _buildTodayTag(), ], ), const SizedBox(height: 20), ListView.separated( physics: const NeverScrollableScrollPhysics(), shrinkWrap: true, itemCount: RecordType.values.length, separatorBuilder: (context, index) => SizedBox(height: 8), itemBuilder: (context, index) { final recordType = RecordType.values[index]; return _buildRecordListTile(recordType); }, ), ], ), ); } Widget _buildTodayTag() { final provider = context.watch(); return Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), decoration: BoxDecoration( color: Colors.grey.shade100, borderRadius: BorderRadius.circular(16), ), child: Text( DateFormat('MM月dd日').format(provider.selectedDate), style: const TextStyle(fontSize: 12), ), ); } Widget _buildRecordListTile(RecordType type) { final provider = context.watch(); return Container( decoration: BoxDecoration( gradient: LinearGradient( begin: Alignment.topLeft, end: Alignment.bottomRight, colors: [type.color.withAlpha(10), Colors.white.withAlpha(200)], ), borderRadius: BorderRadius.circular(20), boxShadow: [ BoxShadow( color: Colors.black.withAlpha(10), blurRadius: 12, offset: const Offset(0, 4), ), ], ), child: ListTile( leading: Container( width: 48, height: 48, decoration: BoxDecoration( color: type.color.withAlpha(30), borderRadius: BorderRadius.circular(24), ), child: Icon(type.icon, color: type.color, size: 22), ), title: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ _buildRecordTitle(type), const SizedBox(height: 4), _buildRecordContent(provider.currentRecord, type), if (checkHasRecordValue(provider.currentRecord, type)) _buildRecordTip( provider.currentRecord, provider.selectedDate, type, ), ], ), trailing: GestureDetector( onTap: () => onTapRecord(type), child: Container( width: 40, height: 40, decoration: BoxDecoration( color: type.color.withAlpha(30), borderRadius: BorderRadius.circular(20), ), child: Icon( checkHasRecordValue(provider.currentRecord, type) ? FontAwesomeIcons.pencil : FontAwesomeIcons.plus, color: type.color, size: 18, ), ), ), splashColor: Colors.transparent, contentPadding: const EdgeInsets.symmetric( horizontal: 20, vertical: 12, ), ), ); } void onTapRecord(RecordType type) { final provider = context.read(); final record = provider.currentRecord; provider.isEditing = checkHasRecordValue(record, type); provider.type = type; if (provider.isEditing) { switch (provider.type) { case RecordType.weight: provider.updateWeight(record.weight); break; case RecordType.sport: provider.updateSportProject(record.sportProject); provider.updateSportDuration(record.sportDuration); break; case RecordType.sleep: provider.updateSleepStartTime(record.sleepStartTime); provider.updateSleepEndTime(record.sleepEndTime); break; } } else { switch (provider.type) { case RecordType.weight: provider.updateWeight(70); break; case RecordType.sport: provider.updateSportProject(AppConfig.sportProjectList[0]); provider.updateSportDuration(30); break; case RecordType.sleep: provider.updateSleepStartTime(''); provider.updateSleepEndTime(''); break; } } showDialog(context: context, builder: (context) => _buildDialog(type)); } Widget _buildDialog(RecordType type) { return Dialog( child: Padding( padding: EdgeInsets.all(20), child: Column( mainAxisSize: MainAxisSize.min, children: [RecordForm(type: type), _buildDialogButton()], ), ), ); } Widget _buildDialogButton() { final colors = Theme.of(context).colorScheme; return Row( children: [ Expanded( child: GFButton( onPressed: () { Navigator.of(context).pop(); _resetRecord(); }, color: Colors.grey, text: "取消", ), ), const SizedBox(width: 12), Expanded( child: GFButton( onPressed: () { Navigator.of(context).pop(); _saveRecord(); }, color: colors.primary, text: "确定", ), ), ], ); } void _resetRecord() { final provider = context.read(); if (!provider.isEditing) { switch (provider.type) { case RecordType.weight: provider.updateWeight(0); break; case RecordType.sport: provider.updateSportProject('跑步'); provider.updateSportDuration(0); break; case RecordType.sleep: provider.updateSleepStartTime(''); provider.updateSleepEndTime(''); break; } } } void _saveRecord() async { final provider = context.read(); final newRecord = HealthRecord( date: formatDate(provider.selectedDate), weight: provider.formItem.weight, sportProject: provider.formItem.sportProject, sportDuration: provider.formItem.sportDuration, sleepStartTime: provider.formItem.sleepStartTime, sleepEndTime: provider.formItem.sleepEndTime, ); await service.updateRecord(newRecord); provider.updateRecord(service.getRecordByDate(provider.selectedDate)); _refreshGoalProcess(provider); } void _refreshGoalProcess(HealthProvider provider) { provider.updateLatestWeight(service.getLatestWeight()); provider.updateTotalSpot(service.getTotalSport()); provider.updateAvgSleep(service.getAvgSleep()); } Widget _buildRecordTitle(RecordType type) { return Text( type.title, style: TextStyle(fontWeight: FontWeight.bold, color: type.color), ); } Widget _buildRecordContent(HealthRecord record, RecordType type) { if (!checkHasRecordValue(record, type)) { return Text( "尚未记录今日数据,点击添加", style: TextStyle(fontSize: 14, color: Colors.grey[500]), ); } return Text(formatContent(record, type), style: TextStyle(fontSize: 14)); } Widget _buildRecordTip(HealthRecord record, DateTime date, RecordType type) { late num lastValue = 0; late String change = ''; switch (type) { case RecordType.weight: lastValue = service.getLastWeight(formatDate(date)); break; case RecordType.sport: lastValue = service.getLastSport(formatDate(date)); break; case RecordType.sleep: lastValue = service.getLastSleep(formatDate(date)); break; } if (lastValue == 0) { return const SizedBox.shrink(); } switch (type) { case RecordType.weight: num difference = record.weight - lastValue; String formattedDiff = NumberFormat('+0.00;-0.00').format(difference); change = '较上次 $formattedDiff ${type.unit}'; break; case RecordType.sport: num difference = record.sportDuration - lastValue; String formattedDiff = NumberFormat('+0;-0').format(difference); change = '较上次 $formattedDiff ${type.unit}'; break; case RecordType.sleep: String startTime = record.sleepStartTime; String endTime = record.sleepEndTime; num difference = calSleepMinDuration(startTime, endTime) - lastValue; String formattedDiff = NumberFormat('+0.00;-0.00').format(difference / 60); change = '较上次 $formattedDiff ${type.unit}'; break; } return Text( change, style: TextStyle(fontSize: 14, color: Colors.grey[500]), ); } }