From 2cc69b4019988f43b1e5caa6e04d9429005d969d Mon Sep 17 00:00:00 2001 From: Cxx0822 <1556464090@qq.com> Date: Mon, 1 Dec 2025 23:01:23 +0800 Subject: [PATCH] =?UTF-8?q?feat:=E5=A2=9E=E5=8A=A0=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=AD=98=E5=82=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/layout/app_navbar.dart | 2 +- lib/main.dart | 27 +- lib/models/health.dart | 82 ++++++ lib/models/health.g.dart | 62 +++++ lib/{modesl => models}/menu.dart | 0 lib/modesl/health.dart | 57 ----- lib/pages/home_page.dart | 2 +- lib/pages/record_page.dart | 5 +- lib/provider/health_provider.dart | 43 ++++ lib/service/health_service.dart | 46 ++++ lib/widget/record/goal_progress.dart | 2 +- lib/widget/record/record_daily.dart | 211 ++++++++++++++++ lib/widget/record/record_form.dart | 156 ++++++++++++ lib/widget/record/today_record.dart | 359 --------------------------- pubspec.lock | 69 +++-- pubspec.yaml | 2 +- 16 files changed, 679 insertions(+), 446 deletions(-) create mode 100644 lib/models/health.dart create mode 100644 lib/models/health.g.dart rename lib/{modesl => models}/menu.dart (100%) delete mode 100644 lib/modesl/health.dart create mode 100644 lib/provider/health_provider.dart create mode 100644 lib/service/health_service.dart create mode 100644 lib/widget/record/record_daily.dart create mode 100644 lib/widget/record/record_form.dart delete mode 100644 lib/widget/record/today_record.dart diff --git a/lib/layout/app_navbar.dart b/lib/layout/app_navbar.dart index a2236f0..5991ac1 100644 --- a/lib/layout/app_navbar.dart +++ b/lib/layout/app_navbar.dart @@ -1,4 +1,4 @@ -import 'package:fitnote/modesl/menu.dart'; +import 'package:fitnote/models/menu.dart'; import 'package:flutter/material.dart'; class AppNavbar extends StatefulWidget { diff --git a/lib/main.dart b/lib/main.dart index b84f1e0..a857e78 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,12 +1,31 @@ +import 'package:fitnote/models/health.dart'; import 'package:fitnote/pages/home_page.dart'; +import 'package:fitnote/provider/health_provider.dart'; import 'package:flutter/material.dart'; +import 'package:hive_flutter/hive_flutter.dart'; +import 'package:provider/provider.dart'; -void main() { - runApp(const HealthLogApp()); +void main() async{ + // 初始化Hive + await Hive.initFlutter(); + + // 注册适配器 + Hive.registerAdapter(HealthRecordAdapter()); + + final healthsBox = await Hive.openBox('healths'); + + runApp( + MultiProvider( + providers: [ + ChangeNotifierProvider(create: (_) => HealthProvider()) + ], + child: MyApp(), + ), + ); } -class HealthLogApp extends StatelessWidget { - const HealthLogApp({super.key}); +class MyApp extends StatelessWidget { + const MyApp({super.key}); @override Widget build(BuildContext context) { diff --git a/lib/models/health.dart b/lib/models/health.dart new file mode 100644 index 0000000..32541e1 --- /dev/null +++ b/lib/models/health.dart @@ -0,0 +1,82 @@ +import 'package:flutter/cupertino.dart'; +import 'package:font_awesome_flutter/font_awesome_flutter.dart'; +import 'package:hive/hive.dart'; + +// flutter packages pub run build_runner build +part 'health.g.dart'; + +@HiveType(typeId: 0) +class HealthRecord extends HiveObject { + @HiveField(0) + int id; + + @HiveField(1) + String sportProject; + + @HiveField(2) + num sportDuration; + + @HiveField(3) + num weight; + + @HiveField(4) + String sleepStartTime; + + @HiveField(5) + String sleepEndTime; + + @HiveField(6) + num sleepDuration; + + @HiveField(7) + String date; + + HealthRecord({ + required this.id, + required this.sportProject, + required this.sportDuration, + required this.weight, + required this.sleepStartTime, + required this.sleepEndTime, + required this.sleepDuration, + required this.date, + }); + + static HealthRecord getEmpty() { + return HealthRecord( + id: DateTime.now().millisecondsSinceEpoch ~/ 1000, + sportProject: '', + sportDuration: 0, + weight: 0, + sleepStartTime: '', + sleepEndTime: '', + sleepDuration: 0, + date: '', + ); + } + + static HealthRecord getDefault() { + return HealthRecord( + id: DateTime.now().millisecondsSinceEpoch ~/ 1000, + sportProject: '跑步', + sportDuration: 30, + weight: 70, + sleepStartTime: '', + sleepEndTime: '', + sleepDuration: 0, + date: '', + ); + } +} + +enum RecordType { + weight('体重', FontAwesomeIcons.weightScale, Color(0xFFF59E0B)), + sport('运动', FontAwesomeIcons.personRunning, Color(0xFF38BDF8)), + sleep('睡眠', FontAwesomeIcons.moon, Color(0xFF10B981)); + + final String title; + final IconData icon; + final Color color; + + const RecordType(this.title, this.icon, this.color); +} diff --git a/lib/models/health.g.dart b/lib/models/health.g.dart new file mode 100644 index 0000000..e904ad6 --- /dev/null +++ b/lib/models/health.g.dart @@ -0,0 +1,62 @@ +// GENERATED CODE - DO NOT MODIFY BY HAND + +part of 'health.dart'; + +// ************************************************************************** +// TypeAdapterGenerator +// ************************************************************************** + +class HealthRecordAdapter extends TypeAdapter { + @override + final int typeId = 0; + + @override + HealthRecord read(BinaryReader reader) { + final numOfFields = reader.readByte(); + final fields = { + for (int i = 0; i < numOfFields; i++) reader.readByte(): reader.read(), + }; + return HealthRecord( + id: fields[0] as int, + sportProject: fields[1] as String, + sportDuration: fields[2] as num, + weight: fields[3] as num, + sleepStartTime: fields[4] as String, + sleepEndTime: fields[5] as String, + sleepDuration: fields[6] as num, + date: fields[7] as String, + ); + } + + @override + void write(BinaryWriter writer, HealthRecord obj) { + writer + ..writeByte(8) + ..writeByte(0) + ..write(obj.id) + ..writeByte(1) + ..write(obj.sportProject) + ..writeByte(2) + ..write(obj.sportDuration) + ..writeByte(3) + ..write(obj.weight) + ..writeByte(4) + ..write(obj.sleepStartTime) + ..writeByte(5) + ..write(obj.sleepEndTime) + ..writeByte(6) + ..write(obj.sleepDuration) + ..writeByte(7) + ..write(obj.date); + } + + @override + int get hashCode => typeId.hashCode; + + @override + bool operator ==(Object other) => + identical(this, other) || + other is HealthRecordAdapter && + runtimeType == other.runtimeType && + typeId == other.typeId; +} diff --git a/lib/modesl/menu.dart b/lib/models/menu.dart similarity index 100% rename from lib/modesl/menu.dart rename to lib/models/menu.dart diff --git a/lib/modesl/health.dart b/lib/modesl/health.dart deleted file mode 100644 index 2131bed..0000000 --- a/lib/modesl/health.dart +++ /dev/null @@ -1,57 +0,0 @@ -import 'package:flutter/cupertino.dart'; -import 'package:font_awesome_flutter/font_awesome_flutter.dart'; -import 'package:json_annotation/json_annotation.dart'; - -@JsonSerializable() -class HealthRecord { - @JsonKey(name: 'id') - int? id; - - @JsonKey(name: 'sportProject') - String? sportProject; - - @JsonKey(name: 'sportDuration') - num? sportDuration; - - @JsonKey(name: 'weight') - num? weight; - - @JsonKey(name: 'sleepStartTime') - String? sleepStartTime; - - @JsonKey(name: 'sleepEndTime') - String? sleepEndTime; - - @JsonKey(name: 'sleepDuration') - num? sleepDuration; - - @JsonKey(name: 'date') - String date; - - HealthRecord({ - this.id, - this.sportProject, - this.sportDuration, - this.weight, - this.sleepStartTime, - this.sleepEndTime, - this.sleepDuration, - required this.date, - }); - - // factory Health.fromJson(Map json) => _$HealthFromJson(json); - // - // Map toJson() => _$HealthToJson(this); -} - -enum RecordType { - weight('体重', FontAwesomeIcons.weightScale, Color(0xFFF59E0B)), - sport('运动', FontAwesomeIcons.personRunning, Color(0xFF38BDF8)), - sleep('睡眠', FontAwesomeIcons.moon, Color(0xFF10B981)); - - final String title; - final IconData icon; - final Color color; - - const RecordType(this.title, this.icon, this.color); -} diff --git a/lib/pages/home_page.dart b/lib/pages/home_page.dart index 38642e9..1d1f521 100644 --- a/lib/pages/home_page.dart +++ b/lib/pages/home_page.dart @@ -1,5 +1,5 @@ import 'package:fitnote/layout/app_navbar.dart'; -import 'package:fitnote/modesl/menu.dart'; +import 'package:fitnote/models/menu.dart'; import 'package:flutter/material.dart'; class HomePage extends StatefulWidget { diff --git a/lib/pages/record_page.dart b/lib/pages/record_page.dart index a67e873..a557fd5 100644 --- a/lib/pages/record_page.dart +++ b/lib/pages/record_page.dart @@ -1,5 +1,5 @@ import 'package:fitnote/widget/record/goal_progress.dart'; -import 'package:fitnote/widget/record/today_record.dart'; +import 'package:fitnote/widget/record/record_daily.dart'; import 'package:flutter/material.dart'; import 'package:font_awesome_flutter/font_awesome_flutter.dart'; import 'package:intl/intl.dart'; @@ -24,12 +24,13 @@ class _RecordPageState extends State { const weekdays = ['周日', '周一', '周二', '周三', '周四', '周五', '周六']; return weekdays[weekday - 1]; } + @override Widget build(BuildContext context) { return SingleChildScrollView( child: Column( - children: [GoalProgress(), const SizedBox(height: 24), TodayRecord()], + children: [GoalProgress(), const SizedBox(height: 24), RecordDaily()], ), ); } diff --git a/lib/provider/health_provider.dart b/lib/provider/health_provider.dart new file mode 100644 index 0000000..8119138 --- /dev/null +++ b/lib/provider/health_provider.dart @@ -0,0 +1,43 @@ +import 'package:fitnote/models/health.dart'; +import 'package:flutter/material.dart'; + +class HealthProvider with ChangeNotifier { + late HealthRecord _formItem; + + HealthRecord get formItem => _formItem; + + void resetForm() { + _formItem = HealthRecord.getEmpty(); + notifyListeners(); + } + + void initForm(HealthRecord record) { + _formItem = record; + notifyListeners(); + } + + void updateWeight(num weight) { + _formItem.weight = weight; + notifyListeners(); + } + + void updateSportProject(String sportProject) { + _formItem.sportProject = sportProject; + notifyListeners(); + } + + void updateSportDuration(num sportDuration) { + _formItem.sportDuration = sportDuration; + notifyListeners(); + } + + void updateSleepStartTime(String sleepStartTime) { + _formItem.sleepStartTime = sleepStartTime; + notifyListeners(); + } + + void updateSleepEndTime(String sleepEndTime) { + _formItem.sleepEndTime = sleepEndTime; + notifyListeners(); + } +} diff --git a/lib/service/health_service.dart b/lib/service/health_service.dart new file mode 100644 index 0000000..9504dbd --- /dev/null +++ b/lib/service/health_service.dart @@ -0,0 +1,46 @@ +import 'package:fitnote/models/health.dart'; +import 'package:hive_flutter/hive_flutter.dart'; + +class HealthService { + static const String boxName = 'healths'; + + Box get box => Hive.box(boxName); + + Future addRecord(HealthRecord record) async { + try { + await box.add(record); + return true; + } catch (e) { + return false; + } + } + + List getAllRecords() { + return box.values.toList(); + } + + HealthRecord getAllRecordByDate(String date) { + return box.values.toList().firstWhere( + (item) => item.date == date, + orElse: () => HealthRecord.getEmpty(), + ); + } + + Future updateRecord(HealthRecord record) async { + try { + await record.save(); + return true; + } catch (e) { + return false; + } + } + + Future deleteRecord(HealthRecord record) async { + try { + await record.delete(); + return true; + } catch (e) { + return false; + } + } +} diff --git a/lib/widget/record/goal_progress.dart b/lib/widget/record/goal_progress.dart index 2ef068f..6a9e5bc 100644 --- a/lib/widget/record/goal_progress.dart +++ b/lib/widget/record/goal_progress.dart @@ -1,4 +1,4 @@ -import 'package:fitnote/modesl/health.dart'; +import 'package:fitnote/models/health.dart'; import 'package:fitnote/widget/record/common.dart'; import 'package:flutter/material.dart'; import 'package:flutter_common/widget/common_widget.dart'; diff --git a/lib/widget/record/record_daily.dart b/lib/widget/record/record_daily.dart new file mode 100644 index 0000000..995ed22 --- /dev/null +++ b/lib/widget/record/record_daily.dart @@ -0,0 +1,211 @@ +import 'package:fitnote/models/health.dart'; +import 'package:fitnote/provider/health_provider.dart'; +import 'package:fitnote/service/health_service.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: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(); + + late HealthRecord _record; + + @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', + ); + + @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() { + return Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), + decoration: BoxDecoration( + color: const Color(0xFFF1F5F9), + borderRadius: BorderRadius.circular(16), + ), + child: Text( + DateFormat('MM月dd日').format(DateTime.now()), + style: const TextStyle( + fontSize: 12, + color: Color(0xFF64748B), + fontWeight: FontWeight.w500, + ), + ), + ); + } + + Widget _buildRecordListTile(RecordType type) { + 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: [ + Text( + type.title, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.bold, + color: type.color, + ), + ), + const SizedBox(height: 4), + _buildRecordContent(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(type) + ? FontAwesomeIcons.pencil + : FontAwesomeIcons.plus, + color: type.color, + size: 18, + ), + ), + ), + splashColor: Colors.transparent, + contentPadding: const EdgeInsets.symmetric( + horizontal: 20, + vertical: 12, + ), + ), + ); + } + + 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(); + provider.initForm(HealthRecord.getDefault()); + + showDialog( + context: context, + builder: (context) => Dialog(child: RecordForm(type: type)), + ); + } + + Widget _buildRecordContent(RecordType type) { + if (!_checkHasRecordValue(type)) { + return Text( + "尚未记录今日数据,点击添加", + style: TextStyle(fontSize: 12, color: Colors.grey[500]), + ); + } + + late String title; + switch (type) { + case RecordType.weight: + title = "${_record.weight} kg"; + case RecordType.sport: + title = "${_record.sportProject} · ${_record.sportDuration}分钟"; + case RecordType.sleep: + title = + "入睡 ${_record.sleepStartTime} · 起床 ${_record.sleepEndTime} · ${_record.sleepDuration}分钟"; + } + + return Text( + title, + style: TextStyle( + fontSize: 14, + fontWeight: FontWeight.w500, + color: Colors.grey[800], + ), + ); + } +} diff --git a/lib/widget/record/record_form.dart b/lib/widget/record/record_form.dart new file mode 100644 index 0000000..b95f31d --- /dev/null +++ b/lib/widget/record/record_form.dart @@ -0,0 +1,156 @@ +import 'package:fitnote/models/health.dart'; +import 'package:fitnote/provider/health_provider.dart'; +import 'package:fitnote/service/health_service.dart'; +import 'package:fitnote/widget/dropdown_select.dart'; +import 'package:fitnote/widget/number_input.dart'; +import 'package:flutter/material.dart'; +import 'package:getwidget/getwidget.dart'; +import 'package:provider/provider.dart'; + +class RecordForm extends StatefulWidget { + final RecordType type; + + const RecordForm({super.key, required this.type}); + + @override + State createState() => _RecordFormState(); +} + +class _RecordFormState extends State { + final HealthService service = HealthService(); + + @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(); + + // service.updateRecord(provider.formItem); + } + + Widget _buildFormTitle(String title) { + return Text( + title, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.bold, + color: Theme.of(context).colorScheme.primary, + ), + ); + } + + Widget _buildFiledTitle(String title) { + return Text(title, style: TextStyle(fontSize: 16)); + } + + Widget _buildFormContent() { + final provider = context.watch(); + + switch (widget.type) { + case RecordType.weight: + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + _buildFormTitle('记录体重'), + SizedBox(height: 20), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + _buildFiledTitle('体重:'), + SizedBox(width: 10), + NumberInput( + value: provider.formItem.weight, + min: 0, + max: 100, + step: 0.1, + unit: 'Kg', + onChanged: (value) => provider.updateWeight(value!), + ), + ], + ), + SizedBox(height: 20), + _buildFormButton(), + ], + ); + case RecordType.sport: + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + _buildFormTitle('记录运动'), + SizedBox(height: 24), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + _buildFiledTitle('运动类型:'), + SizedBox(width: 10), + DropdownSelect( + value: provider.formItem.sportProject, + items: ['羽毛球', '乒乓球', '篮球', '跑步'], + onChanged: (value) => provider.updateSportProject(value!), + ), + ], + ), + SizedBox(height: 20), + + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + _buildFiledTitle('运动时长:'), + SizedBox(width: 10), + NumberInput( + value: provider.formItem.sportDuration, + min: 5, + max: 240, + step: 5, + unit: '分钟', + onChanged: (value) => provider.updateSportDuration(value!), + ), + ], + ), + SizedBox(height: 24), + _buildFormButton(), + ], + ); + case RecordType.sleep: + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + _buildFiledTitle('记录睡眠'), + SizedBox(height: 20), + // 这里添加睡眠相关的输入控件 + SizedBox(height: 20), + _buildFormButton(), + ], + ); + } + } +} diff --git a/lib/widget/record/today_record.dart b/lib/widget/record/today_record.dart deleted file mode 100644 index 72f55b5..0000000 --- a/lib/widget/record/today_record.dart +++ /dev/null @@ -1,359 +0,0 @@ -import 'package:fitnote/modesl/health.dart'; -import 'package:fitnote/widget/dropdown_select.dart'; -import 'package:fitnote/widget/number_input.dart'; -import 'package:fitnote/widget/record/common.dart'; -import 'package:fitnote/widget/select_input.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:numberpicker/numberpicker.dart'; - -class TodayRecord extends StatefulWidget { - const TodayRecord({super.key}); - - @override - State createState() => _TodayRecordState(); -} - -class _TodayRecordState extends State { - final record = HealthRecord( - id: 1, - sportProject: '羽毛球', - sportDuration: 20, - sleepStartTime: '23:00', - sleepEndTime: '24:00', - sleepDuration: 60, - weight: 0, - date: '2025-11-28', - ); - - final recordForm = HealthRecord( - id: 1, - sportProject: '羽毛球', - sportDuration: 20, - sleepStartTime: '23:00', - sleepEndTime: '24:00', - sleepDuration: 60, - weight: 70, - date: '2025-11-28', - ); - - @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() { - return Container( - padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), - decoration: BoxDecoration( - color: const Color(0xFFF1F5F9), - borderRadius: BorderRadius.circular(16), - ), - child: Text( - DateFormat('MM月dd日').format(DateTime.now()), - style: const TextStyle( - fontSize: 12, - color: Color(0xFF64748B), - fontWeight: FontWeight.w500, - ), - ), - ); - } - - Widget _buildRecordListTile(RecordType type) { - 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: [ - Text( - type.title, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.bold, - color: type.color, - ), - ), - const SizedBox(height: 4), - _buildRecordContent(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(type) - ? FontAwesomeIcons.pencil - : FontAwesomeIcons.plus, - color: type.color, - size: 18, - ), - ), - ), - splashColor: Colors.transparent, - contentPadding: const EdgeInsets.symmetric( - horizontal: 20, - vertical: 12, - ), - ), - ); - } - - bool _checkHasRecordValue(RecordType type) { - switch (type) { - case RecordType.weight: - return record.weight != null; - case RecordType.sport: - return record.sportProject != null; - case RecordType.sleep: - return record.sleepStartTime != null; - } - } - - Widget _buildDialogButton(RecordType type) { - final colors = Theme.of(context).colorScheme; - - return Row( - children: [ - Expanded( - child: GFButton( - onPressed: () => Navigator.of(context).pop(), - color: colors.secondary.withAlpha(100), - text: "取消", - ), - ), - const SizedBox(width: 12), - Expanded( - child: GFButton(onPressed: () {}, color: colors.primary, text: "确定"), - ), - ], - ); - } - - // void _saveRecordData(RecordType type) { - // setState(() { - // switch (type) { - // case RecordType.weight: - // record.weight = _dialogWeight; - // break; - // case RecordType.sport: - // record.sportProject = _dialogSportProject; - // record.sportDuration = _dialogSportDuration.toInt(); - // break; - // case RecordType.sleep: - // // 处理睡眠数据 - // break; - // } - // }); - // } - - Widget _buildDialogTitle(String title) { - return Text( - title, - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.bold, - color: Theme.of(context).colorScheme.primary, - ), - ); - } - - Widget _buildFiledTitle(String title) { - return Text(title, style: TextStyle(fontSize: 16)); - } - - Widget _buildDialogContent(RecordType type) { - final colors = Theme.of(context).colorScheme; - - switch (type) { - case RecordType.weight: - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - _buildDialogTitle('记录体重'), - SizedBox(height: 20), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - _buildFiledTitle('体重:'), - SizedBox(width: 10), - NumberInput( - value: recordForm.weight!, - min: 0, - max: 100, - step: 0.1, - unit: 'Kg', - onChanged: - (value) => setState(() => recordForm.weight = value!), - ), - ], - ), - SizedBox(height: 20), - _buildDialogButton(type), - ], - ); - case RecordType.sport: - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - _buildDialogTitle('记录运动'), - SizedBox(height: 24), - - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - _buildFiledTitle('运动类型:'), - SizedBox(width: 10), - DropdownSelect( - value: recordForm.sportProject!, - items: ['羽毛球', '乒乓球', '篮球', '跑步'], - onChanged: (newValue) { - setState(() { - recordForm.sportProject = newValue!; - }); - }, - ), - ], - ), - SizedBox(height: 20), - - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - // 运动时长 - _buildFiledTitle('运动时长:'), - SizedBox(width: 10), - NumberInput( - value: recordForm.sportDuration, - min: 5, - max: 240, - step: 5, - unit: '分钟', - onChanged: - (value) => - setState(() => recordForm.sportDuration = value!), - ), - ], - ), - SizedBox(height: 24), - _buildDialogButton(type), - ], - ); - case RecordType.sleep: - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - _buildFiledTitle('记录睡眠'), - SizedBox(height: 20), - // 这里添加睡眠相关的输入控件 - SizedBox(height: 20), - _buildDialogButton(type), - ], - ); - } - } - - void onTapRecord(RecordType type) { - showDialog( - context: context, - builder: - (context) => Dialog( - child: Padding( - padding: EdgeInsets.all(20), - child: _buildDialogContent(type), - ), - ), - ); - } - - Widget _buildRecordContent(RecordType type) { - if (!_checkHasRecordValue(type)) { - return Text( - "尚未记录今日数据,点击添加", - style: TextStyle(fontSize: 12, color: Colors.grey[500]), - ); - } - - late String title; - switch (type) { - case RecordType.weight: - title = "${record.weight} kg"; - case RecordType.sport: - title = "${record.sportProject} · ${record.sportDuration}分钟"; - case RecordType.sleep: - title = - "入睡 ${record.sleepStartTime} · 起床 ${record.sleepEndTime} · ${record.sleepDuration}分钟"; - } - - return Text( - title, - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w500, - color: Colors.grey[800], - ), - ); - } -} diff --git a/pubspec.lock b/pubspec.lock index 4dd4f75..698342f 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -5,18 +5,23 @@ packages: dependency: transitive description: name: _fe_analyzer_shared - sha256: da0d9209ca76bde579f2da330aeb9df62b6319c834fa7baae052021b0462401f + sha256: "16e298750b6d0af7ce8a3ba7c18c69c3785d11b15ec83f6dcd0ad2a0009b3cab" url: "https://pub.flutter-io.cn" source: hosted - version: "85.0.0" + version: "76.0.0" + _macros: + dependency: transitive + description: dart + source: sdk + version: "0.3.3" analyzer: dependency: transitive description: name: analyzer - sha256: "974859dc0ff5f37bc4313244b3218c791810d03ab3470a579580279ba971a48d" + sha256: "1f14db053a8c23e260789e9b0980fa27f2680dd640932cae5e1137cce0e46e1e" url: "https://pub.flutter-io.cn" source: hosted - version: "7.7.1" + version: "6.11.0" args: dependency: transitive description: @@ -197,10 +202,10 @@ packages: dependency: transitive description: name: dart_style - sha256: "8a0e5fba27e8ee025d2ffb4ee820b4e6e2cf5e4246a6b1a477eb66866947e0bb" + sha256: "7306ab8a2359a48d22310ad823521d723acfed60ee1f7e37388e8986853b6820" url: "https://pub.flutter-io.cn" source: hosted - version: "3.1.1" + version: "2.3.8" dbus: dependency: transitive description: @@ -399,6 +404,30 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "2.3.2" + hive: + dependency: "direct main" + description: + name: hive + sha256: "8dcf6db979d7933da8217edcec84e9df1bdb4e4edc7fc77dbd5aa74356d6d941" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.2.3" + hive_flutter: + dependency: "direct main" + description: + name: hive_flutter + sha256: dca1da446b1d808a51689fb5d0c6c9510c0a2ba01e22805d492c73b68e33eecc + url: "https://pub.flutter-io.cn" + source: hosted + version: "1.1.0" + hive_generator: + dependency: "direct dev" + description: + name: hive_generator + sha256: "06cb8f58ace74de61f63500564931f9505368f45f98958bd7a6c35ba24159db4" + url: "https://pub.flutter-io.cn" + source: hosted + version: "2.0.1" http: dependency: transitive description: @@ -456,21 +485,13 @@ packages: source: hosted version: "0.7.2" json_annotation: - dependency: "direct main" + dependency: transitive description: name: json_annotation sha256: "1ce844379ca14835a50d2f019a3099f419082cfdd231cd86a142af94dd5c6bb1" url: "https://pub.flutter-io.cn" source: hosted version: "4.9.0" - json_serializable: - dependency: "direct dev" - description: - name: json_serializable - sha256: c50ef5fc083d5b5e12eef489503ba3bf5ccc899e487d691584699b4bdefeea8c - url: "https://pub.flutter-io.cn" - source: hosted - version: "6.9.5" leak_tracker: dependency: transitive description: @@ -519,6 +540,14 @@ packages: url: "https://pub.flutter-io.cn" source: hosted version: "1.3.0" + macros: + dependency: transitive + description: + name: macros + sha256: "1d9e801cd66f7ea3663c45fc708450db1fa57f988142c64289142c9b7ee80656" + url: "https://pub.flutter-io.cn" + source: hosted + version: "0.1.3-main.0" matcher: dependency: transitive description: @@ -672,7 +701,7 @@ packages: source: hosted version: "1.5.2" provider: - dependency: transitive + dependency: "direct main" description: name: provider sha256: "4e82183fa20e5ca25703ead7e05de9e4cceed1fbd1eadc1ac3cb6f565a09f272" @@ -792,18 +821,18 @@ packages: dependency: transitive description: name: source_gen - sha256: "35c8150ece9e8c8d263337a265153c3329667640850b9304861faea59fc98f6b" + sha256: "14658ba5f669685cd3d63701d01b31ea748310f7ab854e471962670abcf57832" url: "https://pub.flutter-io.cn" source: hosted - version: "2.0.0" + version: "1.5.0" source_helper: dependency: transitive description: name: source_helper - sha256: a447acb083d3a5ef17f983dd36201aeea33fedadb3228fa831f2f0c92f0f3aca + sha256: "86d247119aedce8e63f4751bd9626fc9613255935558447569ad42f9f5b48b3c" url: "https://pub.flutter-io.cn" source: hosted - version: "1.3.7" + version: "1.3.5" source_span: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index 2ad2d05..d0e75fa 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1 +1 @@ -name: fitnote description: "记录健康生活" publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 environment: sdk: ^3.7.0 dependencies: flutter: sdk: flutter cupertino_icons: ^1.0.8 getwidget: ^7.0.0 intl: ^0.19.0 font_awesome_flutter: ^10.7.0 json_annotation: ^4.9.0 numberpicker: ^2.1.2 flutter_common: path: ..\flutter_common dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^5.0.0 build_runner: ^2.4.5 json_serializable: ^6.7.1 # The following section is specific to Flutter packages. flutter: uses-material-design: true \ No newline at end of file +name: fitnote description: "记录健康生活" publish_to: 'none' # Remove this line if you wish to publish to pub.dev version: 1.0.0+1 environment: sdk: ^3.7.0 dependencies: flutter: sdk: flutter cupertino_icons: ^1.0.8 getwidget: ^7.0.0 intl: ^0.19.0 font_awesome_flutter: ^10.7.0 hive: ^2.2.3 hive_flutter: ^1.1.0 numberpicker: ^2.1.2 provider: ^6.1.1 flutter_common: path: ..\flutter_common dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^5.0.0 hive_generator: ^2.0.1 build_runner: ^2.4.6 # The following section is specific to Flutter packages. flutter: uses-material-design: true \ No newline at end of file