feat:增加睡眠记录

This commit is contained in:
2025-12-02 14:09:56 +08:00
parent 2cc69b4019
commit 8a71ea542e
14 changed files with 402 additions and 129 deletions

View File

@@ -35,3 +35,13 @@ Widget buildCircleIcon({
child: Icon(icon, color: Theme.of(context).colorScheme.primary, size: 18),
);
}
BoxDecoration buildBoxDecoration(BuildContext context) {
return BoxDecoration(
borderRadius: BorderRadius.circular(8),
border: Border.all(
color: Theme.of(context).colorScheme.primary,
width: 1.5,
),
);
}

View File

@@ -1,11 +1,13 @@
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/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';
@@ -19,24 +21,18 @@ class RecordDaily extends StatefulWidget {
class _RecordDailyState extends State<RecordDaily> {
final HealthService service = HealthService();
late HealthRecord _record;
HealthRecord _record = HealthRecord.getEmpty('');
@override
void initState() {
super.initState();
_record = service.getAllRecordByDate('2025-12-01');
}
final recordForm = HealthRecord(
id: 1,
sportProject: '羽毛球',
sportDuration: 20,
sleepStartTime: '23:00',
sleepEndTime: '24:00',
sleepDuration: 60,
weight: 70,
date: '2025-11-28',
);
WidgetsBinding.instance.addPostFrameCallback((_) {
final provider = context.read<HealthProvider>();
_record = service.getAllRecordByDate(provider.date);
provider.initForm(HealthRecord.getEmpty(provider.date));
});
}
@override
Widget build(BuildContext context) {
@@ -142,7 +138,7 @@ class _RecordDailyState extends State<RecordDaily> {
borderRadius: BorderRadius.circular(20),
),
child: Icon(
_checkHasRecordValue(type)
checkHasRecordValue(_record, type)
? FontAwesomeIcons.pencil
: FontAwesomeIcons.plus,
color: type.color,
@@ -159,29 +155,112 @@ class _RecordDailyState extends State<RecordDaily> {
);
}
bool _checkHasRecordValue(RecordType type) {
switch (type) {
case RecordType.weight:
return _record.weight != 0;
case RecordType.sport:
return _record.sportProject != '';
case RecordType.sleep:
return _record.sleepStartTime != '';
}
}
void onTapRecord(RecordType type) {
final provider = context.read<HealthProvider>();
provider.initForm(HealthRecord.getDefault());
provider.isEditing = checkHasRecordValue(_record, type);
provider.type = type;
if (provider.isEditing) {
_initRecord(provider);
} else {
_defaultRecord(provider);
}
showDialog(
context: context,
builder: (context) => Dialog(child: RecordForm(type: type)),
builder:
(context) => 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.secondary,
text: "取消",
),
),
const SizedBox(width: 12),
Expanded(
child: GFButton(
onPressed: () {
Navigator.of(context).pop();
_saveRecord();
},
color: colors.primary,
text: "确定",
),
),
],
);
}
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() {
final provider = context.read<HealthProvider>();
if (!provider.isEditing) {
_defaultRecord(provider);
}
}
void _defaultRecord(HealthProvider 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;
}
}
void _saveRecord() async {
final provider = context.read<HealthProvider>();
await service.updateRecord(provider.formItem);
setState(() {
_record = service.getAllRecordByDate(provider.date);
});
}
Widget _buildRecordContent(RecordType type) {
if (!_checkHasRecordValue(type)) {
if (!checkHasRecordValue(_record, type)) {
return Text(
"尚未记录今日数据,点击添加",
style: TextStyle(fontSize: 12, color: Colors.grey[500]),
@@ -195,8 +274,16 @@ class _RecordDailyState extends State<RecordDaily> {
case RecordType.sport:
title = "${_record.sportProject} · ${_record.sportDuration}分钟";
case RecordType.sleep:
title =
"入睡 ${_record.sleepStartTime} · 起床 ${_record.sleepEndTime} · ${_record.sleepDuration}分钟";
final startTime = _record.sleepStartTime;
final endTime = _record.sleepEndTime;
if (startTime.isNotEmpty && endTime.isNotEmpty) {
final duration = calculateDuration(startTime, endTime);
title = "入睡 $startTime · 起床 $endTime · ${duration}";
} else if (startTime.isNotEmpty) {
title = "入睡 $startTime";
} else if (endTime.isNotEmpty) {
title = "起床 $endTime";
}
}
return Text(

View File

@@ -1,10 +1,11 @@
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/dropdown_select.dart';
import 'package:fitnote/widget/number_input.dart';
import 'package:fitnote/widget/time_picker.dart';
import 'package:flutter/material.dart';
import 'package:getwidget/getwidget.dart';
import 'package:provider/provider.dart';
class RecordForm extends StatefulWidget {
@@ -21,40 +22,7 @@ class _RecordFormState extends State<RecordForm> {
@override
Widget build(BuildContext context) {
return Padding(padding: EdgeInsets.all(20), child: _buildFormContent());
}
Widget _buildFormButton() {
final colors = Theme.of(context).colorScheme;
return Row(
children: [
Expanded(
child: GFButton(
onPressed: () => Navigator.of(context).pop(),
color: colors.secondary,
text: "取消",
),
),
const SizedBox(width: 12),
Expanded(
child: GFButton(
onPressed: () {
_saveRecord();
Navigator.of(context).pop();
},
color: colors.primary,
text: "确定",
),
),
],
);
}
void _saveRecord() {
final provider = context.read<HealthProvider>();
// service.updateRecord(provider.formItem);
return _buildFormContent();
}
Widget _buildFormTitle(String title) {
@@ -72,6 +40,34 @@ class _RecordFormState extends State<RecordForm> {
return Text(title, style: TextStyle(fontSize: 16));
}
Widget _buildSleepDuration(HealthRecord record) {
final startTime = record.sleepStartTime;
final endTime = record.sleepEndTime;
late String duration = calculateDuration(startTime, endTime);
if (startTime.isNotEmpty && endTime.isNotEmpty) {
duration = calculateDuration(startTime, endTime);
} else {
duration = "";
}
return Container(
padding: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
decoration: BoxDecoration(
color: Theme.of(context).colorScheme.surface,
borderRadius: BorderRadius.circular(8),
),
child: Text(
'睡眠时长:$duration',
style: TextStyle(
fontSize: 16,
fontWeight: FontWeight.bold,
color: Theme.of(context).colorScheme.primary,
),
),
);
}
Widget _buildFormContent() {
final provider = context.watch<HealthProvider>();
@@ -98,7 +94,6 @@ class _RecordFormState extends State<RecordForm> {
],
),
SizedBox(height: 20),
_buildFormButton(),
],
);
case RecordType.sport:
@@ -120,7 +115,6 @@ class _RecordFormState extends State<RecordForm> {
],
),
SizedBox(height: 20),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
@@ -137,18 +131,52 @@ class _RecordFormState extends State<RecordForm> {
],
),
SizedBox(height: 24),
_buildFormButton(),
],
);
case RecordType.sleep:
return Column(
mainAxisSize: MainAxisSize.min,
children: [
_buildFiledTitle('记录睡眠'),
_buildFormTitle('记录睡眠'),
SizedBox(height: 24),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_buildFiledTitle('入睡时间:'),
SizedBox(width: 10),
TimePicker(
initialTime: parseTime(provider.formItem.sleepStartTime),
label: provider.formItem.sleepStartTime,
onTimeSelected: (time) {
provider.updateSleepStartTime(formatTime(time));
final startTime = provider.formItem.sleepStartTime;
final endTime = provider.formItem.sleepEndTime;
calculateDuration(startTime, endTime);
},
),
],
),
SizedBox(height: 20),
// 这里添加睡眠相关的输入控件
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
_buildFiledTitle('起床时间:'),
SizedBox(width: 10),
TimePicker(
initialTime: parseTime(provider.formItem.sleepEndTime),
label: provider.formItem.sleepEndTime,
onTimeSelected: (time) {
provider.updateSleepEndTime(formatTime(time));
final startTime = provider.formItem.sleepStartTime;
final endTime = provider.formItem.sleepEndTime;
calculateDuration(startTime, endTime);
},
),
],
),
SizedBox(height: 20),
_buildFormButton(),
_buildSleepDuration(provider.formItem),
SizedBox(height: 24),
],
);
}