From aed666ea74e51a0b957a827e44aeb762f82fd257 Mon Sep 17 00:00:00 2001 From: czz <862977248@qq.com> Date: Thu, 14 Aug 2025 17:48:14 +0800 Subject: [PATCH] =?UTF-8?q?=E5=91=A8=E6=8A=A5=E6=9C=88=E6=8A=A5=E5=8D=A1?= =?UTF-8?q?=E7=89=87=E5=BC=B9=E7=AA=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- assets/langs/en_US.json | 7 +- assets/langs/zh_CN.json | 6 +- .../home_page/WeekSleepDataModule.dart | 252 ++++++++++++++++++ .../component/MonthDataWidget.dart | 3 +- .../component/WeekDataWidget.dart | 3 +- .../sleep_report/component/WeekSleepCard.dart | 159 +++++++++++ .../component/WeekSleepScoreWidget.dart | 9 +- .../sleep_report/new_sleep_report_page.dart | 9 +- 8 files changed, 433 insertions(+), 15 deletions(-) create mode 100644 lib/component/home_page/WeekSleepDataModule.dart create mode 100644 lib/pages/sleep_report/component/WeekSleepCard.dart diff --git a/assets/langs/en_US.json b/assets/langs/en_US.json index 9389200..c016ff8 100644 --- a/assets/langs/en_US.json +++ b/assets/langs/en_US.json @@ -376,7 +376,7 @@ "入睡时间": "Sleep time", "睡眠时长": "Sleep duration", "起床时间": "Wake time", - "小时": "hours", + "小时": "h", "分钟": "minutes", "消息提醒设置": "Message notification settings", "APP消息": "APP messages", @@ -503,5 +503,8 @@ "本月睡眠时长": "Monthly Sleep Duration", "知道了": "Back", "本月睡眠时长是指从月初到月末,用户每天实际睡眠的时间总和": "The monthly sleep duration refers to the total actual sleep time of the user from the beginning to the end of the month.", - "选择生日": "Select Birthday" + "选择生日": "Select Birthday", + "sleep_duration": "Sleep Duration", + "deep_sleep": "Deep Sleep", + "light_sleep": "Light Sleep" } \ No newline at end of file diff --git a/assets/langs/zh_CN.json b/assets/langs/zh_CN.json index dd7721c..8cf4c81 100644 --- a/assets/langs/zh_CN.json +++ b/assets/langs/zh_CN.json @@ -515,5 +515,9 @@ "睡眠分数与上周分数进行对比,是通过量化分析近期睡眠质量变化,可了解自身睡眠状态的波动情况,进而调整用户的作息习惯。": "睡眠分数与上周分数进行对比,是通过量化分析近期睡眠质量变化,可了解自身睡眠状态的波动情况,进而调整用户的作息习惯。", "本月睡眠时长": "本月睡眠时长", "知道了": "返回", - "本月睡眠时长是指从月初到月末,用户每天实际睡眠的时间总和": "本月睡眠时长是指从月初到月末,用户每天实际睡眠的时间总和", "选择生日": "选择生日" + "本月睡眠时长是指从月初到月末,用户每天实际睡眠的时间总和": "本月睡眠时长是指从月初到月末,用户每天实际睡眠的时间总和", + "选择生日": "选择生日", + "sleep_duration": "睡眠时长", + "deep_sleep": "深睡", + "light_sleep": "浅睡" } \ No newline at end of file diff --git a/lib/component/home_page/WeekSleepDataModule.dart b/lib/component/home_page/WeekSleepDataModule.dart new file mode 100644 index 0000000..d622d9f --- /dev/null +++ b/lib/component/home_page/WeekSleepDataModule.dart @@ -0,0 +1,252 @@ +import 'package:ef/ef.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.dart'; +import 'package:flutterflow_ui/flutterflow_ui.dart'; +import 'package:vbvs_app/common/color/appConstants.dart'; +import 'package:vbvs_app/common/util/FitTool.dart'; +import 'package:vbvs_app/common/util/MyUtils.dart'; +import 'package:vbvs_app/component/tool/ClickableContainer.dart'; +import 'package:vbvs_app/controller/sleep/sleep_report_controller.dart'; +import 'package:vbvs_app/controller/theme_controller/ThemeController.dart'; +import 'package:vbvs_app/pages/device_bind/componnet/bind_dialog.dart'; + +class WeekSleepDataModule extends StatefulWidget { + final Map data; + final dynamic sleepReportData; // 可选参数,类型为 var/dynamic + + const WeekSleepDataModule({ + super.key, + required this.data, + this.sleepReportData, // 标记为可选参数 + }); + + @override + State createState() => _WeekSleepDataModuleState(); +} + +class _WeekSleepDataModuleState extends State { + @override + void setState(VoidCallback callback) { + super.setState(callback); + } + + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + ThemeController themeController = Get.find(); + return ClickableContainer( + backgroundColor: themeController.currentColor.sc5, + highlightColor: themeController.currentColor.sc21, + borderRadius: 20.rpx, + padding: EdgeInsetsDirectional.fromSTEB(18.rpx, 10.rpx, 18.rpx, 10.rpx), + onTap: () { + showTipDialog( + backgroundColor: stringToColor("#FFFFFF"), + context, + Container( + constraints: BoxConstraints( + maxHeight: 700.rpx, + ), + child: SingleChildScrollView( + child: Column( + children: [ + Text( + "${widget.data['name']}", + style: TextStyle( + color: stringToColor("#333333"), + fontSize: 36.rpx, + ), + ), + SizedBox( + height: 17.rpx, + ), + // Text( + // (widget.data['tips']?.toString().trim().isNotEmpty ?? false) + // ? widget.data['tips'].toString() + // : "未知数据".tr, + // style: TextStyle( + // color: stringToColor("#C8CBD2"), + // fontSize: 26.rpx, + // ), + // ), + // SizedBox( + // height: 37.rpx, + // ), + Text( + "${widget.data['value']}" + + ((widget.data['unit'] == null || + widget.data['unit'].toString().isEmpty) + ? '' + : widget.data['unit']), + style: TextStyle( + color: stringToColor("${widget.data['color']}"), + fontSize: 60.rpx, + ), + ), + + // SizedBox( + // height: 81.rpx, + // ), + // IntrinsicHeight( + // child: Row( + // crossAxisAlignment: CrossAxisAlignment.stretch, + // children: [ + // for (int i = 0; i < levelGroups.length; i++) ...[ + // // 每个 levelGroup 区域 + // Expanded( + // child: Column( + // mainAxisSize: MainAxisSize.min, + // children: [ + // // Level 名称 + // Text( + // levelGroups[i]['levelName'], + // style: TextStyle( + // fontSize: 30.rpx, + // color: stringToColor("#333333"), + // fontWeight: FontWeight.bold, + // ), + // ), + // SizedBox(height: 38.rpx), + ], + ), + ), + ), + ); + + if (widget.data['onto'] != null && widget.data['onto'] == true) { + //跳转睡眠报告 + Get.toNamed("/newSleepReportPage", arguments: { + 'date': widget.data['time'] != null + ? int.parse(widget.data['time'].toString()) + : DateTime.now().millisecondsSinceEpoch, + "mac": widget.data['mac'] != null && widget.data['mac'].isNotEmpty + ? widget.data['mac'] + : 'aaaaaaeeeeeq', + 'type': 1, + 'name': 'sleep', //'sleep', 'heartRate' 或 'breathe' + 'itemName': widget.data['id'], + 'person': widget.data['person'], + }); + } + }, + child: Container( + width: MediaQuery.sizeOf(context).width * 0.267, + constraints: BoxConstraints( + minWidth: 200.rpx, + minHeight: 161.rpx, + ), + child: Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.spaceEvenly, + children: [ + Text( + '${widget.data['name']}', + style: TextStyle( + fontFamily: 'Inter', + fontSize: 26.rpx, + letterSpacing: 0.0, + color: themeController.currentColor.sc3, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text.rich( + TextSpan( + children: [ + TextSpan( + text: '${widget.data['value']}', + style: TextStyle( + fontFamily: 'Inter', + fontSize: 36.rpx, + letterSpacing: 0.0, + color: themeController.currentColor.sc3, + ), + ), + WidgetSpan(child: SizedBox(width: 2.rpx)), // 可选间距 + TextSpan( + text: widget.data['unit'] != null + ? '${widget.data['unit']}' + : '', + style: TextStyle( + fontFamily: 'Inter', + fontSize: AppConstants().small_text_fontSize, + letterSpacing: 0.0, + color: themeController.currentColor.sc3, + ), + ), + ], + ), + maxLines: 1, + style: TextStyle( + color: + themeController.currentColor.sc3), // 强制 ellipsis 颜色 + overflow: TextOverflow.ellipsis, + ), + ), + if (widget.data['level'] != null) + ClickableContainer( + backgroundColor: (widget.data['color'] == null || + widget.data['color'].toString().isEmpty) + ? Colors.transparent + : stringToColor(widget.data['color']), + highlightColor: themeController.currentColor.sc3, + padding: EdgeInsets.symmetric( + horizontal: 0.rpx, + vertical: 0.rpx, + ), + borderRadius: 8.rpx, + onTap: () { + print('Button pressed ...'); + }, + child: Container( + alignment: Alignment.center, + constraints: BoxConstraints( + minWidth: 43.rpx, + minHeight: 25.rpx, + ), + child: Text( + '${widget.data['level']}', + style: TextStyle( + fontFamily: 'Inter Tight', + color: themeController.currentColor.sc3, + letterSpacing: 0.0, + fontSize: 15.rpx, + ), + ), + ), + ), + ].divide(SizedBox(width: 0.rpx)), + ), + Text( + "正常值".tr + + '${(widget.data['range'] ?? '').toString().isEmpty ? '未知数据'.tr : widget.data['range']}', + style: TextStyle( + fontFamily: 'Inter', + fontSize: AppConstants().small_text_fontSize, + letterSpacing: 0.0, + color: themeController.currentColor.sc4, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/sleep_report/component/MonthDataWidget.dart b/lib/pages/sleep_report/component/MonthDataWidget.dart index ff8fd92..136f81b 100644 --- a/lib/pages/sleep_report/component/MonthDataWidget.dart +++ b/lib/pages/sleep_report/component/MonthDataWidget.dart @@ -7,6 +7,7 @@ import 'package:vbvs_app/pages/sleep_report/component/SleepCard.dart'; import 'package:vbvs_app/pages/sleep_report/component/SleepChartWidget.dart'; import 'package:vbvs_app/pages/sleep_report/component/TrendDataTablePage.dart'; import 'package:vbvs_app/pages/sleep_report/component/TrendDataTextPage.dart'; +import 'package:vbvs_app/pages/sleep_report/component/WeekSleepCard.dart'; import 'package:vbvs_app/pages/sleep_report/component/WeekSleepScoreWidget.dart'; Widget MonthDataWidget( @@ -88,7 +89,7 @@ Widget MonthDataWidget( ), showLabel: sleepReport['scoreList']['type'], ), - SleepCard( + WeekSleepCard( sleepReport: sleepReport, highlightItem: data['itemName'], ), diff --git a/lib/pages/sleep_report/component/WeekDataWidget.dart b/lib/pages/sleep_report/component/WeekDataWidget.dart index 75a8b27..40e5300 100644 --- a/lib/pages/sleep_report/component/WeekDataWidget.dart +++ b/lib/pages/sleep_report/component/WeekDataWidget.dart @@ -7,6 +7,7 @@ import 'package:vbvs_app/pages/sleep_report/component/SleepCard.dart'; import 'package:vbvs_app/pages/sleep_report/component/SleepChartWidget.dart'; import 'package:vbvs_app/pages/sleep_report/component/TrendDataTablePage.dart'; import 'package:vbvs_app/pages/sleep_report/component/TrendDataTextPage.dart'; +import 'package:vbvs_app/pages/sleep_report/component/WeekSleepCard.dart'; import 'package:vbvs_app/pages/sleep_report/component/WeekSleepScoreWidget.dart'; @@ -109,7 +110,7 @@ Widget WeekDataWidget( ), showLabel: sleepReport['scoreList']['type'], ), - SleepCard( + WeekSleepCard( sleepReport: sleepReport, highlightItem: data['itemName'], ), diff --git a/lib/pages/sleep_report/component/WeekSleepCard.dart b/lib/pages/sleep_report/component/WeekSleepCard.dart new file mode 100644 index 0000000..e0eedbc --- /dev/null +++ b/lib/pages/sleep_report/component/WeekSleepCard.dart @@ -0,0 +1,159 @@ +import 'package:flutter/material.dart'; +import 'package:vbvs_app/common/color/appConstants.dart'; +import 'package:vbvs_app/common/util/FitTool.dart'; +import 'package:vbvs_app/common/util/MyUtils.dart'; +import 'package:vbvs_app/component/home_page/SleepDataModuleWidget.dart'; +import 'package:EasyDartModule/EasyDartModule.dart' as es; +import 'package:vbvs_app/component/home_page/WeekSleepDataModule.dart'; +import 'package:vbvs_app/enum/APPPackageType.dart'; +import 'package:vbvs_app/language/AppLanguage.dart'; + +class WeekSleepCard extends StatefulWidget { + final dynamic sleepReport; + final int? highlightItem; + + WeekSleepCard({super.key, required this.sleepReport, this.highlightItem}); + + @override + State createState() => _WeekSleepCardState(); +} + +class _WeekSleepCardState extends State + with TickerProviderStateMixin { + final GlobalKey _highlightKey = GlobalKey(); + AnimationController? _animationController; + bool _shouldAnimate = false; + int? _highlightedId; + int _flashCount = 0; // 用于跟踪闪烁次数 + + @override + void initState() { + super.initState(); + if (widget.highlightItem != null) { + _highlightedId = widget.highlightItem; + _shouldAnimate = true; + _initAnimation(); + } + + WidgetsBinding.instance.addPostFrameCallback((_) { + if (widget.highlightItem != null && + _highlightKey.currentContext != null) { + Scrollable.ensureVisible( + _highlightKey.currentContext!, + duration: Duration(milliseconds: 500), + curve: Curves.easeInOut, + alignment: 0.3, + ); + } + }); + } + + void _initAnimation() { + _animationController = AnimationController( + vsync: this, + duration: Duration(milliseconds: 300), + )..addStatusListener((status) { + if (status == AnimationStatus.completed) { + // 正向动画完成,开始反向动画 + _animationController!.reverse(); + } else if (status == AnimationStatus.dismissed) { + // 反向动画完成,增加计数 + _flashCount++; + // 闪烁3次后停止 + if (_flashCount >= 5) { + _animationController!.dispose(); + setState(() { + _shouldAnimate = false; + _highlightedId = null; + }); + } else { + // 继续下一次闪烁 + _animationController!.forward(); + } + } + }); + + _animationController!.forward(); + } + + @override + void dispose() { + _animationController?.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + try { + if (widget.sleepReport == null || + widget.sleepReport is! Map || + widget.sleepReport.isEmpty) { + return Container(); + } + String? language = ""; + if (AppConstants().ent_type == APPPackageType.MHT.code) { + if (mhLanguageController.selectLanguage != null) { + language = mhLanguageController.selectLanguage.value!.language_code; + } + } else { + if (languageController.selectLanguage != null) { + language = languageController.selectLanguage.value!.language_code; + } + } + int num = AppLanguage().isChinese() ? 3 : 2; + List data = widget.sleepReport['bs'] ?? []; + + return Container( + width: double.infinity, + decoration: BoxDecoration( + color: themeController.currentColor.sc5, + borderRadius: + BorderRadius.circular(AppConstants().normal_container_radius), + ), + child: Padding( + padding: + EdgeInsetsDirectional.fromSTEB(26.rpx, 29.rpx, 26.rpx, 45.rpx), + child: Wrap( + spacing: 23.rpx, + runSpacing: 25.rpx, + children: List.generate(data.length, (index) { + final item = data[index]; + item['showTip'] = true; + final bool isHighlighted = + _shouldAnimate && item['id'] == _highlightedId; + return SizedBox( + width: (MediaQuery.of(context).size.width - 160.rpx) / num, + child: AnimatedBuilder( + animation: _animationController ?? AlwaysStoppedAnimation(0), + builder: (context, child) { + return Container( + key: isHighlighted ? _highlightKey : null, + decoration: isHighlighted + ? BoxDecoration( + border: Border.all( + color: themeController.currentColor.sc2 + .withOpacity( + _animationController?.value ?? 0), + width: 1.rpx, + ), + borderRadius: BorderRadius.circular(8), + ) + : null, + child: WeekSleepDataModule( + data: item, + sleepReportData: widget.sleepReport, + ), + ); + }, + ), + ); + }), + ), + ), + ); + } catch (e) { + es.EasyDartModule.logger.error("数据卡片渲染异常${e}"); + return Container(); + } + } +} diff --git a/lib/pages/sleep_report/component/WeekSleepScoreWidget.dart b/lib/pages/sleep_report/component/WeekSleepScoreWidget.dart index e633d2f..7f26cbf 100644 --- a/lib/pages/sleep_report/component/WeekSleepScoreWidget.dart +++ b/lib/pages/sleep_report/component/WeekSleepScoreWidget.dart @@ -108,16 +108,9 @@ class AvgSleepScoreWidget extends StatelessWidget { centerWidget: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - // Text( - // mediumLabel.tr, - // style: TextStyle( - // color: Colors.white, - // fontSize: AppConstants().normal_text_fontSize, - // ), - // ), Padding( padding: - EdgeInsets.only(top: 12.rpx), // 👈 向下偏移的关键 + EdgeInsets.only(top: 15.rpx), // 👈 向下偏移的关键 child: Text( mediumLabel.tr, style: TextStyle( diff --git a/lib/pages/sleep_report/new_sleep_report_page.dart b/lib/pages/sleep_report/new_sleep_report_page.dart index 6547a99..75b5364 100644 --- a/lib/pages/sleep_report/new_sleep_report_page.dart +++ b/lib/pages/sleep_report/new_sleep_report_page.dart @@ -907,10 +907,15 @@ class _NewSleepReportPageState extends State { breatheCardKey, widget.data); case 2: - return WeekDataWidget(sleepReport, widget.data); + return WeekDataWidget( + sleepReport, + widget.data, + ); case 3: return MonthDataWidget( - sleepReport, widget.data); + sleepReport, + widget.data, + ); default: return NullDataWidget(); }