feat:更新目标模块
This commit is contained in:
@@ -1,6 +1,6 @@
|
|||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
<application
|
<application
|
||||||
android:label="fitnote"
|
android:label="健康记"
|
||||||
android:name="${applicationName}"
|
android:name="${applicationName}"
|
||||||
android:icon="@mipmap/ic_launcher">
|
android:icon="@mipmap/ic_launcher">
|
||||||
<activity
|
<activity
|
||||||
|
|||||||
@@ -8,6 +8,10 @@ class HealthProvider with ChangeNotifier {
|
|||||||
RecordType type = RecordType.weight;
|
RecordType type = RecordType.weight;
|
||||||
bool isEditing = false;
|
bool isEditing = false;
|
||||||
|
|
||||||
|
num latestWeight = 0;
|
||||||
|
num totalSpot = 0;
|
||||||
|
num avgSleep = 0;
|
||||||
|
|
||||||
void initForm(HealthRecord record) {
|
void initForm(HealthRecord record) {
|
||||||
formItem = record;
|
formItem = record;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
@@ -37,4 +41,19 @@ class HealthProvider with ChangeNotifier {
|
|||||||
formItem.sleepEndTime = sleepEndTime;
|
formItem.sleepEndTime = sleepEndTime;
|
||||||
notifyListeners();
|
notifyListeners();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void updateLatestWeight(num weight) {
|
||||||
|
latestWeight = weight;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateTotalSpot(num duration) {
|
||||||
|
totalSpot = duration;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
|
|
||||||
|
void updateAvgSleep(num duration) {
|
||||||
|
avgSleep = duration;
|
||||||
|
notifyListeners();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
import 'package:fitnote/models/health.dart';
|
import 'package:fitnote/models/health.dart';
|
||||||
|
import 'package:fitnote/utils/record_utils.dart';
|
||||||
import 'package:hive_flutter/hive_flutter.dart';
|
import 'package:hive_flutter/hive_flutter.dart';
|
||||||
|
|
||||||
class HealthService {
|
class HealthService {
|
||||||
@@ -28,6 +29,67 @@ class HealthService {
|
|||||||
return record.copyWith();
|
return record.copyWith();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
num getTotalSport() {
|
||||||
|
int year = DateTime.now().year;
|
||||||
|
int month = DateTime.now().month;
|
||||||
|
num totalDuration = 0;
|
||||||
|
|
||||||
|
for (final record in getAllRecords()) {
|
||||||
|
if (record.sportDuration != 0) {
|
||||||
|
final recordDate = DateTime.parse(record.date);
|
||||||
|
|
||||||
|
if (recordDate.year == year && recordDate.month == month) {
|
||||||
|
totalDuration += record.sportDuration;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return totalDuration;
|
||||||
|
}
|
||||||
|
|
||||||
|
num getAvgSleep() {
|
||||||
|
int year = DateTime.now().year;
|
||||||
|
int month = DateTime.now().month;
|
||||||
|
num totalDuration = 0;
|
||||||
|
int total = 0;
|
||||||
|
|
||||||
|
for (final record in getAllRecords()) {
|
||||||
|
final startTime = record.sleepStartTime;
|
||||||
|
final endTime = record.sleepEndTime;
|
||||||
|
if (startTime.isNotEmpty && endTime.isNotEmpty) {
|
||||||
|
final recordDate = DateTime.parse(record.date);
|
||||||
|
if (recordDate.year == year && recordDate.month == month) {
|
||||||
|
total++;
|
||||||
|
totalDuration += calSleepMinuteDuration(startTime, endTime);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (totalDuration == 0) {
|
||||||
|
return 0;
|
||||||
|
} else {
|
||||||
|
return roundNum(totalDuration / total / 60);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
});
|
||||||
|
|
||||||
|
for (final record in records) {
|
||||||
|
if (record.weight != 0) {
|
||||||
|
return record.weight;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
Future<bool> updateRecord(HealthRecord record) async {
|
Future<bool> updateRecord(HealthRecord record) async {
|
||||||
try {
|
try {
|
||||||
List<HealthRecord> records =
|
List<HealthRecord> records =
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ bool checkHasRecordValue(HealthRecord record, RecordType type) {
|
|||||||
case RecordType.weight:
|
case RecordType.weight:
|
||||||
return record.weight != 0;
|
return record.weight != 0;
|
||||||
case RecordType.sport:
|
case RecordType.sport:
|
||||||
return record.sportProject != '';
|
return record.sportDuration != 0;
|
||||||
case RecordType.sleep:
|
case RecordType.sleep:
|
||||||
return record.sleepStartTime != '';
|
return record.sleepStartTime != '';
|
||||||
}
|
}
|
||||||
@@ -30,21 +30,25 @@ String formatTime(TimeOfDay time) {
|
|||||||
return '${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}';
|
return '${time.hour.toString().padLeft(2, '0')}:${time.minute.toString().padLeft(2, '0')}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int calSleepMinuteDuration(String startTime, String endTime) {
|
||||||
|
final start = parseTime(startTime);
|
||||||
|
final end = parseTime(endTime);
|
||||||
|
|
||||||
|
int startMinutes = start.hour * 60 + start.minute;
|
||||||
|
int endMinutes = end.hour * 60 + end.minute;
|
||||||
|
|
||||||
|
// 处理跨天情况(如果结束时间小于开始时间,认为是第二天)
|
||||||
|
if (endMinutes < startMinutes) {
|
||||||
|
endMinutes += 24 * 60;
|
||||||
|
}
|
||||||
|
|
||||||
|
return endMinutes - startMinutes;
|
||||||
|
}
|
||||||
|
|
||||||
// 计算睡眠时长
|
// 计算睡眠时长
|
||||||
String calculateDuration(String startTime, String endTime) {
|
String calSleepDuration(String startTime, String endTime) {
|
||||||
try {
|
try {
|
||||||
final start = parseTime(startTime);
|
int durationMinutes = calSleepMinuteDuration(startTime, endTime);
|
||||||
final end = parseTime(endTime);
|
|
||||||
|
|
||||||
int startMinutes = start.hour * 60 + start.minute;
|
|
||||||
int endMinutes = end.hour * 60 + end.minute;
|
|
||||||
|
|
||||||
// 处理跨天情况(如果结束时间小于开始时间,认为是第二天)
|
|
||||||
if (endMinutes < startMinutes) {
|
|
||||||
endMinutes += 24 * 60; // 加上一天的分钟数
|
|
||||||
}
|
|
||||||
|
|
||||||
int durationMinutes = endMinutes - startMinutes;
|
|
||||||
int hours = durationMinutes ~/ 60;
|
int hours = durationMinutes ~/ 60;
|
||||||
int minutes = durationMinutes % 60;
|
int minutes = durationMinutes % 60;
|
||||||
|
|
||||||
@@ -53,3 +57,28 @@ String calculateDuration(String startTime, String endTime) {
|
|||||||
return '计算错误';
|
return '计算错误';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
double roundNum(double value) {
|
||||||
|
return double.parse(value.toStringAsFixed(2));
|
||||||
|
}
|
||||||
|
|
||||||
|
double calWeightProgress(num currentWeight, num targetWeight, num startWeight) {
|
||||||
|
if (currentWeight == 0) return 0.0;
|
||||||
|
if (startWeight <= targetWeight) return 0.0;
|
||||||
|
if (currentWeight <= targetWeight) return 1.0;
|
||||||
|
|
||||||
|
num totalToLose = startWeight - targetWeight;
|
||||||
|
num alreadyLost = startWeight - currentWeight;
|
||||||
|
|
||||||
|
return formatProgress((alreadyLost / totalToLose).clamp(0.0, 1.0));
|
||||||
|
}
|
||||||
|
|
||||||
|
double formatProgress(double value) {
|
||||||
|
final result = roundNum(value);
|
||||||
|
|
||||||
|
if (result >= 1) {
|
||||||
|
return 1;
|
||||||
|
} else {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
import 'package:fitnote/models/health.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/common.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter_common/widget/common_widget.dart';
|
import 'package:flutter_common/widget/common_widget.dart';
|
||||||
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
|
||||||
import 'package:getwidget/getwidget.dart';
|
import 'package:getwidget/getwidget.dart';
|
||||||
|
import 'package:provider/provider.dart';
|
||||||
|
|
||||||
class GoalProgress extends StatefulWidget {
|
class GoalProgress extends StatefulWidget {
|
||||||
const GoalProgress({super.key});
|
const GoalProgress({super.key});
|
||||||
@@ -13,8 +17,29 @@ class GoalProgress extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
class _GoalProgressState extends State<GoalProgress> {
|
class _GoalProgressState extends State<GoalProgress> {
|
||||||
|
final HealthService service = HealthService();
|
||||||
|
|
||||||
|
final int initWeight = 72;
|
||||||
|
final int goalWeight = 70;
|
||||||
|
final int goalTotalSpot = 600;
|
||||||
|
final int goalAvgSleep = 8;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initState() {
|
||||||
|
super.initState();
|
||||||
|
|
||||||
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
|
final provider = context.read<HealthProvider>();
|
||||||
|
provider.totalSpot = service.getTotalSport();
|
||||||
|
provider.avgSleep = service.getAvgSleep();
|
||||||
|
provider.latestWeight = service.getLatestWeight();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
final provider = context.watch<HealthProvider>();
|
||||||
|
|
||||||
return CommonCard(
|
return CommonCard(
|
||||||
child: Column(
|
child: Column(
|
||||||
crossAxisAlignment: CrossAxisAlignment.start,
|
crossAxisAlignment: CrossAxisAlignment.start,
|
||||||
@@ -27,28 +52,28 @@ class _GoalProgressState extends State<GoalProgress> {
|
|||||||
const SizedBox(height: 28),
|
const SizedBox(height: 28),
|
||||||
_buildProgressItem(
|
_buildProgressItem(
|
||||||
title: '目标体重',
|
title: '目标体重',
|
||||||
current: '65.2',
|
current: provider.latestWeight.toString(),
|
||||||
target: '60',
|
target: goalWeight.toString(),
|
||||||
unit: 'kg',
|
unit: 'kg',
|
||||||
progress: 0.85,
|
progress: calWeightProgress(provider.latestWeight, goalWeight, initWeight),
|
||||||
color: RecordType.weight.color,
|
color: RecordType.weight.color,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 28),
|
const SizedBox(height: 28),
|
||||||
_buildProgressItem(
|
_buildProgressItem(
|
||||||
title: '运动总时长',
|
title: '运动总时长',
|
||||||
current: '450',
|
current: provider.totalSpot.toString(),
|
||||||
target: '600',
|
target: goalTotalSpot.toString(),
|
||||||
unit: '分钟',
|
unit: '分钟',
|
||||||
progress: 0.75,
|
progress: formatProgress(provider.totalSpot / goalTotalSpot),
|
||||||
color: RecordType.sport.color,
|
color: RecordType.sport.color,
|
||||||
),
|
),
|
||||||
const SizedBox(height: 28),
|
const SizedBox(height: 28),
|
||||||
_buildProgressItem(
|
_buildProgressItem(
|
||||||
title: '日均睡眠',
|
title: '日均睡眠',
|
||||||
current: '7.2',
|
current: provider.avgSleep.toString(),
|
||||||
target: '8',
|
target: goalAvgSleep.toString(),
|
||||||
unit: '小时',
|
unit: '小时',
|
||||||
progress: 0.9,
|
progress: formatProgress(provider.avgSleep / goalAvgSleep),
|
||||||
color: RecordType.sleep.color,
|
color: RecordType.sleep.color,
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -161,9 +161,33 @@ class _RecordDailyState extends State<RecordDaily> {
|
|||||||
provider.type = type;
|
provider.type = type;
|
||||||
|
|
||||||
if (provider.isEditing) {
|
if (provider.isEditing) {
|
||||||
_initRecord(provider);
|
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 {
|
} else {
|
||||||
_defaultRecord(provider);
|
switch (provider.type) {
|
||||||
|
case RecordType.weight:
|
||||||
|
provider.updateWeight(70);
|
||||||
|
break;
|
||||||
|
case RecordType.sport:
|
||||||
|
provider.updateSportProject('跑步');
|
||||||
|
provider.updateSportDuration(30);
|
||||||
|
break;
|
||||||
|
case RecordType.sleep:
|
||||||
|
provider.updateSleepStartTime('');
|
||||||
|
provider.updateSleepEndTime('');
|
||||||
|
break;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
showDialog(
|
showDialog(
|
||||||
@@ -211,42 +235,22 @@ class _RecordDailyState extends State<RecordDaily> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
void _initRecord(HealthProvider provider) {
|
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void _resetRecord() {
|
void _resetRecord() {
|
||||||
final provider = context.read<HealthProvider>();
|
final provider = context.read<HealthProvider>();
|
||||||
if (!provider.isEditing) {
|
if (!provider.isEditing) {
|
||||||
_defaultRecord(provider);
|
switch (provider.type) {
|
||||||
}
|
case RecordType.weight:
|
||||||
}
|
provider.updateWeight(0);
|
||||||
|
break;
|
||||||
void _defaultRecord(HealthProvider provider) {
|
case RecordType.sport:
|
||||||
switch (provider.type) {
|
provider.updateSportProject('跑步');
|
||||||
case RecordType.weight:
|
provider.updateSportDuration(0);
|
||||||
provider.updateWeight(70);
|
break;
|
||||||
break;
|
case RecordType.sleep:
|
||||||
case RecordType.sport:
|
provider.updateSleepStartTime('');
|
||||||
provider.updateSportProject('跑步');
|
provider.updateSleepEndTime('');
|
||||||
provider.updateSportDuration(30);
|
break;
|
||||||
break;
|
}
|
||||||
case RecordType.sleep:
|
|
||||||
provider.updateSleepStartTime('');
|
|
||||||
provider.updateSleepEndTime('');
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -257,6 +261,10 @@ class _RecordDailyState extends State<RecordDaily> {
|
|||||||
setState(() {
|
setState(() {
|
||||||
_record = service.getAllRecordByDate(provider.date);
|
_record = service.getAllRecordByDate(provider.date);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
provider.updateLatestWeight(service.getLatestWeight());
|
||||||
|
provider.updateTotalSpot(service.getTotalSport());
|
||||||
|
provider.updateAvgSleep(service.getAvgSleep());
|
||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildRecordContent(RecordType type) {
|
Widget _buildRecordContent(RecordType type) {
|
||||||
@@ -277,7 +285,7 @@ class _RecordDailyState extends State<RecordDaily> {
|
|||||||
final startTime = _record.sleepStartTime;
|
final startTime = _record.sleepStartTime;
|
||||||
final endTime = _record.sleepEndTime;
|
final endTime = _record.sleepEndTime;
|
||||||
if (startTime.isNotEmpty && endTime.isNotEmpty) {
|
if (startTime.isNotEmpty && endTime.isNotEmpty) {
|
||||||
final duration = calculateDuration(startTime, endTime);
|
final duration = calSleepDuration(startTime, endTime);
|
||||||
title = "入睡 $startTime · 起床 $endTime · ${duration}";
|
title = "入睡 $startTime · 起床 $endTime · ${duration}";
|
||||||
} else if (startTime.isNotEmpty) {
|
} else if (startTime.isNotEmpty) {
|
||||||
title = "入睡 $startTime";
|
title = "入睡 $startTime";
|
||||||
|
|||||||
@@ -43,10 +43,10 @@ class _RecordFormState extends State<RecordForm> {
|
|||||||
Widget _buildSleepDuration(HealthRecord record) {
|
Widget _buildSleepDuration(HealthRecord record) {
|
||||||
final startTime = record.sleepStartTime;
|
final startTime = record.sleepStartTime;
|
||||||
final endTime = record.sleepEndTime;
|
final endTime = record.sleepEndTime;
|
||||||
late String duration = calculateDuration(startTime, endTime);
|
late String duration = calSleepDuration(startTime, endTime);
|
||||||
|
|
||||||
if (startTime.isNotEmpty && endTime.isNotEmpty) {
|
if (startTime.isNotEmpty && endTime.isNotEmpty) {
|
||||||
duration = calculateDuration(startTime, endTime);
|
duration = calSleepDuration(startTime, endTime);
|
||||||
} else {
|
} else {
|
||||||
duration = "";
|
duration = "";
|
||||||
}
|
}
|
||||||
@@ -151,7 +151,7 @@ class _RecordFormState extends State<RecordForm> {
|
|||||||
provider.updateSleepStartTime(formatTime(time));
|
provider.updateSleepStartTime(formatTime(time));
|
||||||
final startTime = provider.formItem.sleepStartTime;
|
final startTime = provider.formItem.sleepStartTime;
|
||||||
final endTime = provider.formItem.sleepEndTime;
|
final endTime = provider.formItem.sleepEndTime;
|
||||||
calculateDuration(startTime, endTime);
|
calSleepDuration(startTime, endTime);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
@@ -169,7 +169,7 @@ class _RecordFormState extends State<RecordForm> {
|
|||||||
provider.updateSleepEndTime(formatTime(time));
|
provider.updateSleepEndTime(formatTime(time));
|
||||||
final startTime = provider.formItem.sleepStartTime;
|
final startTime = provider.formItem.sleepStartTime;
|
||||||
final endTime = provider.formItem.sleepEndTime;
|
final endTime = provider.formItem.sleepEndTime;
|
||||||
calculateDuration(startTime, endTime);
|
calSleepDuration(startTime, endTime);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
],
|
],
|
||||||
|
|||||||
Reference in New Issue
Block a user