From 8d23f06f7435333f3dd553557cd8a940782bc724 Mon Sep 17 00:00:00 2001 From: Cxx0822 <1556464090@qq.com> Date: Wed, 3 Dec 2025 14:28:39 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E5=A2=9E=E5=8A=A0=E5=8F=98=E5=8C=96?= =?UTF-8?q?=E6=8F=90=E9=86=92=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/main.dart | 2 +- lib/models/health.dart | 9 +-- lib/pages/home_page.dart | 5 +- lib/provider/health_provider.dart | 4 +- lib/service/health_service.dart | 87 +++++++++++++++++++++++--- lib/utils/record_utils.dart | 10 +-- lib/widget/record/common.dart | 39 ++++++++++++ lib/widget/record/goal_progress.dart | 50 ++------------- lib/widget/record/record_calendar.dart | 5 +- lib/widget/record/record_daily.dart | 51 +++++++++++++++ 10 files changed, 194 insertions(+), 68 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index b411d4a..9b7ecdc 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -14,7 +14,7 @@ void main() async { Hive.registerAdapter(HealthRecordAdapter()); final healthsBox = await Hive.openBox('healths'); - // healthsBox.clear(); + healthsBox.clear(); // await Hive.deleteBoxFromDisk('healths'); runApp( diff --git a/lib/models/health.dart b/lib/models/health.dart index 65035f0..b7e2e08 100644 --- a/lib/models/health.dart +++ b/lib/models/health.dart @@ -67,13 +67,14 @@ class HealthRecord extends HiveObject { } enum RecordType { - weight('体重', FontAwesomeIcons.weightScale, Color(0xFFF59E0B)), - sport('运动', FontAwesomeIcons.personRunning, Color(0xFF38BDF8)), - sleep('睡眠', FontAwesomeIcons.moon, Color(0xFF10B981)); + weight('体重', FontAwesomeIcons.weightScale, Color(0xFFF59E0B), 'Kg'), + sport('运动', FontAwesomeIcons.personRunning, Color(0xFF38BDF8), '分钟'), + sleep('睡眠', FontAwesomeIcons.moon, Color(0xFF10B981), '小时'); final String title; final IconData icon; final Color color; + final String unit; - const RecordType(this.title, this.icon, this.color); + const RecordType(this.title, this.icon, this.color, this.unit); } diff --git a/lib/pages/home_page.dart b/lib/pages/home_page.dart index 9893d38..686aeca 100644 --- a/lib/pages/home_page.dart +++ b/lib/pages/home_page.dart @@ -1,4 +1,5 @@ import 'package:fitnote/layout/app_navbar.dart'; +import 'package:fitnote/models/health.dart'; import 'package:fitnote/models/menu.dart'; import 'package:fitnote/provider/health_provider.dart'; import 'package:fitnote/service/health_service.dart'; @@ -65,7 +66,9 @@ class _HomePageState extends State { final HealthService service = HealthService(); final provider = context.read(); provider.updateSelectDate(DateTime.now()); - provider.updateRecord(service.getRecordByDate(provider.selectedDate)); + HealthRecord record = service.getRecordByDate(provider.selectedDate); + provider.updateRecord(record); + provider.initForm(record); } void _onTapNavbarItem(int index) { diff --git a/lib/provider/health_provider.dart b/lib/provider/health_provider.dart index 272fbaf..d336762 100644 --- a/lib/provider/health_provider.dart +++ b/lib/provider/health_provider.dart @@ -13,12 +13,12 @@ class HealthProvider with ChangeNotifier { num avgSleep = 0; void initForm(HealthRecord record) { - formItem = record; + formItem = record.copyWith(); notifyListeners(); } void updateRecord(HealthRecord record) { - currentRecord = record; + currentRecord = record.copyWith(); notifyListeners(); } diff --git a/lib/service/health_service.dart b/lib/service/health_service.dart index 4d48cf5..4b41944 100644 --- a/lib/service/health_service.dart +++ b/lib/service/health_service.dart @@ -21,6 +21,18 @@ class HealthService { return box.values.toList(); } + List getAllRecordsOrderByDate() { + final records = getAllRecords(); + + records.sort((a, b) { + final dateA = DateTime.parse(a.date); + final dateB = DateTime.parse(b.date); + return dateB.compareTo(dateA); + }); + + return records; + } + HealthRecord getRecordByDate(DateTime date) { final record = box.values.toList().firstWhere( (item) => item.date == formatDate(date), @@ -30,6 +42,7 @@ class HealthService { return record.copyWith(); } + // 获取总运动时长 num getTotalSport() { int year = DateTime.now().year; int month = DateTime.now().month; @@ -48,6 +61,7 @@ class HealthService { return totalDuration; } + // 获取平均睡眠 num getAvgSleep() { int year = DateTime.now().year; int month = DateTime.now().month; @@ -61,7 +75,7 @@ class HealthService { final recordDate = DateTime.parse(record.date); if (recordDate.year == year && recordDate.month == month) { total++; - totalDuration += calSleepMinuteDuration(startTime, endTime); + totalDuration += calSleepMinDuration(startTime, endTime); } } } @@ -73,14 +87,9 @@ class HealthService { } } + // 获取最新体重 num getLatestWeight() { - final records = getAllRecords(); - - records.sort((a, b) { - final dateA = DateTime.parse(a.date); - final dateB = DateTime.parse(b.date); - return dateB.compareTo(dateA); - }); + final records = getAllRecordsOrderByDate(); for (final record in records) { if (record.weight != 0) { @@ -91,10 +100,68 @@ class HealthService { return 0; } + // 获取上次体重 + num getLastWeight(String date) { + final records = getAllRecordsOrderByDate(); + + final targetIndex = records.indexWhere((record) => record.date == date); + if (targetIndex == -1) return 0; + + // 从目标日期往前找(更早的日期) + for (int i = targetIndex + 1; i < records.length; i++) { + final record = records[i]; + if (record.weight != 0) { + return record.weight; + } + } + + return 0; + } + + // 获取上次运动时长 + num getLastSport(String date) { + final records = getAllRecordsOrderByDate(); + + final targetIndex = records.indexWhere((record) => record.date == date); + if (targetIndex == -1) return 0; + + // 从目标日期往前找(更早的日期) + for (int i = targetIndex + 1; i < records.length; i++) { + final record = records[i]; + if (record.sportDuration != 0) { + return record.sportDuration; + } + } + + return 0; + } + + // 获取上次睡眠时长 + num getLastSleep(String date) { + final records = getAllRecordsOrderByDate(); + + final targetIndex = records.indexWhere((record) => record.date == date); + if (targetIndex == -1) return 0; + + // 从目标日期往前找(更早的日期) + for (int i = targetIndex + 1; i < records.length; i++) { + final record = records[i]; + final startTime = record.sleepStartTime; + final endTime = record.sleepEndTime; + if (startTime.isNotEmpty && endTime.isNotEmpty) { + return calSleepMinDuration(startTime, endTime); + } + } + + return 0; + } + Future updateRecord(HealthRecord record) async { try { final existingRecords = box.values.toList(); - final index = existingRecords.indexWhere((item) => item.date == record.date); + final index = existingRecords.indexWhere( + (item) => item.date == record.date, + ); if (index == -1) { await box.add(record); @@ -102,7 +169,7 @@ class HealthService { final key = box.keyAt(index); await box.put(key, record); } - + return true; } catch (e) { print('Update record error: $e'); diff --git a/lib/utils/record_utils.dart b/lib/utils/record_utils.dart index 0c03162..0fa89f5 100644 --- a/lib/utils/record_utils.dart +++ b/lib/utils/record_utils.dart @@ -30,7 +30,7 @@ String formatTime(TimeOfDay time) { return '${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}'; } -int calSleepMinuteDuration(String startTime, String endTime) { +int calSleepMinDuration(String startTime, String endTime) { final start = parseTime(startTime); final end = parseTime(endTime); @@ -48,7 +48,7 @@ int calSleepMinuteDuration(String startTime, String endTime) { // 计算睡眠时长 String calSleepDuration(String startTime, String endTime) { try { - int durationMinutes = calSleepMinuteDuration(startTime, endTime); + int durationMinutes = calSleepMinDuration(startTime, endTime); int hours = durationMinutes ~/ 60; int minutes = durationMinutes % 60; @@ -58,7 +58,7 @@ String calSleepDuration(String startTime, String endTime) { } } -double roundNum(double value) { +double roundNum(num value) { return double.parse(value.toStringAsFixed(2)); } @@ -87,9 +87,9 @@ String formatContent(HealthRecord record, RecordType type) { late String title; switch (type) { case RecordType.weight: - title = "${record.weight} kg"; + title = "${record.weight} ${type.unit}"; case RecordType.sport: - title = "${record.sportProject} · ${record.sportDuration} 分钟"; + title = "${record.sportProject} · ${record.sportDuration} ${type.unit}"; case RecordType.sleep: final startTime = record.sleepStartTime; final endTime = record.sleepEndTime; diff --git a/lib/widget/record/common.dart b/lib/widget/record/common.dart index b24a93d..0aa17a4 100644 --- a/lib/widget/record/common.dart +++ b/lib/widget/record/common.dart @@ -1,4 +1,6 @@ +import 'package:fitnote/utils/record_utils.dart'; import 'package:flutter/material.dart'; +import 'package:getwidget/getwidget.dart'; Widget buildCardTitle({ required BuildContext context, @@ -45,3 +47,40 @@ BoxDecoration buildBoxDecoration(BuildContext context) { ), ); } + +Widget buildProgressItem({ + required String title, + required String current, + required String target, + required String unit, + required double progress, + required Color color, +}) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text(title), + Text( + '$current/$target $unit', + style: TextStyle(fontWeight: FontWeight.w500, color: color), + ), + ], + ), + const SizedBox(height: 8), + GFProgressBar( + animation: true, + lineHeight: 20, + percentage: progress, + progressBarColor: color, + child: Text( + '${roundNum(progress * 100)}%', + textAlign: TextAlign.end, + style: TextStyle(color: Colors.white), + ), + ), + ], + ); +} diff --git a/lib/widget/record/goal_progress.dart b/lib/widget/record/goal_progress.dart index 5c4fbcc..c3cbe3d 100644 --- a/lib/widget/record/goal_progress.dart +++ b/lib/widget/record/goal_progress.dart @@ -7,7 +7,6 @@ import 'package:fitnote/widget/record/common.dart'; import 'package:flutter/material.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:provider/provider.dart'; class GoalProgress extends StatefulWidget { @@ -48,11 +47,11 @@ class _GoalProgressState extends State { icon: FontAwesomeIcons.trophy, ), const SizedBox(height: 28), - _buildProgressItem( + buildProgressItem( title: '目标体重', current: provider.latestWeight.toString(), target: AppConfig.goalWeight.toString(), - unit: 'kg', + unit: RecordType.weight.unit, progress: calWeightProgress( provider.latestWeight, AppConfig.goalWeight, @@ -61,22 +60,22 @@ class _GoalProgressState extends State { color: RecordType.weight.color, ), const SizedBox(height: 28), - _buildProgressItem( + buildProgressItem( title: '运动总时长', current: provider.totalSpot.toString(), target: AppConfig.goalTotalSpot.toString(), - unit: '分钟', + unit: RecordType.sport.unit, progress: formatProgress( provider.totalSpot / AppConfig.goalTotalSpot, ), color: RecordType.sport.color, ), const SizedBox(height: 28), - _buildProgressItem( + buildProgressItem( title: '日均睡眠', current: provider.avgSleep.toString(), target: AppConfig.goalAvgSleep.toString(), - unit: '小时', + unit: RecordType.sleep.unit, progress: formatProgress( provider.avgSleep / AppConfig.goalAvgSleep, ), @@ -86,41 +85,4 @@ class _GoalProgressState extends State { ), ); } - - Widget _buildProgressItem({ - required String title, - required String current, - required String target, - required String unit, - required double progress, - required Color color, - }) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(title), - Text( - '$current/$target $unit', - style: TextStyle(fontWeight: FontWeight.w500, color: color), - ), - ], - ), - const SizedBox(height: 8), - GFProgressBar( - animation: true, - lineHeight: 20, - percentage: progress, - progressBarColor: color, - child: Text( - '${roundNum(progress * 100)}%', - textAlign: TextAlign.end, - style: TextStyle(color: Colors.white), - ), - ), - ], - ); - } } diff --git a/lib/widget/record/record_calendar.dart b/lib/widget/record/record_calendar.dart index 7fe0585..4fc8ff2 100644 --- a/lib/widget/record/record_calendar.dart +++ b/lib/widget/record/record_calendar.dart @@ -1,3 +1,4 @@ +import 'package:fitnote/models/health.dart'; import 'package:fitnote/provider/health_provider.dart'; import 'package:fitnote/service/health_service.dart'; import 'package:flutter/material.dart'; @@ -32,7 +33,9 @@ class _RecordCalendarState extends State { onDaySelected: (selectedDay, focusedDay) { if (!isSameDay(provider.selectedDate, selectedDay)) { provider.updateSelectDate(selectedDay); - provider.updateRecord(service.getRecordByDate(provider.selectedDate)); + HealthRecord record = service.getRecordByDate(provider.selectedDate); + provider.updateRecord(record); + provider.initForm(record); } } ); diff --git a/lib/widget/record/record_daily.dart b/lib/widget/record/record_daily.dart index 35c2a10..5b16f4e 100644 --- a/lib/widget/record/record_daily.dart +++ b/lib/widget/record/record_daily.dart @@ -108,6 +108,12 @@ class _RecordDailyState extends State { _buildRecordTitle(type), const SizedBox(height: 4), _buildRecordContent(provider.currentRecord, type), + if (checkHasRecordValue(provider.currentRecord, type)) + _buildRecordTip( + provider.currentRecord, + provider.selectedDate, + type, + ), ], ), trailing: GestureDetector( @@ -279,4 +285,49 @@ class _RecordDailyState extends State { 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]), + ); + } }