diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 5b724c0..523e747 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -67,6 +67,13 @@ + + diff --git a/assets/img/followus.png b/assets/img/followus.png index edc8b3f..aef184a 100644 Binary files a/assets/img/followus.png and b/assets/img/followus.png differ diff --git a/assets/img/icon/explain.svg b/assets/img/icon/explain.svg new file mode 100644 index 0000000..b258c59 --- /dev/null +++ b/assets/img/icon/explain.svg @@ -0,0 +1 @@ +资源 236 \ No newline at end of file diff --git a/assets/langs/zh_CN.json b/assets/langs/zh_CN.json index 7d7f8f0..4efe637 100644 --- a/assets/langs/zh_CN.json +++ b/assets/langs/zh_CN.json @@ -353,5 +353,11 @@ "版本":"版本:", "日报":"日报", "月报":"月报", - "周报":"周报" + "周报":"周报", + "4g设备配置wifi提示":"该设备为4G设备,无需配置wifi", + "微信客服提示":"请先安装微信APP,再联系客服", + "打开微信客服提示":"正在打开微信客服...", + "身高":"身高", + "身高输入提示":"请输入身高" + } \ No newline at end of file diff --git a/assets/langs/zh_Hant.json b/assets/langs/zh_TW.json similarity index 100% rename from assets/langs/zh_Hant.json rename to assets/langs/zh_TW.json diff --git a/lib/common/util/CommonVariables.dart b/lib/common/util/CommonVariables.dart index 583badc..f7cbaf4 100644 --- a/lib/common/util/CommonVariables.dart +++ b/lib/common/util/CommonVariables.dart @@ -3,9 +3,9 @@ class CommonVariables { // 企业微信客服拉起的url地址 - static String wxKfUrl = "https://work.weixin.qq.com/kfid/kfc7d2337b9c07b1269"; + static String wxKfUrl = "https://work.weixin.qq.com/kfid/kfcab6a07e8aac68945"; // 企业微信ID - static String wxCorpId = "ww51feda6026280cd0"; + static String wxCorpId = "wwc17348c75dbde1dc"; //ICP备案号 static String ICPRightCode = "皖ICP备2024068219号-1A"; //公司名称 diff --git a/lib/common/util/MyUtils.dart b/lib/common/util/MyUtils.dart index f1a47e9..db9f71b 100644 --- a/lib/common/util/MyUtils.dart +++ b/lib/common/util/MyUtils.dart @@ -163,16 +163,20 @@ class MyUtils { return '${twoDigits(date.month)}/${twoDigits(date.day)}'; } - static String getFormatChineseTime(date) { - DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(date); - // 格式化年月日 - String dateStr = DateFormat('yyyy年MM月dd日').format(dateTime); - // 获取星期 - const weekDays = ['星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期日']; - String weekStr = weekDays[dateTime.weekday - 1]; + static String getFormatChineseTime(int date, {bool showWeekday = true}) { + DateTime dateTime = DateTime.fromMillisecondsSinceEpoch(date); + // 格式化年月日 + String dateStr = DateFormat('yyyy年MM月dd日').format(dateTime); + + if (!showWeekday) return dateStr; + + // 获取星期 + const weekDays = ['星期一', '星期二', '星期三', '星期四', '星期五', '星期六', '星期日']; + String weekStr = weekDays[dateTime.weekday - 1]; + + return '$dateStr $weekStr'; +} - return '$dateStr $weekStr'; - } } Color stringToColor(String hexColor) { diff --git a/lib/component/base/SleepCalendarWidget.dart b/lib/component/base/SleepCalendarWidget.dart index 4883880..b23fcf3 100644 --- a/lib/component/base/SleepCalendarWidget.dart +++ b/lib/component/base/SleepCalendarWidget.dart @@ -10,11 +10,15 @@ import 'SleepdateWidget.dart'; class SleepCalendarWidget extends StatefulWidget { final int? timestamp; final ValueChanged? onDateSelected; + final int? type; // 新增参数,默认日历类型为日 + final Color highlightColor; // ✅ 新增 const SleepCalendarWidget({ super.key, this.timestamp, this.onDateSelected, + this.type = 1, + this.highlightColor = Colors.black, // ✅ 默认值 }); @override @@ -55,172 +59,233 @@ class _SleepCalendarWidgetState extends State { child: Column( mainAxisSize: MainAxisSize.min, children: [ - Container( - width: double.infinity, - constraints: BoxConstraints(minHeight: 90.rpx), - decoration: BoxDecoration( - color: const Color(0xFF313541), - borderRadius: BorderRadius.only( - topLeft: Radius.circular(20.rpx), - topRight: Radius.circular(20.rpx), - ), - ), - child: Padding( - padding: - EdgeInsetsDirectional.fromSTEB(65.rpx, 0.rpx, 65.rpx, 0.rpx), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - ClickableContainer( - backgroundColor: Colors.transparent, - highlightColor: Colors.grey, - padding: EdgeInsetsDirectional.fromSTEB( - 8.rpx, 8.rpx, 8.rpx, 8.rpx), - onTap: () => calendarController.previousMonth(), - child: Icon( - Icons.arrow_back_ios_new, - color: const Color(0xFF6D6F73), - size: 24.rpx, - ), - ), - Obx(() => Text( - '${calendarController.displayedMonth.value.year}年${calendarController.displayedMonth.value.month}月', - style: TextStyle( - color: Colors.white, - fontSize: 30.rpx, - letterSpacing: 0.0, - ), - )), - ClickableContainer( - backgroundColor: Colors.transparent, - highlightColor: Colors.grey, - padding: EdgeInsetsDirectional.fromSTEB( - 8.rpx, 8.rpx, 8.rpx, 8.rpx), - onTap: () => calendarController.nextMonth(), - child: Icon( - Icons.arrow_forward_ios, - color: const Color(0xFF6D6F73), - size: 24.rpx, - ), - ), - ], - ), - ), - ), - Container( - width: double.infinity, - constraints: BoxConstraints(minHeight: 720.rpx), - decoration: const BoxDecoration(color: Color(0xFF242835)), - child: Padding( - padding: EdgeInsetsDirectional.fromSTEB( - 65.rpx, 13.rpx, 65.rpx, 38.rpx), - child: Obx(() { - final daysInMonth = calendarController.getDaysInMonth(); - final calendarRows = - calendarController.getCalendarRows(daysInMonth); - final selectedDate = calendarController.selectedDate.value; - - return Column( - mainAxisSize: MainAxisSize.max, - children: [ - // Weekdays Header - Container( - constraints: BoxConstraints(minHeight: 90.rpx), - child: Row( - children: [ - for (var day in ["一", "二", "三", "四", "五", "六", "日"]) - Expanded( - child: Center( - child: Text( - day, - style: TextStyle( - color: stringToColor("#FFFFFF"), - fontSize: - AppConstants().normal_text_fontSize, - ), - ), - ), - ), - ], - ), - ), - // Calendar days - Column( - children: calendarRows.map((week) { - return Row( - mainAxisAlignment: MainAxisAlignment.start, - children: week.map((date) { - if (date.year == 0) { - return Expanded( - child: Padding( - padding: EdgeInsets.all(0.rpx), - child: SizedBox.shrink(), - ), - ); - } - final isSelected = selectedDate != null && - date.year == selectedDate.year && - date.month == selectedDate.month && - date.day == selectedDate.day; - return Expanded( - child: SleepdateWidget( - date: date, - isSelected: isSelected, - onTap: () { - calendarController.selectDate(date); - if (widget.onDateSelected != null) { - widget.onDateSelected!(date); - } - print(date); - Get.back(); - }, - ), - ); - }).toList(), - ); - }).toList(), - ), - SizedBox(height: 55.rpx), - Wrap( - spacing: 20.rpx, - runSpacing: 20.rpx, - children: showLabel.map((item) { - return Container( - padding: EdgeInsets.all(5.rpx), - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - width: 20.rpx, - height: 20.rpx, - decoration: BoxDecoration( - color: item["color"], - borderRadius: BorderRadius.circular(10.rpx), - ), - ), - SizedBox(width: 8.rpx), - Text( - item["name"], - style: TextStyle( - color: Colors.white, - fontSize: 24.rpx, - ), - ), - ], - ), - ); - }).toList(), - ), - - ], - ); - }), - ), - ), + _buildHeader(), + _buildCalendarBody(showLabel), ], ), ); } + + Widget _buildHeader() { + return Container( + width: double.infinity, + constraints: BoxConstraints(minHeight: 90.rpx), + decoration: BoxDecoration( + color: const Color(0xFF313541), + borderRadius: BorderRadius.only( + topLeft: Radius.circular(20.rpx), + topRight: Radius.circular(20.rpx), + ), + ), + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 65.rpx), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ClickableContainer( + backgroundColor: Colors.transparent, + highlightColor: Colors.grey, + padding: EdgeInsets.all(8.rpx), + onTap: () => calendarController.previousMonth(), + child: Icon( + Icons.arrow_back_ios_new, + color: const Color(0xFF6D6F73), + size: 24.rpx, + ), + ), + Obx(() => Text( + '${calendarController.displayedMonth.value.year}年${calendarController.displayedMonth.value.month}月', + style: TextStyle( + color: Colors.white, + fontSize: 30.rpx, + ), + )), + ClickableContainer( + backgroundColor: Colors.transparent, + highlightColor: Colors.grey, + padding: EdgeInsets.all(8.rpx), + onTap: () => calendarController.nextMonth(), + child: Icon( + Icons.arrow_forward_ios, + color: const Color(0xFF6D6F73), + size: 24.rpx, + ), + ), + ], + ), + ), + ); + } + + Widget _buildCalendarBody(List> showLabel) { + return Container( + width: double.infinity, + constraints: BoxConstraints(minHeight: 720.rpx), + decoration: const BoxDecoration(color: Color(0xFF242835)), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB(65.rpx, 13.rpx, 65.rpx, 38.rpx), + child: Obx(() { + final daysInMonth = calendarController.getDaysInMonth(); + final calendarRows = calendarController.getCalendarRows(daysInMonth); + final selectedDate = calendarController.selectedDate.value; + + return Column( + mainAxisSize: MainAxisSize.max, + children: [ + // 仅当为日历模式显示周标题 + _buildWeekdayHeader(), + // 日历日期格子,支持日和周的高亮逻辑 + + _buildCalendarGrid(calendarRows, selectedDate), + // TODO: 你可以扩展 month 类型的展示 + SizedBox(height: 55.rpx), + _buildLegend(showLabel), + ], + ); + }), + ), + ); + } + + Widget _buildWeekdayHeader() { + return Container( + constraints: BoxConstraints(minHeight: 90.rpx), + child: Row( + children: ["一", "二", "三", "四", "五", "六", "日"].map((day) { + return Expanded( + child: Center( + child: Text( + day, + style: TextStyle( + color: stringToColor("#FFFFFF"), + fontSize: AppConstants().normal_text_fontSize, + ), + ), + ), + ); + }).toList(), + ), + ); + } + + Widget _buildCalendarGrid( + List> calendarRows, DateTime? selectedDate) { + final isMonthSelected = widget.type == 3; + + Widget content = Column( + children: calendarRows.map((week) { + final isWeekSelected = widget.type == 2 && + selectedDate != null && + week.any((d) => + d.year == selectedDate.year && + d.month == selectedDate.month && + d.day == selectedDate.day); + + final row = Row( + children: week.map((date) { + if (date.year == 0) { + return const Expanded(child: SizedBox.shrink()); + } + + final isSelected = widget.type == 1 && + selectedDate != null && + date.year == selectedDate.year && + date.month == selectedDate.month && + date.day == selectedDate.day; + + return Expanded( + child: SleepdateWidget( + highlightColor: widget.highlightColor, // ✅ 传入高亮颜色 + date: date, + isSelected: isSelected, + onTap: () { + calendarController.selectDate(date); + widget.onDateSelected?.call(date); + Get.back(); + }, + ), + ); + }).toList(), + ); + + if (isWeekSelected) { + return Container( + decoration: BoxDecoration( + color: widget.highlightColor, + borderRadius: + BorderRadius.circular(AppConstants().button_container_radius), + ), + padding: EdgeInsets.symmetric(vertical: 6.rpx), + margin: EdgeInsets.symmetric(vertical: 6.rpx), + child: row, + ); + } else { + return row; + } + }).toList(), + ); + + // 如果是月份模式,包一层黑色背景容器 + if (isMonthSelected && selectedDate != null) { + final displayedMonth = calendarController.displayedMonth.value; + + final isSameMonth = displayedMonth.year == selectedDate.year && + displayedMonth.month == selectedDate.month; + + if (isSameMonth) { + // ✅ 当前显示的月 == 当前选中的月 → 加高亮外框 + return Container( + decoration: BoxDecoration( + color: widget.highlightColor, // 背景淡色 + borderRadius: + BorderRadius.circular(AppConstants().normal_container_radius), + // border: Border.all( + // color: widget.highlightColor, + // width: 2.rpx, + // ), + ), + // padding: EdgeInsets.symmetric(vertical: 0.rpx, horizontal: 0.rpx), + // margin: EdgeInsets.symmetric(vertical: 0.rpx), + child: content, + ); + } else { + return content; + } + } else { + return content; + } + } + + Widget _buildLegend(List> showLabel) { + return Wrap( + spacing: 20.rpx, + runSpacing: 20.rpx, + children: showLabel.map((item) { + return Container( + padding: EdgeInsets.all(5.rpx), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 20.rpx, + height: 20.rpx, + decoration: BoxDecoration( + color: item["color"], + borderRadius: BorderRadius.circular(10.rpx), + ), + ), + SizedBox(width: 8.rpx), + Text( + item["name"], + style: TextStyle( + color: Colors.white, + fontSize: 24.rpx, + ), + ), + ], + ), + ); + }).toList(), + ); + } } - - diff --git a/lib/component/base/SleepdateWidget.dart b/lib/component/base/SleepdateWidget.dart index dad3b1b..2aea2a5 100644 --- a/lib/component/base/SleepdateWidget.dart +++ b/lib/component/base/SleepdateWidget.dart @@ -6,12 +6,14 @@ class SleepdateWidget extends StatelessWidget { final DateTime date; final bool isSelected; final VoidCallback onTap; + final Color highlightColor; // 新增 const SleepdateWidget({ - super.key, + super.key, required this.date, required this.isSelected, required this.onTap, + this.highlightColor = Colors.black, // 默认值黑色 }); @override @@ -25,8 +27,8 @@ class SleepdateWidget extends StatelessWidget { width: 90.rpx, height: 90.rpx, decoration: BoxDecoration( - borderRadius: BorderRadius.circular(30.rpx), - color: isSelected ? Colors.black : Colors.transparent, + borderRadius: BorderRadius.circular(30.rpx), + color: isSelected ? highlightColor : Colors.transparent, // 使用传入的颜色 ), child: Padding( padding: EdgeInsetsDirectional.fromSTEB(10.rpx, 10.rpx, 10.rpx, 10.rpx), @@ -35,15 +37,13 @@ class SleepdateWidget extends StatelessWidget { color: Color(0xFFDC1C1C), shape: BoxShape.circle, ), - child: Align( - alignment: AlignmentDirectional(0, 0), - child: Text( - '${date.day}', - style: TextStyle( - color: Colors.white, - fontSize: 26.rpx, - letterSpacing: 0.0, - ), + alignment: Alignment.center, + child: Text( + '${date.day}', + style: TextStyle( + color: Colors.white, + fontSize: 26.rpx, + letterSpacing: 0.0, ), ), ), diff --git a/lib/component/tool/ToggleColorContainer.dart b/lib/component/tool/ToggleColorContainer.dart new file mode 100644 index 0000000..479c1c1 --- /dev/null +++ b/lib/component/tool/ToggleColorContainer.dart @@ -0,0 +1,47 @@ +import 'package:flutter/material.dart'; + +class ToggleColorContainer extends StatelessWidget { + final Color initialColor; + final Color toggledColor; + final EdgeInsetsGeometry padding; + final Widget child; + final double borderRadius; + final VoidCallback onToggle; + final bool toggled; // 新增,外部传入是否高亮 + + const ToggleColorContainer({ + Key? key, + required this.initialColor, + required this.toggledColor, + required this.padding, + required this.child, + required this.onToggle, + required this.toggled, + this.borderRadius = 0, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final Color currentColor = toggled ? toggledColor : initialColor; + + return Material( + color: Colors.transparent, + child: Ink( + decoration: BoxDecoration( + color: currentColor, + borderRadius: BorderRadius.circular(borderRadius), + ), + child: InkWell( + borderRadius: BorderRadius.circular(borderRadius), + onTap: onToggle, + splashColor: Colors.transparent, + highlightColor: Colors.transparent, + child: Padding( + padding: padding, + child: child, + ), + ), + ), + ); + } +} diff --git a/lib/component/tool/cmd.dart b/lib/component/tool/cmd.dart index dafd83e..791e3b9 100644 --- a/lib/component/tool/cmd.dart +++ b/lib/component/tool/cmd.dart @@ -1,17 +1,18 @@ //蓝牙指令 -// wifi列表指令 import 'package:EasyDartModule/EasyDartModule.dart' as edm; import 'package:easydevice/src/app/thapp.dart'; import 'package:vbvs_app/common/util/DailyLogUtils.dart'; +// wifi列表指令 getWifiList(THapp tHapp) async { try { + print("wscan scan"); edm.EasyDartModule.logger.info("发送请求网络列表指令"); DailyLogUtils.writeLog("发送请求网络列表指令"); List data = []; var wifilist = await tHapp.send("wscan scan", true, (log) { - print("[bles]${log.log}"); + print("[aaaaaa]${log.log}"); if (log.log.contains("SCAN RESULT OVER!")) { final wifiList = >[]; final items = log.log.split('[wifi]: SCAN RESULT ITEM:'); @@ -39,7 +40,7 @@ getWifiList(THapp tHapp) async { } return false; - }, 10); + }, 2); return wifilist; } catch (e) { @@ -93,7 +94,7 @@ Future sendWifiSetting(wifiItem, String password, THapp tHapp) async { return true; } return false; - }, 10); + }, 1); if (!success) { edm.EasyDartModule.logger.error("WiFi配置超时或失败"); @@ -107,13 +108,71 @@ Future sendWifiSetting(wifiItem, String password, THapp tHapp) async { } } +// getDeviceWifiStatus(THapp tHapp, int times) async { +// edm.EasyDartModule.logger.info("发送请求设备已配置网络状态指令"); +// DailyLogUtils.writeLog("发送请求设备已配置网络状态指令"); +// try { +// var result = await tHapp.send("at+system info", true, (ss) { +// var log = ss.log; + +// // 匹配设备状态 +// final statusMatch = RegExp(r'Status=([^\s]+)').firstMatch(log); +// final status = statusMatch?.group(1); +// if (status != null) { +// print('提取到的 status: $status'); + +// // 如果设备连接状态是 "connect",继续检测 +// if (status.contains('connect')) { +// ss.result = true; +// ss.over = true; + +// // 匹配 Wi-Fi 连接信息 +// final wifiInfoMatch = RegExp( +// r'WIFI CONNECTED INFO:SSID=([^\s]+),RSSI=([-0-9]+),AUTH=([0-9]+),CH=([0-9]+),BSSID=([A-F0-9]+)') +// .firstMatch(log); +// if (wifiInfoMatch != null) { +// final ssid = wifiInfoMatch.group(1); +// final rssi = wifiInfoMatch.group(2); +// final auth = wifiInfoMatch.group(3); +// final ch = wifiInfoMatch.group(4); +// final bssid = wifiInfoMatch.group(5); + +// // 打印并返回 Wi-Fi 信息 +// print( +// 'Wi-Fi 信息: SSID=$ssid, RSSI=$rssi, AUTH=$auth, CH=$ch, BSSID=$bssid'); + +// // 停止监听并返回信息 +// ss.result = { +// 'ssid': ssid, +// 'rssi': rssi, +// 'auth': auth, +// 'ch': ch, +// 'bssid': bssid, +// }; +// ss.over = true; +// return ss.result; +// } +// } +// } + +// // 未找到状态或Wi-Fi信息时,返回 false +// return false; +// }, times); + +// return result; +// } catch (e) { +// print(e); +// } +// } + getDeviceWifiStatus(THapp tHapp, int times) async { edm.EasyDartModule.logger.info("发送请求设备已配置网络状态指令"); DailyLogUtils.writeLog("发送请求设备已配置网络状态指令"); + print("at+system info"); + try { var result = await tHapp.send("at+system info", true, (ss) { var log = ss.log; - // 匹配设备状态 final statusMatch = RegExp(r'Status=([^\s]+)').firstMatch(log); final status = statusMatch?.group(1); @@ -122,9 +181,6 @@ getDeviceWifiStatus(THapp tHapp, int times) async { // 如果设备连接状态是 "connect",继续检测 if (status.contains('connect')) { - ss.result = true; - ss.over = true; - // 匹配 Wi-Fi 连接信息 final wifiInfoMatch = RegExp( r'WIFI CONNECTED INFO:SSID=([^\s]+),RSSI=([-0-9]+),AUTH=([0-9]+),CH=([0-9]+),BSSID=([A-F0-9]+)') @@ -149,12 +205,12 @@ getDeviceWifiStatus(THapp tHapp, int times) async { 'bssid': bssid, }; ss.over = true; - return ss.result; + return true; } } } - // 未找到状态或Wi-Fi信息时,返回 false + // 继续监听 return false; }, times); @@ -163,3 +219,37 @@ getDeviceWifiStatus(THapp tHapp, int times) async { print(e); } } + + Future getDeviceNetVersion(THapp tHapp, int times) async { + edm.EasyDartModule.logger.info("发送请求设备的网络信息"); + DailyLogUtils.writeLog("发送请求设备的网络信息"); + print("ls /root/mnt"); + + String netType = "unknown"; + + try { + var result = await tHapp.send("ls /root/mnt", true, (ss) { + var log = ss.log; + print("[获取]$log"); + + if (log.contains("wifi.json") || log.contains("a76xx.json")) { + if (log.contains("wifi.json")) { + netType = "wifi"; + } else if (log.contains("a76xx.json")) { + netType = "4g"; + } + ss.over = true; + ss.result = netType; + // 继续监听 + return true; + } + ss.over = false; + return false; + }, times, 15); + + return netType; + } catch (e) { + print(e); + return netType; + } +} diff --git a/lib/controller/device/blueteeth_bind_controller.dart b/lib/controller/device/blueteeth_bind_controller.dart index be29036..67ba02c 100644 --- a/lib/controller/device/blueteeth_bind_controller.dart +++ b/lib/controller/device/blueteeth_bind_controller.dart @@ -66,8 +66,14 @@ class BlueteethBindController extends GetControllerEx { RxMap connect_wifi = {}.obs; RxString scanMac = "".obs; - String? currentDeviceMac; - RxString? cid = "".obs ;//校准id + RxString? currentDeviceMac = "".obs; + RxString? cid = "".obs ; + + RxString search = "".obs;//搜索关键字 + + RxInt connectStatus = 0.obs; + + RxMap selectWifi = {}.obs; //正在连接wifi信息 // 安全展示 TopSlideNotification void safeShowNotification(String msg) { diff --git a/lib/controller/device/body_device_controller.dart b/lib/controller/device/body_device_controller.dart index cdcf1fe..2c1a09d 100644 --- a/lib/controller/device/body_device_controller.dart +++ b/lib/controller/device/body_device_controller.dart @@ -44,6 +44,8 @@ class BodyDeviceController extends GetControllerEx { RxString keyWord = "".obs; + String wifiMac = ""; + Future getDeviceNum() async { try { ApiResponse apiResponse = ApiResponse(code: -1, msg: "设备.设备列表请求失败".tr); diff --git a/lib/controller/login/login_controller.dart b/lib/controller/login/login_controller.dart index 70fd69e..9501365 100644 --- a/lib/controller/login/login_controller.dart +++ b/lib/controller/login/login_controller.dart @@ -10,6 +10,7 @@ import 'package:get_storage/get_storage.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:vbvs_app/common/color/ServiceConstant.dart'; import 'package:vbvs_app/common/color/app_uri_status.dart'; +import 'package:vbvs_app/common/util/CommonVariables.dart'; import 'package:vbvs_app/common/util/MyUtils.dart'; import 'package:vbvs_app/common/util/requestWithLog.dart'; import 'package:vbvs_app/component/tool/TopSlideNotification.dart'; @@ -231,6 +232,21 @@ class LoginController extends GetControllerEx { return apiResponse; } + Future openWeChatCustomerService(BuildContext context) async { + bool isWeChatInstalled = await fluwx.isWeChatInstalled; + if (!isWeChatInstalled) { + TopSlideNotification.show(context, + text: "微信客服提示".tr, + textColor: themeController.currentColor.sc9); + return; + } + TopSlideNotification.show(context, + text: "打开微信客服提示".tr, textColor: themeController.currentColor.sc1); + await fluwx.open( + target: CustomerServiceChat( + corpId: CommonVariables.wxCorpId, url: CommonVariables.wxKfUrl)); + } + //退出登录 // Future logout() async { // await repository.logout(); diff --git a/lib/controller/person/person_controller.dart b/lib/controller/person/person_controller.dart index 6a1ab5c..a2447b1 100644 --- a/lib/controller/person/person_controller.dart +++ b/lib/controller/person/person_controller.dart @@ -56,6 +56,7 @@ class PersonController extends GetControllerEx { RxInt gender = 1.obs; RxString birthday = "".obs; RxInt weight = 65.obs; + RxInt height = 175.obs; DateTime? dateTime = DateTime.now(); //选择时间 RxList diseaseList = [].obs; @@ -89,6 +90,10 @@ class PersonController extends GetControllerEx { apiResponse.msg = "请输入体重".tr; return apiResponse; } + if (height.value == 0) { + apiResponse.msg = "请输入身高".tr; + return apiResponse; + } var data = { "id": currentPersonId.value, @@ -98,6 +103,7 @@ class PersonController extends GetControllerEx { ? MyUtils.formatBindTime(dateTime!) : birthday.value, "weight": weight.value, + "height": height.value, "disease": selectedDiseaseIds.value, }; var response = diff --git a/lib/controller/user_info_controller.dart b/lib/controller/user_info_controller.dart index c746e49..c47806a 100644 --- a/lib/controller/user_info_controller.dart +++ b/lib/controller/user_info_controller.dart @@ -17,6 +17,7 @@ import 'package:vbvs_app/controller/time/countdown_controller.dart'; import 'package:vbvs_app/model/api_response.dart'; import 'package:vbvs_app/model/user_data.dart'; + part 'user_info_controller.g.dart'; @JsonSerializable() @@ -134,16 +135,16 @@ class UserInfoController extends GetControllerEx { String serviceApi = ServiceConstant.user_info; String queryUrl = "${serviceAddress}${serviceName}${serviceApi}"; String? language = ""; - if (languageController.selectLanguage != null) { - language = languageController.selectLanguage.value!.language_code; - } - if (language != null && language.isNotEmpty) { - if (queryUrl.contains("?")) { - queryUrl += "&lang=$language"; - } else { - queryUrl += "?lang=$language"; - } + if (languageController.selectLanguage != null) { + language = languageController.selectLanguage.value!.language_code; + } + if (language != null && language.isNotEmpty) { + if (queryUrl.contains("?")) { + queryUrl += "&lang=$language"; + } else { + queryUrl += "?lang=$language"; } + } final data = { "nickName": user.tmpNickName, if (user.tmpHead != null && user.tmpHead!.isNotEmpty) @@ -180,16 +181,16 @@ class UserInfoController extends GetControllerEx { String serviceApi = ServiceConstant.user_info; String queryUrl = "${serviceAddress}${serviceName}${serviceApi}"; String? language = ""; - if (languageController.selectLanguage != null) { - language = languageController.selectLanguage.value!.language_code; - } - if (language != null && language.isNotEmpty) { - if (queryUrl.contains("?")) { - queryUrl += "&lang=$language"; - } else { - queryUrl += "?lang=$language"; - } + if (languageController.selectLanguage != null) { + language = languageController.selectLanguage.value!.language_code; + } + if (language != null && language.isNotEmpty) { + if (queryUrl.contains("?")) { + queryUrl += "&lang=$language"; + } else { + queryUrl += "?lang=$language"; } + } var response = await EasyDartModule.dio.get(queryUrl); if (response != null) { var responseData = @@ -238,4 +239,6 @@ class UserInfoController extends GetControllerEx { countdownController.countdown.value = 0; return apiResponse; } + + } diff --git a/lib/enum/CalendarType.dart b/lib/enum/CalendarType.dart new file mode 100644 index 0000000..1f6862a --- /dev/null +++ b/lib/enum/CalendarType.dart @@ -0,0 +1,10 @@ +enum CalendarType { + day(1, '日'), + week(2, '周'), + month(3, '月'); + + final int code; + final String description; + + const CalendarType(this.code, this.description); +} diff --git a/lib/pages/common/selectDialog.dart b/lib/pages/common/selectDialog.dart index 3afcb13..b8f497c 100644 --- a/lib/pages/common/selectDialog.dart +++ b/lib/pages/common/selectDialog.dart @@ -5,9 +5,9 @@ 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/base/SleepCalendarWidget.dart'; -import 'package:vbvs_app/component/tool/TopSlideNotification.dart'; import 'package:vbvs_app/controller/device/device_calibration_controller.dart'; import 'package:vbvs_app/controller/theme_controller/ThemeController.dart'; +import 'package:vbvs_app/enum/CalendarType.dart'; getOnePicker(context, List arr, int checkIndex, Function onSelectedItemChanged, {bool looping = false}) { @@ -924,6 +924,7 @@ void showProgressDialog( void showSleepCalendarBottomSheet({ required BuildContext context, int? timestamp, + int? type = 1, // 新增参数,默认值为1 required void Function(DateTime selectedDate) onDateSelected, }) { showModalBottomSheet( @@ -941,7 +942,9 @@ void showSleepCalendarBottomSheet({ ), child: SleepCalendarWidget( timestamp: timestamp, - onDateSelected: onDateSelected, // 传递回调下去 + type: type, // 传递类型给子组件 + onDateSelected: onDateSelected, + // highlightColor: Colors.green, ), ); }, diff --git a/lib/pages/device/component/DeviceDataComponentWidget.dart b/lib/pages/device/component/DeviceDataComponentWidget.dart index c7e4a63..44592ff 100644 --- a/lib/pages/device/component/DeviceDataComponentWidget.dart +++ b/lib/pages/device/component/DeviceDataComponentWidget.dart @@ -13,7 +13,9 @@ 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/component/tool/CustomCard.dart'; +import 'package:vbvs_app/component/tool/ToggleColorContainer.dart'; import 'package:vbvs_app/component/tool/TopSlideNotification.dart'; +import 'package:vbvs_app/component/tool/cmd.dart'; import 'package:vbvs_app/controller/device/blueteeth_bind_controller.dart'; import 'package:vbvs_app/controller/device/body_device_controller.dart'; import 'package:vbvs_app/controller/person/person_controller.dart'; @@ -41,6 +43,9 @@ class _DeviceDataComponentWidgetState extends State { OverlayEntry? _popupEntry; BodyDeviceController bodyDeviceController = Get.find(); PersonController personController = Get.find(); + bool _isPopupOpen = false; + + var lisObj; @override void dispose() { @@ -48,7 +53,15 @@ class _DeviceDataComponentWidgetState extends State { super.dispose(); } + @override + void initState() { + super.initState(); + } + void _showPopup() { + setState(() { + _isPopupOpen = true; + }); final RenderBox? renderBox = _arrowKey.currentContext?.findRenderObject() as RenderBox?; if (renderBox == null) return; @@ -67,7 +80,7 @@ class _DeviceDataComponentWidgetState extends State { final textStyle = TextStyle( fontSize: AppConstants().normal_text_fontSize, - color: themeController.currentColor.sc3, // 你也可以传 sc2 + color: themeController.currentColor.sc3, ); final allTexts = [ @@ -126,11 +139,17 @@ class _DeviceDataComponentWidgetState extends State { popupLeft = screenWidth - popupWidth - paddingOffset; } + // setState(() { + // _isPopupOpen = false; + // }); _popupEntry?.remove(); _popupEntry = OverlayEntry( builder: (context) => GestureDetector( behavior: HitTestBehavior.translucent, onTap: () { + setState(() { + _isPopupOpen = false; + }); _popupEntry?.remove(); _popupEntry = null; }, @@ -222,6 +241,9 @@ class _DeviceDataComponentWidgetState extends State { : null, text: "体征检测设备.首页展示".tr, onTap: () async { + setState(() { + _isPopupOpen = false; + }); _popupEntry?.remove(); _popupEntry = null; ApiResponse apiResponse = @@ -245,6 +267,9 @@ class _DeviceDataComponentWidgetState extends State { _buildMenuItem( text: "体征检测设备.设备详情".tr, onTap: () { + setState(() { + _isPopupOpen = false; + }); _popupEntry?.remove(); _popupEntry = null; Get.toNamed("/deviceDetail", arguments: widget.device); @@ -254,18 +279,12 @@ class _DeviceDataComponentWidgetState extends State { if (widget.device['bind_type'] == BindType.active.code) { items.addAll([ - // _buildMenuItem( - // text: "WIFI配置".tr, - // onTap: () { - // _popupEntry?.remove(); - // _popupEntry = null; - // // Get.toNamed("/deviceDetail", arguments: widget.device); - // dealWifi(widget.device['mac']); - // }, - // ), _buildMenuItem( text: "WIFI配置".tr, onTap: () { + setState(() { + _isPopupOpen = false; + }); _popupEntry?.remove(); _popupEntry = null; dealWifi(widget.device['mac']); @@ -274,6 +293,9 @@ class _DeviceDataComponentWidgetState extends State { _buildMenuItem( text: "设备校准".tr, onTap: () { + setState(() { + _isPopupOpen = false; + }); _popupEntry?.remove(); _popupEntry = null; BlueteethBindController blueteethBindController = Get.find(); @@ -284,6 +306,9 @@ class _DeviceDataComponentWidgetState extends State { _buildMenuItem( text: "分享设备".tr, onTap: () { + setState(() { + _isPopupOpen = false; + }); _popupEntry?.remove(); _popupEntry = null; Get.toNamed("/deviceSharePage", arguments: widget.device); @@ -292,6 +317,9 @@ class _DeviceDataComponentWidgetState extends State { _buildMenuItem( text: "消息设置".tr, onTap: () { + setState(() { + _isPopupOpen = false; + }); _popupEntry?.remove(); _popupEntry = null; // Get.toNamed("/deviceDetail", arguments: widget.device); @@ -305,6 +333,9 @@ class _DeviceDataComponentWidgetState extends State { _buildMenuItem( text: "体征检测设备.重命名".tr, onTap: () { + setState(() { + _isPopupOpen = false; + }); _popupEntry?.remove(); _popupEntry = null; _showRenameDialog(); @@ -313,6 +344,9 @@ class _DeviceDataComponentWidgetState extends State { _buildMenuItem( text: "体征检测设备.删除".tr, onTap: () { + setState(() { + _isPopupOpen = false; + }); _popupEntry?.remove(); _popupEntry = null; _showUnbindConfirmDialog(); @@ -494,14 +528,37 @@ class _DeviceDataComponentWidgetState extends State { color: themeController.currentColor.sc3, ), ), - ClickableContainer( + // ClickableContainer( + // key: _arrowKey, + // padding: EdgeInsetsDirectional.fromSTEB( + // 16.rpx, 16.rpx, 14.rpx, 16.rpx), + // backgroundColor: Colors.transparent, + // highlightColor: Colors.black.withOpacity(0.1), + // borderRadius: 8.rpx, + // onTap: _showPopup, + // child: Container( + // width: 15.rpx, + // height: 8.rpx, + // child: SvgPicture.asset( + // 'assets/img/icon/arrow_down.svg', + // fit: BoxFit.cover, + // color: Colors.white, + // ), + // ), + // ), + ToggleColorContainer( key: _arrowKey, padding: EdgeInsetsDirectional.fromSTEB( - 16.rpx, 16.rpx, 14.rpx, 16.rpx), - backgroundColor: Colors.transparent, - highlightColor: Colors.black.withOpacity(0.1), + 16.rpx, + 16.rpx, + 14.rpx, + 16.rpx, + ), + initialColor: Colors.transparent, + toggledColor: Colors.black.withOpacity(0.5), borderRadius: 8.rpx, - onTap: _showPopup, + onToggle: _showPopup, + toggled: _isPopupOpen, child: Container( width: 15.rpx, height: 8.rpx, @@ -1167,6 +1224,9 @@ class _DeviceDataComponentWidgetState extends State { } Future dealWifi(String mac) async { + bodyDeviceController.wifiMac = mac; + Get.toNamed("/wifiPage", arguments: mac); + return; final blueteethBindController = Get.find(); final themeController = Get.find(); @@ -1179,18 +1239,24 @@ class _DeviceDataComponentWidgetState extends State { try { // 开始扫描蓝牙设备 - await FlutterBluePlus.startScan(timeout: Duration(seconds: 20)); + await FlutterBluePlus.startScan(timeout: Duration(seconds: 10)); // 设置超时(20秒) timeoutTimer = Timer(Duration(seconds: 20), () { - if (!isConnected) { - Navigator.of(Get.context!).pop(); // 关闭加载对话框 - TopSlideNotification.show( - Get.context!, - text: "设备连接超时,请重试".tr, - textColor: themeController.currentColor.sc9, - ); - FlutterBluePlus.stopScan(); + try { + if (!isConnected) { + Navigator.of(context).pop(); // 先关闭 dialog + WidgetsBinding.instance.addPostFrameCallback((_) { + TopSlideNotification.show( + context, + text: "设备连接超时,请重试".tr, + textColor: themeController.currentColor.sc9, + ); + }); + FlutterBluePlus.stopScan(); + } + } catch (e) { + print(e); } }); @@ -1198,23 +1264,20 @@ class _DeviceDataComponentWidgetState extends State { StreamSubscription>? scanSubscription; scanSubscription = FlutterBluePlus.scanResults.listen((results) async { // 过滤出符合条件的设备 - final targetDevice = results.firstWhere( - (r) { - try { - if (r.advertisementData.manufacturerData.containsKey(0xFFED)) { - List rawData = - r.advertisementData.manufacturerData[0xFFED]!; - BleDeviceData deviceData = parseBleData(rawData); - String deviceMac = - deviceData.deviceId.replaceAll(':', '').toLowerCase(); - return deviceMac == mac.toLowerCase(); - } - return false; - } catch (e) { - return false; + ScanResult? targetDevice; + + for (var r in results) { + if (r.advertisementData.manufacturerData.containsKey(0xFFED)) { + List rawData = r.advertisementData.manufacturerData[0xFFED]!; + BleDeviceData deviceData = parseBleData(rawData); + String deviceMac = + deviceData.deviceId.replaceAll(':', '').toLowerCase(); + if (deviceMac == mac.toLowerCase()) { + targetDevice = r; + break; } - }, - ); + } + } if (targetDevice != null && !isConnected) { isConnected = true; @@ -1236,8 +1299,40 @@ class _DeviceDataComponentWidgetState extends State { textColor: themeController.currentColor.sc2, ); blueteethBindController.currentDevice = bledevice; + if (lisObj != null) { + lisObj!.cancel(); + } + var aa; + lisObj = blueteethBindController.currentDevice!.statusStream + .listen((onData) async { + if (onData.status == BleEventType.recvLineLog) { + final line = onData.val; + print("[bleee]:" + line); + } + if (onData.status == BleEventType.ready) { + aa = await getDeviceNetVersion( + blueteethBindController.currentDevice!, 1); + if (aa == "4g") { + // TopSlideNotification.show( + // Get.context!, + // text: "4g设备配置wifi提示".tr, + // textColor: themeController.currentColor.sc9, + // ); + WidgetsBinding.instance.addPostFrameCallback((_) { + TopSlideNotification.show( + context, + text: "4g设备配置wifi提示".tr, + textColor: themeController.currentColor.sc9, + ); + }); + return; + } else { + Get.toNamed("/wifiPage", arguments: 2); + } + } + }); + // Get.toNamed("/wifiPage", arguments: {bledevice}); - Get.toNamed("/wifiPage", arguments: 1); } else { Navigator.pop(context); TopSlideNotification.show( @@ -1256,6 +1351,7 @@ class _DeviceDataComponentWidgetState extends State { } } }); + // 等待扫描完成 await Future.delayed(Duration(seconds: 20)); } catch (e) { diff --git a/lib/pages/device/instant_body_page.dart b/lib/pages/device/instant_body_page.dart index c198a59..31ce6ca 100644 --- a/lib/pages/device/instant_body_page.dart +++ b/lib/pages/device/instant_body_page.dart @@ -360,13 +360,7 @@ class _InstantBodyPageState extends State { padding: EdgeInsetsDirectional.fromSTEB( 66.rpx, 0, 66.rpx, 0), child: Container( - // decoration: BoxDecoration( - // image: DecorationImage( - // image: AssetImage( - // 'assets/img/body_black.gif'), // 本地图片 - // fit: BoxFit.cover, - // ), - // ), + decoration: BoxDecoration( image: DecorationImage( image: AssetImage( diff --git a/lib/pages/device_bind/bind_device_success.dart b/lib/pages/device_bind/bind_device_success.dart index c6b2449..5846a94 100644 --- a/lib/pages/device_bind/bind_device_success.dart +++ b/lib/pages/device_bind/bind_device_success.dart @@ -5,6 +5,7 @@ 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/component/tool/CustomCard.dart'; import 'package:vbvs_app/controller/device/blueteeth_bind_controller.dart'; import 'package:vbvs_app/controller/device/body_device_controller.dart'; @@ -71,9 +72,28 @@ class _EPageState extends State { ), /// 左边返回按钮 + // Positioned( + // left: 0, + // child: returnIconButtom, + // ), Positioned( - left: 0, - child: returnIconButtom, + left: 40.rpx, + child: ClickableContainer( + onTap: () { + Get.offAllNamed("/mianPageBottomChange"); + }, + backgroundColor: Colors.transparent, + highlightColor: Colors + .grey, // 可以设置为 themeController.currentColor.sc3 之类 + borderRadius: 8.rpx, + padding: EdgeInsets.all(0.rpx), // 增加可点击区域 + child: SvgPicture.asset( + 'assets/img/icon/close.svg', + width: 25.rpx, + height: 25.rpx, + color: themeController.currentColor.sc3, + ), + ), ), ], ), diff --git a/lib/pages/device_bind/blueteeth_device_page.dart b/lib/pages/device_bind/blueteeth_device_page.dart index 93007e3..544f0d7 100644 --- a/lib/pages/device_bind/blueteeth_device_page.dart +++ b/lib/pages/device_bind/blueteeth_device_page.dart @@ -8,6 +8,7 @@ import 'package:flutterflow_ui/flutterflow_ui.dart'; import 'package:permission_handler/permission_handler.dart'; // 引入permission_handler 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/device/blueteeth_bind_controller.dart'; import 'package:vbvs_app/controller/main_bottom/global_controller.dart'; import 'package:vbvs_app/controller/theme_controller/ThemeController.dart'; @@ -51,6 +52,8 @@ class _BlueteethDevicePageState extends State { flutterBlue = FlutterBluePlus(); // 初始化flutterBlue实例 _checkBluetoothPermission(); // 检查蓝牙权限 Get.find().startStatusPolling(); + blueteethBindController.search.value = ""; + blueteethBindController.currentDeviceMac?.value = ""; } // 检查蓝牙权限 @@ -131,15 +134,94 @@ class _BlueteethDevicePageState extends State { await FlutterBluePlus.startScan(timeout: Duration(seconds: 10)); // await FlutterBluePlus.startScan(timeout: Duration(minutes: 30)); + // _scanSubscription = FlutterBluePlus.scanResults.listen((results) { + // if (!mounted) return; // 确保页面未销毁 + // final signalThreshold = blueteethBindController.model.singal!; + // final filteredResults = results + // .where((r) => + // r.rssi > signalThreshold && + // r.advertisementData.localName == "AITH-V2" && + // r.advertisementData.manufacturerData.containsKey(0xFFED)) + // .toList(); + + // // 解析数据 + // final parsedDeviceList = []; + + // for (var r in filteredResults) { + // try { + // List rawData = r.advertisementData.manufacturerData[0xFFED]!; + // BleDeviceData deviceData = parseBleData(rawData); + // deviceData.name = r.advertisementData.advName; + // deviceData.rssi = r.rssi; + // deviceData.mac = deviceData.deviceId.replaceAll(':', ''); + // parsedDeviceList.add(deviceData); + // if (deviceData.mac!.toLowerCase() == 'b43a45c3dfa0') { + // print('匹配设备数据: ${deviceData.mac}-->sn:${deviceData.sn}'); + // } + // } catch (e) { + // print("设备数据解析失败: $e"); + // } + // } + + // // 使用一个临时变量 lastDeviceList 来比较是否有变化 + // setState(() { + // bool hasChanges = false; + + // // 如果 devicelist 长度不同或内容有差异,认为有变化 + // if (blueteethBindController.model.devicelist?.length != + // parsedDeviceList.length) { + // hasChanges = true; + // } else { + // // 深度比较每个设备的属性(比如 mac, rssi) + // for (int i = 0; + // i < blueteethBindController.model.devicelist!.length; + // i++) { + // if (blueteethBindController.model.devicelist![i].mac != + // parsedDeviceList[i].mac || + // blueteethBindController.model.devicelist![i].rssi != + // parsedDeviceList[i].rssi) { + // hasChanges = true; + // break; + // } + // } + // } + + // // 如果有变化,更新 devicelist 和 blelist + // if (hasChanges) { + // blueteethBindController.model.devicelist = parsedDeviceList; + // blueteethBindController.model.blelist = filteredResults; + // // blueteethBindController.updateDeviceStatus(); + // } + // }); + // }); + _scanSubscription = FlutterBluePlus.scanResults.listen((results) { - if (!mounted) return; // 确保页面未销毁 + if (!mounted) return; + final signalThreshold = blueteethBindController.model.singal!; - final filteredResults = results - .where((r) => - r.rssi > signalThreshold && - r.advertisementData.localName == "AITH-V2" && - r.advertisementData.manufacturerData.containsKey(0xFFED)) - .toList(); + final searchKey = + blueteethBindController.search.value.trim().toLowerCase(); + + final filteredResults = results.where((r) { + final isTarget = r.rssi > signalThreshold && + r.advertisementData.localName == "AITH-V2" && + r.advertisementData.manufacturerData.containsKey(0xFFED); + + if (!isTarget) return false; + + // 搜索关键字过滤(根据名称和 MAC 地址模糊匹配) + final name = r.advertisementData.advName.toLowerCase(); + final mac = r.device.remoteId.str.replaceAll(':', '').toLowerCase(); + final search = searchKey.trim().replaceAll(':', '').toLowerCase(); + + if (search.isNotEmpty && + !name.contains(search) && + !mac.contains(search)) { + return false; + } + + return true; + }).toList(); // 解析数据 final parsedDeviceList = []; @@ -152,24 +234,23 @@ class _BlueteethDevicePageState extends State { deviceData.rssi = r.rssi; deviceData.mac = deviceData.deviceId.replaceAll(':', ''); parsedDeviceList.add(deviceData); - if (deviceData.mac!.toLowerCase() == 'b43a45c3dfa0') { - print('匹配设备数据: ${deviceData.mac}-->sn:${deviceData.sn}'); - } + + // if (deviceData.mac!.toLowerCase() == 'b43a45c3dfa0') { + // print('匹配设备数据: ${deviceData.mac}-->sn:${deviceData.sn}'); + // } } catch (e) { print("设备数据解析失败: $e"); } } - // 使用一个临时变量 lastDeviceList 来比较是否有变化 + // 判断是否更新 setState(() { bool hasChanges = false; - // 如果 devicelist 长度不同或内容有差异,认为有变化 if (blueteethBindController.model.devicelist?.length != parsedDeviceList.length) { hasChanges = true; } else { - // 深度比较每个设备的属性(比如 mac, rssi) for (int i = 0; i < blueteethBindController.model.devicelist!.length; i++) { @@ -183,11 +264,9 @@ class _BlueteethDevicePageState extends State { } } - // 如果有变化,更新 devicelist 和 blelist if (hasChanges) { blueteethBindController.model.devicelist = parsedDeviceList; blueteethBindController.model.blelist = filteredResults; - // blueteethBindController.updateDeviceStatus(); } }); }); @@ -360,6 +439,7 @@ class _BlueteethDevicePageState extends State { newValue.toStringAsFixed(0)); blueteethBindController.model.singal = newValue; + _startScanning(); blueteethBindController.updateAll(); }, ); @@ -425,6 +505,12 @@ class _BlueteethDevicePageState extends State { child: Align( alignment: AlignmentDirectional(-1, 0), child: TextFormField( + initialValue: blueteethBindController + .search.value, + onChanged: (Value) { + blueteethBindController + .search.value = Value; + }, autofocus: false, obscureText: false, decoration: InputDecoration( @@ -494,11 +580,8 @@ class _BlueteethDevicePageState extends State { fontSize: 26.rpx, letterSpacing: 0.0, ), - cursorColor: themeController - .currentColor.sc3, - // validator: _model - // .textControllerValidator - // .asValidator(context), + cursorColor: + themeController.currentColor.sc3, ), ), ), @@ -519,16 +602,60 @@ class _BlueteethDevicePageState extends State { color: stringToColor("#333333"), //固定 ), ), - Text( - '搜索'.tr, - style: FlutterFlowTheme.of(context) - .bodyMedium - .override( - fontFamily: 'Inter', - fontSize: 30.rpx, - letterSpacing: 0.0, - color: stringToColor("#333333"), //固定 - ), + ClickableContainer( + backgroundColor: Colors.transparent, + highlightColor: + themeController.currentColor.sc4, + borderRadius: 6.rpx, + padding: EdgeInsets.zero, + // onTap: () async { + + // blueteethBindController + // .model.betDevicelist; + // blueteethBindController.search.value; + // blueteethBindController.updateAll(); + // }, + onTap: () async { + // final searchKey = blueteethBindController + // .search.value + // .trim(); + // if (searchKey.isNotEmpty) { + // final filtered = blueteethBindController + // .model.betDevicelist! + // .where((device) { + // final name = + // device.name?.toLowerCase() ?? ''; + // final mac = + // device.mac?.toLowerCase() ?? ''; + // return name.contains( + // searchKey.toLowerCase()) || + // mac.contains( + // searchKey.toLowerCase()); + // }).toList(); + + // // 替换原始列表 + // blueteethBindController + // .model.betDevicelist! + // ..clear() + // ..addAll(filtered); + // } + + // blueteethBindController.updateAll(); + _startScanning(); + }, + + child: Text( + '搜索'.tr, + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Inter', + fontSize: 30.rpx, + letterSpacing: 0.0, + color: + stringToColor("#333333"), //固定 + ), + ), ), ].divide(SizedBox(width: 26.rpx)), ), @@ -548,8 +675,10 @@ class _BlueteethDevicePageState extends State { EdgeInsetsDirectional.fromSTEB(19.rpx, 0, 0, 0), child: Obx(() { return Text( + // '匹配出的外围设备'.tr + + // "(${blueteethBindController.model.betDevicelist!.length})", '匹配出的外围设备'.tr + - "(${blueteethBindController.model.betDevicelist!.length})", + "(${blueteethBindController.model.blelist!.length})", style: FlutterFlowTheme.of(context) .bodyMedium .override( diff --git a/lib/pages/device_bind/componnet/SingleBlueteethDeviceCompoentWidget.dart b/lib/pages/device_bind/componnet/SingleBlueteethDeviceCompoentWidget.dart index fef14a4..eda48ff 100644 --- a/lib/pages/device_bind/componnet/SingleBlueteethDeviceCompoentWidget.dart +++ b/lib/pages/device_bind/componnet/SingleBlueteethDeviceCompoentWidget.dart @@ -8,6 +8,7 @@ import 'package:vbvs_app/common/util/DailyLogUtils.dart'; import 'package:vbvs_app/common/util/FitTool.dart'; import 'package:vbvs_app/component/tool/ClickableContainer.dart'; import 'package:vbvs_app/component/tool/TopSlideNotification.dart'; +import 'package:vbvs_app/component/tool/cmd.dart'; import 'package:vbvs_app/controller/device/blueteeth_bind_controller.dart'; import 'package:vbvs_app/controller/theme_controller/ThemeController.dart'; import 'package:vbvs_app/model/BleDeviceData.dart'; @@ -32,6 +33,8 @@ class _SingleBlueteethDeviceCompoentWidgetState extends State { ThemeController themeController = Get.find(); BlueteethBindController blueteethBindController = Get.find(); + var lisObj; + @override Widget build(BuildContext context) { List rawData = @@ -41,12 +44,11 @@ class _SingleBlueteethDeviceCompoentWidgetState deviceData.rssi = widget.bleDevice.rssi; deviceData.mac = deviceData.deviceId.replaceAll(':', ''); BleDeviceData device = deviceData; - blueteethBindController.currentDeviceMac = device.mac; + // blueteethBindController.currentDeviceMac = device.mac; device = blueteethBindController.model.betDevicelist!.firstWhere( (d) => d.mac == device.mac, orElse: () => device, ); - return ClickableContainer( backgroundColor: themeController.currentColor.sc5, highlightColor: themeController.currentColor.sc21, @@ -54,9 +56,25 @@ class _SingleBlueteethDeviceCompoentWidgetState padding: EdgeInsetsDirectional.fromSTEB(30.rpx, 36.rpx, 0, 52.rpx), onTap: () async { try { + //1.先判斷当前是否有别的设备处于连接中,没有处于连接,判断是否绑定; + //2.如果没有绑定,直接连接;如果绑定,弹出提示框,提示是否解绑; + if (blueteethBindController.currentDeviceMac?.value != null && + blueteethBindController.currentDeviceMac!.value.isNotEmpty) { + if (blueteethBindController.currentDeviceMac?.value != device.mac) { + showConfirmDialog( + context, Container(), "其他设备正在绑定中,是否终止其他设备绑定?".tr, + onConfirm: () { + blueteethBindController.currentDeviceMac = null; + blueteethBindController.updateAll(); + }, onCancel: () {}); + } + } else { + blueteethBindController.currentDeviceMac?.value = device.mac!; + blueteethBindController.updateAll(); + } + if (device.bind == true) { showHaveBindDialog(context); - // showBindDoubleDialog(context, []); } else { showConfirmDialog( context, @@ -67,29 +85,52 @@ class _SingleBlueteethDeviceCompoentWidgetState await blueteethBindController.bindDeviceAndMAC(device); TopSlideNotification.show(context, text: response.msg!); if (response.code == HttpStatusCodes.ok) { - showLoadingDialog(context); // 显示 loading + // showLoadingDialog(context); // 显示 loading + Get.toNamed("/personPage"); THapp bledevice = THapp(device: widget.bleDevice.device); - await bledevice.device.connect(); - var res2 = bledevice.isConnected; - if (res2) { - Navigator.pop(context); - TopSlideNotification.show( - context, - text: "蓝牙绑定.连接成功".tr, - textColor: themeController.currentColor.sc2, - ); - blueteethBindController.currentDevice = bledevice; - // Get.toNamed("/wifiPage", arguments: {bledevice}); - Get.toNamed("/wifiPage"); - } else { - Navigator.pop(context); - TopSlideNotification.show( - context, - text: "蓝牙绑定.连接失败".tr, - textColor: themeController.currentColor.sc9, - ); - } + blueteethBindController.currentDevice = bledevice; + // await bledevice.device.connect(); + // var res2 = bledevice.isConnected; + // if (res2) { + // // Navigator.pop(context); + // TopSlideNotification.show( + // context, + // text: "蓝牙绑定.连接成功".tr, + // textColor: themeController.currentColor.sc2, + // ); + // blueteethBindController.currentDevice = bledevice; + + // if (lisObj != null) { + // lisObj!.cancel(); + // } + // var aa; + // lisObj = blueteethBindController.currentDevice!.statusStream + // .listen((onData) async { + // if (onData.status == BleEventType.recvLineLog) { + // final line = onData.val; + // print("[bleee]:" + line); + // } + // if (onData.status == BleEventType.ready) { + // aa = await getDeviceNetVersion( + // blueteethBindController.currentDevice!, 1); + // if (aa == "4g") { + // Get.toNamed("/calibrationPage", arguments: 1); + // } else { + // Get.toNamed("/wifiPage"); + // } + // } + // }); + // } else { + // // Navigator.pop(context); + // TopSlideNotification.show( + // context, + // text: "蓝牙绑定.连接失败".tr, + // textColor: themeController.currentColor.sc9, + // ); + // } } else { + blueteethBindController.currentDeviceMac = null; + blueteethBindController.updateAll(); TopSlideNotification.show( context, text: response.msg ?? "蓝牙绑定.连接异常".tr, @@ -99,10 +140,10 @@ class _SingleBlueteethDeviceCompoentWidgetState }, onCancel: () { print('用户点击了取消'); - // 执行取消后的处理逻辑 + blueteethBindController.currentDeviceMac?.value = ""; + blueteethBindController.updateAll(); }, ); - } } catch (e) { Navigator.pop(context); @@ -119,14 +160,36 @@ class _SingleBlueteethDeviceCompoentWidgetState crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.max, children: [ - Text( - device.name ?? '蓝牙绑定.默认设备名称'.tr, - style: FlutterFlowTheme.of(context).bodyMedium.override( - fontFamily: 'Inter', - color: const Color(0xFFF6FAFD), - fontSize: 30.rpx, - letterSpacing: 0.0, + Padding( + padding: + EdgeInsetsDirectional.fromSTEB(0.rpx, 0.rpx, 30.rpx, 0.rpx), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + device.name ?? '蓝牙绑定.默认设备名称'.tr, + style: FlutterFlowTheme.of(context).bodyMedium.override( + fontFamily: 'Inter', + color: const Color(0xFFF6FAFD), + fontSize: 30.rpx, + letterSpacing: 0.0, + ), ), + Obx(() { + if (blueteethBindController.currentDeviceMac == device.mac) { + return SizedBox( + width: 24.rpx, + height: 24.rpx, + child: CircularProgressIndicator( + strokeWidth: 1, + valueColor: AlwaysStoppedAnimation(Colors.white), + ), + ); + } + return Container(); + }), + ], + ), ), Row( children: [ diff --git a/lib/pages/device_bind/componnet/bind_dialog.dart b/lib/pages/device_bind/componnet/bind_dialog.dart index a3b1d47..b8ef3bb 100644 --- a/lib/pages/device_bind/componnet/bind_dialog.dart +++ b/lib/pages/device_bind/componnet/bind_dialog.dart @@ -426,7 +426,7 @@ void showConfirmDialog( color: themeController.currentColor.sc17, borderRadius: BorderRadius.circular(20.0), ), - padding: EdgeInsetsDirectional.fromSTEB(64.rpx, 0, 64.rpx, 0), + padding: EdgeInsetsDirectional.fromSTEB(31.rpx, 0, 31.rpx, 0), child: Container( width: double.infinity, constraints: BoxConstraints( @@ -446,10 +446,13 @@ void showConfirmDialog( padding: EdgeInsets.zero, // 这里去掉外部的 padding,避免影响点击范围 onTap: () { Get.back(); + onCancel(); }, child: Padding( - padding: - EdgeInsetsDirectional.fromSTEB(0, 33.rpx, 0, 0.rpx), + // padding: + // EdgeInsetsDirectional.fromSTEB(0, 33.rpx, 0, 0.rpx), + padding: EdgeInsetsDirectional.fromSTEB( + 33.rpx, 33.rpx, 33.rpx, 33.rpx), child: SvgPicture.asset( 'assets/img/icon/close.svg', width: 25.rpx, @@ -463,8 +466,8 @@ void showConfirmDialog( Align( alignment: AlignmentDirectional(0, 0), child: Padding( - padding: - EdgeInsetsDirectional.fromSTEB(0.rpx, 93.rpx, 0, 0), + padding: EdgeInsetsDirectional.fromSTEB( + 33.rpx, 60.rpx, 33.rpx, 33.rpx), child: Text( title, style: FlutterFlowTheme.of(context).bodyMedium.override( @@ -476,18 +479,22 @@ void showConfirmDialog( ), ), ), - widget, Padding( - padding: EdgeInsetsDirectional.fromSTEB(0, 58.rpx, 0, 60.rpx), + padding: EdgeInsetsDirectional.fromSTEB( + 33.rpx, 0.rpx, 33.rpx, 0.rpx), + child: widget, + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 33.rpx, 58.rpx, 33.rpx, 60.rpx), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ CustomCard( borderRadius: AppConstants().button_container_radius, onTap: () async { - onConfirm(); - // await Future.delayed(Duration(milliseconds: 300)); Get.back(); + onCancel(); }, colors: [ themeController.currentColor.sc1, @@ -505,7 +512,7 @@ void showConfirmDialog( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( - "蓝牙绑定.是".tr, + "蓝牙绑定.否".tr, style: FlutterFlowTheme.of(context) .bodyMedium .override( @@ -523,8 +530,9 @@ void showConfirmDialog( CustomCard( borderRadius: AppConstants().button_container_radius, onTap: () { + onConfirm(); + // await Future.delayed(Duration(milliseconds: 300)); Get.back(); - onCancel(); }, colors: [ themeController.currentColor.sc1, @@ -542,7 +550,7 @@ void showConfirmDialog( mainAxisAlignment: MainAxisAlignment.center, children: [ Text( - "蓝牙绑定.否".tr, + "蓝牙绑定.是".tr, style: FlutterFlowTheme.of(context) .bodyMedium .override( @@ -660,8 +668,8 @@ void showWifiDialog( textColor: themeController.currentColor.sc9, ); } else { - Get.back(); onConfirm(); + Get.back(); } }, colors: [ @@ -788,7 +796,6 @@ void showTipDialog( child: widget, ), ), - // widget, Padding( padding: EdgeInsetsDirectional.fromSTEB(0, 19.rpx, 0, 60.rpx), diff --git a/lib/pages/device_bind/device_calibration.dart b/lib/pages/device_bind/device_calibration.dart index d4c140c..e87f88e 100644 --- a/lib/pages/device_bind/device_calibration.dart +++ b/lib/pages/device_bind/device_calibration.dart @@ -89,7 +89,8 @@ class _CalibrationPageState extends State { child: CustomCard( borderRadius: 20.rpx, onTap: () async { - Get.toNamed("/personPage"); + // Get.toNamed("/personPage"); + Get.toNamed("/bindDeviceSuccess"); }, colors: [ themeController.currentColor.sc1, diff --git a/lib/pages/device_bind/device_share_page.dart b/lib/pages/device_bind/device_share_page.dart index 620a9a3..ad1bcf4 100644 --- a/lib/pages/device_bind/device_share_page.dart +++ b/lib/pages/device_bind/device_share_page.dart @@ -383,7 +383,7 @@ class _DeviceSharePageState extends State { // "太和e护分享链接", // scene: WeChatScene.session)); final Uint8List data = await rootBundle.load('assets/img/camera.png').then((bd) => bd.buffer.asUint8List()); - loginController.fluwx.share(WeChatShareWebPageModel("https://www.baidu.com",title: "标题",description: "描述",thumbData: data)); + loginController.fluwx.share(WeChatShareWebPageModel("taihecare://goods?id=123",title: "标题",description: "描述",thumbData: data)); }, colors: [ // 渐变色 diff --git a/lib/pages/device_bind/wifi_page.dart b/lib/pages/device_bind/wifi_page.dart index 3a3f105..38326dc 100644 --- a/lib/pages/device_bind/wifi_page.dart +++ b/lib/pages/device_bind/wifi_page.dart @@ -1,6 +1,9 @@ +import 'dart:async'; + import 'package:easydevice/easydevice.dart'; import 'package:ef/ef.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_blue_plus/flutter_blue_plus.dart'; import 'package:flutter_svg/svg.dart'; import 'package:flutterflow_ui/flutterflow_ui.dart'; import 'package:vbvs_app/common/color/appConstants.dart'; @@ -15,6 +18,8 @@ import 'package:vbvs_app/controller/main_bottom/global_controller.dart'; import 'package:vbvs_app/controller/person/person_controller.dart'; import 'package:vbvs_app/controller/theme_controller/ThemeController.dart'; import 'package:vbvs_app/controller/user_info_controller.dart'; +import 'package:vbvs_app/model/BleDeviceData.dart'; +import 'package:vbvs_app/pages/device_bind/blueteeth_device_page.dart'; import 'package:vbvs_app/pages/device_bind/componnet/bind_dialog.dart'; class WifiPage extends StatefulWidget { @@ -36,10 +41,69 @@ class _WifiPageState extends State { @override void initState() { super.initState(); + blueteethBindController.connectStatus.value = 0; blueteethBindController.wifiList = [].obs; blueteethBindController.wifiStatus = 0.obs; blueteethBindController.connect_wifi.value = {}; - initWifiStatusAndWifiList(); + blueteethBindController.selectWifi.value = {}; + + if (widget.type == null) { + THapp bledevice = blueteethBindController.currentDevice!; + bledevice.device.connect().then((Value) { + var res2 = bledevice.isConnected; + if (res2) { + // WidgetsBinding.instance.addPostFrameCallback((_) { + // TopSlideNotification.show( + // context, + // text: "蓝牙绑定.连接成功".tr, + // textColor: themeController.currentColor.sc2, + // ); + // }); + blueteethBindController.currentDevice = bledevice; + if (lisObj != null) { + lisObj!.cancel(); + } + var aa; + lisObj = blueteethBindController.currentDevice!.statusStream + .listen((onData) async { + if (onData.status == BleEventType.recvLineLog) { + final line = onData.val; + print("[bleee]:" + line); + } + if (onData.status == BleEventType.ready) { + aa = await getDeviceNetVersion( + blueteethBindController.currentDevice!, 1); + if (aa == "4g") { + TopSlideNotification.show( + context, + text: "4g设备配置wifi提示".tr, + textColor: themeController.currentColor.sc2, + ); + blueteethBindController.connectStatus.value = 1; + blueteethBindController.updateAll(); + Future.delayed(const Duration(seconds: 1), () { + Get.toNamed("/calibrationPage", arguments: 1); + }); + } else { + await initWifiStatusAndWifiList(); + } + } + }); + } else { + WidgetsBinding.instance.addPostFrameCallback((_) { + TopSlideNotification.show( + context, + text: "连接失败".tr, + textColor: themeController.currentColor.sc9, + ); + }); + } + }); + } else { + dealWifi(widget.type).then((aa) { + print("object"); + }); + } } @override @@ -79,14 +143,38 @@ class _WifiPageState extends State { alignment: Alignment.center, children: [ /// 居中标题 - Text( - 'wifi页.标题'.tr, - style: FlutterFlowTheme.of(context).bodyMedium.override( - fontFamily: 'Readex Pro', - color: themeController.currentColor.sc3, - letterSpacing: 0, - fontSize: 30.rpx, - ), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Text( + 'wifi页.标题'.tr, + style: + FlutterFlowTheme.of(context).bodyMedium.override( + fontFamily: 'Readex Pro', + color: themeController.currentColor.sc3, + letterSpacing: 0, + fontSize: 30.rpx, + ), + ), + SizedBox( + width: 14.rpx, + ), + Obx(() { + if (blueteethBindController.connectStatus.value == + 0) { + return SizedBox( + width: 24.rpx, + height: 24.rpx, + child: CircularProgressIndicator( + strokeWidth: 1, + valueColor: + AlwaysStoppedAnimation(Colors.white), + ), + ); + } + return Container(); + }), + ], ), /// 左边返回按钮 @@ -100,15 +188,16 @@ class _WifiPageState extends State { child: CustomCard( borderRadius: 20.rpx, onTap: () async { - if (blueteethBindController.wifiStatus.value != 1) { - TopSlideNotification.show( - context, - text: "wifi页.需配网".tr, - textColor: themeController.currentColor.sc9, - ); - } else { - Get.toNamed("/calibrationPage", arguments: 1); - } + // if (blueteethBindController.wifiStatus.value != 1) { + // TopSlideNotification.show( + // context, + // text: "wifi页.需配网".tr, + // textColor: themeController.currentColor.sc9, + // ); + // } else { + // Get.toNamed("/calibrationPage", arguments: 1); + // } + Get.toNamed("/calibrationPage", arguments: 1); }, colors: [ themeController.currentColor.sc1, @@ -460,8 +549,11 @@ class _WifiPageState extends State { wifiItem['ssid'] ?? '未命名'.tr, onConfirm: () async { - showLoadingDialog( - context); // 显示 loading + // showLoadingDialog( + // context); // 显示 loading + blueteethBindController + .selectWifi + .value = wifiItem; bool flag = await sendWifiSetting( wifiItem, blueteethBindController @@ -478,7 +570,10 @@ class _WifiPageState extends State { var aa = await getDeviceWifiStatus( blueteethBindController .currentDevice!, - 20); + 1); + blueteethBindController + .selectWifi + .value = {}; if (aa != null && aa is Map) { wifiStatus = true; @@ -503,7 +598,7 @@ class _WifiPageState extends State { ? 1 : 0; if (wifiStatus) { - Navigator.pop(context); + // Navigator.pop(context); TopSlideNotification .show( context, @@ -519,7 +614,7 @@ class _WifiPageState extends State { blueteethBindController .updateAll(); } else { - Navigator.pop(context); + // Navigator.pop(context); TopSlideNotification .show( context, @@ -536,7 +631,7 @@ class _WifiPageState extends State { .updateAll(); } } else { - Navigator.pop(context); + // Navigator.pop(context); TopSlideNotification.show( context, text: "wifi页.配网失败".tr, @@ -574,7 +669,24 @@ class _WifiPageState extends State { .sc3, ), ), - getWifiIconByRsso(wifiItem), + if (blueteethBindController + .selectWifi.value == + wifiItem) + SizedBox( + width: 32.rpx, + height: 32.rpx, + child: + CircularProgressIndicator( + strokeWidth: 1, + valueColor: + AlwaysStoppedAnimation< + Color>( + Colors.white), + ), + ) + else + getWifiIconByRsso( + wifiItem), ], ), )) @@ -589,6 +701,9 @@ class _WifiPageState extends State { horizontal: 20.rpx, vertical: 10.rpx), borderRadius: 20.rpx, onTap: () async { + blueteethBindController + .connectStatus.value = 0; + blueteethBindController.updateAll(); print("点击刷新"); await initWifiList(); TopSlideNotification.show( @@ -645,93 +760,92 @@ class _WifiPageState extends State { ); } - Widget _buildDeviceCard(BuildContext context, - {required String title, required String imageUrl, required String type}) { - return CustomCard( - borderRadius: 20.rpx, // 圆角大小 - onTap: () { - if (type != null) { - if (type == '1') { - Get.toNamed("/blueteethDevice"); - } - } - }, - colors: [themeController.currentColor.sc17], // 背景色 - - child: Container( - width: double.infinity, - height: MediaQuery.sizeOf(context).height * 0.135, - constraints: BoxConstraints( - minHeight: 220.rpx, - ), - padding: EdgeInsetsDirectional.fromSTEB(77.rpx, 0, 21.rpx, 0), - child: Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text( - title, - style: FlutterFlowTheme.of(context).bodyMedium.override( - fontFamily: 'Inter', - color: const Color(0xFFC2CED7), - fontSize: 30.rpx, - letterSpacing: 0.0, - ), - ), - ClipRRect( - borderRadius: BorderRadius.circular(8.rpx), - child: Image.asset( - imageUrl, - width: 212.rpx, - height: 168.rpx, - ), - ), - ], - ), - ), - ); - } - - void initWifiStatusAndWifiList() { + Future initWifiStatusAndWifiList() async { if (lisObj != null) { lisObj!.cancel(); } + bool wifiStatus = false; + var aa = + await getDeviceWifiStatus(blueteethBindController.currentDevice!, 1); + if (aa != null && aa is Map) { + wifiStatus = true; + blueteethBindController.connect_wifi.value = aa; + } + blueteethBindController.wifiStatus.value = wifiStatus == true ? 1 : 0; + List wifiList = []; + try { + final result = await getWifiList(blueteethBindController.currentDevice!); + if (result is List) { + wifiList = result; + } + } catch (e) { + print("异常: $e"); + } + if (wifiList.length > 0) { + blueteethBindController.connectStatus.value = 1; + blueteethBindController.updateAll(); + // Navigator.pop(context); + TopSlideNotification.show( + context, + text: "获取wifi列表成功".tr, + textColor: themeController.currentColor.sc2, + ); + blueteethBindController.wifiList.value = wifiList; + blueteethBindController.updateAll(); + } else { + // Navigator.pop(context); + TopSlideNotification.show( + context, + text: "获取wifi列表失败".tr, + textColor: themeController.currentColor.sc9, + ); + } lisObj = blueteethBindController.currentDevice!.statusStream .listen((onData) async { if (onData.status == BleEventType.recvLineLog) { final line = onData.val; print("[bleee]:" + line); } - if (onData.status == BleEventType.ready) { - showLoadingDialog(context, title: "获取wifi列表中...".tr); - bool wifiStatus = false; - var aa = await getDeviceWifiStatus( - blueteethBindController.currentDevice!, 10); - if (aa != null && aa is Map) { - wifiStatus = true; - blueteethBindController.connect_wifi.value = aa; - } - blueteethBindController.wifiStatus.value = wifiStatus == true ? 1 : 0; - List wifiList = - await getWifiList(blueteethBindController.currentDevice!); - if (wifiList.length > 0) { - Navigator.pop(context); - TopSlideNotification.show( - context, - text: "获取wifi列表成功".tr, - textColor: themeController.currentColor.sc2, - ); - blueteethBindController.wifiList.value = wifiList; - blueteethBindController.updateAll(); - } else { - Navigator.pop(context); - TopSlideNotification.show( - context, - text: "获取wifi列表失败".tr, - textColor: themeController.currentColor.sc9, - ); - } - } + // if (onData.status == BleEventType.ready) { + // // showLoadingDialog(context, title: "获取wifi列表中...".tr); + // bool wifiStatus = false; + // var aa = await getDeviceWifiStatus( + // blueteethBindController.currentDevice!, 1); + // if (aa != null && aa is Map) { + // wifiStatus = true; + // blueteethBindController.connect_wifi.value = aa; + // } + // blueteethBindController.wifiStatus.value = wifiStatus == true ? 1 : 0; + // List wifiList = []; + // try { + // final result = + // await getWifiList(blueteethBindController.currentDevice!); + // if (result is List) { + // wifiList = result; + // } + // } catch (e) { + // print("异常: $e"); + // } + // if (wifiList.length > 0) { + // blueteethBindController.connectStatus.value = 1; + // blueteethBindController.updateAll(); + // // Navigator.pop(context); + // TopSlideNotification.show( + // context, + // text: "获取wifi列表成功".tr, + // textColor: themeController.currentColor.sc2, + // ); + // blueteethBindController.wifiList.value = wifiList; + // blueteethBindController.updateAll(); + // } else { + // Navigator.pop(context); + // TopSlideNotification.show( + // context, + // text: "获取wifi列表失败".tr, + // textColor: themeController.currentColor.sc9, + // ); + // } + // } }); } @@ -740,6 +854,8 @@ class _WifiPageState extends State { var wifiList = await getWifiList(blueteethBindController.currentDevice!); print(wifiList); if (wifiList.length > 0) { + blueteethBindController.connectStatus.value = 1; + blueteethBindController.wifiList.value = wifiList; blueteethBindController.updateAll(); } @@ -802,4 +918,150 @@ class _WifiPageState extends State { ); } } + + Future dealWifi(String mac) async { + // bodyDeviceController.wifiMac = mac; + // Get.toNamed("/wifiPage", arguments: 2); + // return; + final blueteethBindController = Get.find(); + final themeController = Get.find(); + + // 显示加载对话框 + // showLoadingDialog(Get.context!, title: "连接中...".tr); + + // 设置超时定时器 + Timer? timeoutTimer; + bool isConnected = false; + + try { + // 开始扫描蓝牙设备 + await FlutterBluePlus.startScan(timeout: Duration(seconds: 10)); + + // 设置超时(20秒) + timeoutTimer = Timer(Duration(seconds: 20), () { + try { + if (!isConnected) { + // Navigator.of(context).pop(); // 先关闭 dialog + WidgetsBinding.instance.addPostFrameCallback((_) { + TopSlideNotification.show( + context, + text: "设备连接超时,请重试".tr, + textColor: themeController.currentColor.sc9, + ); + }); + FlutterBluePlus.stopScan(); + } + } catch (e) { + print(e); + } + }); + + // 监听扫描结果 + StreamSubscription>? scanSubscription; + scanSubscription = FlutterBluePlus.scanResults.listen((results) async { + // 过滤出符合条件的设备 + ScanResult? targetDevice; + + for (var r in results) { + if (r.advertisementData.manufacturerData.containsKey(0xFFED)) { + List rawData = r.advertisementData.manufacturerData[0xFFED]!; + BleDeviceData deviceData = parseBleData(rawData); + String deviceMac = + deviceData.deviceId.replaceAll(':', '').toLowerCase(); + if (deviceMac == mac.toLowerCase()) { + targetDevice = r; + break; + } + } + } + + if (targetDevice != null && !isConnected) { + isConnected = true; + FlutterBluePlus.stopScan(); + scanSubscription?.cancel(); + timeoutTimer?.cancel(); + + try { + // 连接设备 + // await targetDevice.device.connect(); + THapp bledevice = THapp(device: targetDevice.device); + await bledevice.device.connect(); + var res2 = bledevice.isConnected; + if (res2) { + // Navigator.pop(context); + TopSlideNotification.show( + context, + text: "蓝牙绑定.连接成功".tr, + textColor: themeController.currentColor.sc2, + ); + blueteethBindController.currentDevice = bledevice; + if (lisObj != null) { + lisObj!.cancel(); + } + var aa; + lisObj = blueteethBindController.currentDevice!.statusStream + .listen((onData) async { + if (onData.status == BleEventType.recvLineLog) { + final line = onData.val; + print("[bleee]:" + line); + } + if (onData.status == BleEventType.ready) { + aa = await getDeviceNetVersion( + blueteethBindController.currentDevice!, 1); + if (aa == "4g") { + // TopSlideNotification.show( + // Get.context!, + // text: "4g设备配置wifi提示".tr, + // textColor: themeController.currentColor.sc9, + // ); + WidgetsBinding.instance.addPostFrameCallback((_) { + TopSlideNotification.show( + context, + text: "4g设备配置wifi提示".tr, + textColor: themeController.currentColor.sc9, + ); + }); + return; + } else { + // Get.toNamed("/wifiPage", arguments: 2); + await initWifiStatusAndWifiList(); + } + } + }); + + // Get.toNamed("/wifiPage", arguments: {bledevice}); + } else { + // Navigator.pop(context); + TopSlideNotification.show( + context, + text: "蓝牙绑定.连接失败".tr, + textColor: themeController.currentColor.sc9, + ); + } + } catch (e) { + // Navigator.of(Get.context!).pop(); // 关闭加载对话框 + TopSlideNotification.show( + Get.context!, + text: "设备连接失败".tr, + textColor: themeController.currentColor.sc9, + ); + } + } + }); + + // 等待扫描完成 + await Future.delayed(Duration(seconds: 20)); + } catch (e) { + timeoutTimer?.cancel(); + Navigator.of(Get.context!).pop(); // 关闭加载对话框 + TopSlideNotification.show( + Get.context!, + text: "扫描过程中发生错误".tr, + textColor: themeController.currentColor.sc9, + ); + } finally { + timeoutTimer?.cancel(); + await FlutterBluePlus.stopScan(); + } + } } diff --git a/lib/pages/main_bottom/mine_page.dart b/lib/pages/main_bottom/mine_page.dart index d8e63f9..7da451c 100644 --- a/lib/pages/main_bottom/mine_page.dart +++ b/lib/pages/main_bottom/mine_page.dart @@ -10,6 +10,7 @@ import 'package:vbvs_app/component/tool/ClickableContainer.dart'; import 'package:vbvs_app/component/tool/CustomCard.dart'; import 'package:vbvs_app/component/tool/TopSlideNotification.dart'; import 'package:vbvs_app/controller/date/CalendarController.dart'; +import 'package:vbvs_app/controller/login/login_controller.dart'; import 'package:vbvs_app/controller/main_bottom/global_controller.dart'; import 'package:vbvs_app/controller/theme_controller/ThemeController.dart'; import 'package:vbvs_app/controller/user_info_controller.dart'; @@ -27,6 +28,7 @@ class _MinePageState extends State { UserInfoController userInfoController = Get.find(); ThemeController themeController = Get.find(); CalendarController calendarController = Get.find(); + LoginController loginController = Get.find(); @override Widget build(BuildContext context) { @@ -80,41 +82,43 @@ class _MinePageState extends State { mainAxisSize: MainAxisSize.max, mainAxisAlignment: MainAxisAlignment.end, children: [ - ClickableContainer( - backgroundColor: - Colors.transparent, // 容器背景色 - highlightColor: themeController - .currentColor.sc21, // 点击时的背景色 - padding: EdgeInsets - .zero, // 这里去掉外部的 padding,避免影响点击范围 - onTap: () { - if (userInfoController.model.login == - LoginStatus.LOGIN.code) { - TopSlideNotification.show( - context, - text: "待开发功能".tr, - ); - } else { - TopSlideNotification.show( - context, - text: "必须登录提示".tr, - textColor: - themeController.currentColor.sc9, - ); - Get.toNamed("/loginPage"); - } - }, - child: Padding( - padding: EdgeInsetsDirectional.fromSTEB( - 0.rpx, 0.rpx, 0.rpx, 0.rpx), - child: SvgPicture.asset( - 'assets/img/icon/earphone.svg', - width: 29.rpx, - height: 29.rpx, // 如果 SVG 中没有固定颜色,可以这样设置 - color: themeController.currentColor.sc3, - ), - ), - ), + // ClickableContainer( + // backgroundColor: + // Colors.transparent, // 容器背景色 + // highlightColor: themeController + // .currentColor.sc21, // 点击时的背景色 + // padding: EdgeInsets + // .zero, // 这里去掉外部的 padding,避免影响点击范围 + // onTap: () async { + // if (userInfoController.model.login == + // LoginStatus.LOGIN.code) { + // // TopSlideNotification.show( + // // context, + // // text: "待开发功能".tr, + // // ); + // await loginController.openWeChatCustomerService(context); + // } else { + // TopSlideNotification.show( + // context, + // text: "必须登录提示".tr, + // textColor: + // themeController.currentColor.sc9, + // ); + // Get.toNamed("/loginPage"); + // } + // }, + // child: Padding( + // padding: EdgeInsetsDirectional.fromSTEB( + // 0.rpx, 0.rpx, 0.rpx, 0.rpx), + // child: SvgPicture.asset( + // 'assets/img/icon/earphone.svg', + // width: 29.rpx, + // height: 29.rpx, // 如果 SVG 中没有固定颜色,可以这样设置 + // color: themeController.currentColor.sc3, + // ), + // ), + // ), + ClickableContainer( backgroundColor: Colors.transparent, // 容器背景色 @@ -639,13 +643,15 @@ class _MinePageState extends State { ); Get.toNamed("/loginPage"); } else { - TopSlideNotification.show( - context, - text: "待开发.提示".tr, - textColor: - themeController.currentColor.sc2, - ); - // Get.toNamed("/newSleepReportPage",arguments: DateTime.now().millisecondsSinceEpoch); + // TopSlideNotification.show( + // context, + // text: "待开发.提示".tr, + // textColor: + // themeController.currentColor.sc2, + // ); + Get.toNamed("/newSleepReportPage", + arguments: DateTime.now() + .millisecondsSinceEpoch); } }, child: Container( diff --git a/lib/pages/person/person_page.dart b/lib/pages/person/person_page.dart index a3acc0d..b0079a3 100644 --- a/lib/pages/person/person_page.dart +++ b/lib/pages/person/person_page.dart @@ -107,7 +107,8 @@ class _EPageState extends State { if (apiRespons.code == HttpStatusCodes.ok) { TopSlideNotification.show(context, text: apiRespons.msg!); - Get.offAllNamed("/bindDeviceSuccess"); + // Get.offAllNamed("/bindDeviceSuccess"); + Get.toNamed("/wifiPage"); } else { TopSlideNotification.show(context, text: apiRespons.msg!, @@ -149,587 +150,682 @@ class _EPageState extends State { top: true, child: Padding( padding: EdgeInsetsDirectional.fromSTEB(30.rpx, 0, 30.rpx, 0), - child: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.max, - children: [ - Padding( - padding: EdgeInsetsDirectional.fromSTEB( - 70.rpx, 141.rpx, 70.rpx, 0), - child: Container( - width: double.infinity, - height: 100.rpx, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(50.rpx), - border: Border.all( - color: themeController.currentColor.sc4 - .withOpacity(0.5), - width: AppConstants().border_width, - ), - ), - child: Align( - alignment: AlignmentDirectional(0, 0), - child: TextFormField( - // controller: _model.textController1, - // focusNode: _model.textFieldFocusNode1, - initialValue: personController.name.value, - onChanged: (Value) { - personController.name.value = Value; - }, - autofocus: false, - obscureText: false, - decoration: InputDecoration( - fillColor: Colors.transparent, - isDense: true, - labelStyle: FlutterFlowTheme.of(context) - .labelMedium - .override( - fontFamily: 'Inter', - letterSpacing: 0.0, - color: themeController.currentColor.sc3, - ), - hintText: '人员资料.名字输入提示'.tr, - hintStyle: FlutterFlowTheme.of(context) - .labelMedium - .override( - fontFamily: 'Inter', - fontSize: 26.rpx, - letterSpacing: 0.0, - ), - enabledBorder: OutlineInputBorder( - borderSide: BorderSide( - color: Color(0x00000000), - width: 1.rpx, + child: Column( + children: [ + Expanded( + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 70.rpx, 141.rpx, 70.rpx, 0), + child: Container( + width: double.infinity, + height: 100.rpx, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(50.rpx), + border: Border.all( + color: themeController.currentColor.sc4 + .withOpacity(0.5), + width: AppConstants().border_width, ), - borderRadius: BorderRadius.circular(8.rpx), ), - focusedBorder: OutlineInputBorder( - borderSide: BorderSide( - color: Color(0x00000000), - width: 1.rpx, - ), - borderRadius: BorderRadius.circular(8.rpx), - ), - errorBorder: OutlineInputBorder( - borderSide: BorderSide( - color: FlutterFlowTheme.of(context).error, - width: 1.rpx, - ), - borderRadius: BorderRadius.circular(8.rpx), - ), - focusedErrorBorder: OutlineInputBorder( - borderSide: BorderSide( - color: FlutterFlowTheme.of(context).error, - width: 1.rpx, - ), - borderRadius: BorderRadius.circular(8.rpx), - ), - filled: true, - ), - style: FlutterFlowTheme.of(context) - .bodyMedium - .override( - fontFamily: 'Inter', - letterSpacing: 0.0, - color: themeController.currentColor.sc3, - ), - textAlign: TextAlign.center, - cursorColor: themeController.currentColor.sc3, - // validator: _model.textController1Validator - // .asValidator(context), - ), - ), - ), - ), - Align( - alignment: AlignmentDirectional(0, 0), - child: Padding( - padding: - EdgeInsetsDirectional.fromSTEB(0, 90.rpx, 0, 0), - child: Container( - width: double.infinity, - decoration: BoxDecoration(), - child: Align( - alignment: AlignmentDirectional(-1, 0), - child: Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - // Column( - // mainAxisSize: MainAxisSize.max, - // children: [ - // Container( - // width: 90.rpx, - // height: 90.rpx, - // decoration: BoxDecoration(), - // child: Container( - // clipBehavior: Clip.antiAlias, - // decoration: BoxDecoration( - // shape: BoxShape.circle, - // ), - // child: ColorFiltered( - // colorFilter: ColorFilter.mode( - // Colors.grey.withOpacity( - // 0.6), // 这里控制灰色度的强度 - // BlendMode.saturation, // 将图像变成灰度 - // ), - // child: Image.asset( - // "assets/img/man.png", - // fit: BoxFit.cover, - // ), - // ), - // ), - // ), - // Text( - // '男'.tr, - // style: FlutterFlowTheme.of(context) - // .bodyMedium - // .override( - // fontFamily: 'Inter', - // color: themeController - // .currentColor.sc3, - // fontSize: 26.rpx, - // letterSpacing: 0.0, - // ), - // ), - // ].divide(SizedBox(height: 14.rpx)), - // ), - // Column( - // mainAxisSize: MainAxisSize.max, - // children: [ - // Container( - // width: 90.rpx, - // height: 90.rpx, - // decoration: BoxDecoration(), - // child: Container( - // clipBehavior: Clip.antiAlias, - // decoration: BoxDecoration( - // shape: BoxShape.circle, - // ), - // child: Image.asset( - // "assets/img/woman.png", - // fit: BoxFit.cover, - // ), - // ), - // ), - // Text( - // '女'.tr, - // style: FlutterFlowTheme.of(context) - // .bodyMedium - // .override( - // fontFamily: 'Inter', - // color: themeController - // .currentColor.sc3, - // fontSize: 26.rpx, - // letterSpacing: 0.0, - // ), - // ), - // ].divide(SizedBox(height: 14.rpx)), - // ), - - Obx( - () { - bool isMaleGreyed = - personController.gender.value == - 0; // gender == 0 时男生部分变灰 - return GestureDetector( - onTap: () { - personController.gender.value = - 1; // 点击时将 gender 设置为 1(女生部分被选中) - }, - child: Column( - mainAxisSize: MainAxisSize.max, - children: [ - Container( - width: 90.rpx, - height: 90.rpx, - decoration: BoxDecoration(), - child: Container( - clipBehavior: Clip.antiAlias, - decoration: BoxDecoration( - shape: BoxShape.circle, - ), - child: ColorFiltered( - colorFilter: isMaleGreyed - ? ColorFilter.mode( - Colors.grey - .withOpacity(0.6), - BlendMode.saturation) - : ColorFilter.mode( - Colors.transparent, - BlendMode.saturation), - child: Image.asset( - "assets/img/man.png", - fit: BoxFit.cover, - ), - ), - ), - ), - Text( - '男'.tr, - style: - FlutterFlowTheme.of(context) - .bodyMedium - .override( - fontFamily: 'Inter', - fontSize: 26.rpx, - letterSpacing: 0.0, - color: isMaleGreyed - ? themeController - .currentColor - .sc4 - : themeController - .currentColor - .sc3, - ), - ), - ].divide(SizedBox(height: 14.rpx)), - ), - ); + child: Align( + alignment: AlignmentDirectional(0, 0), + child: TextFormField( + // controller: _model.textController1, + // focusNode: _model.textFieldFocusNode1, + initialValue: personController.name.value, + onChanged: (Value) { + personController.name.value = Value; }, - ), - - // 女性部分 - Obx( - () { - bool isFemaleGreyed = - personController.gender.value == - 1; // gender == 1 时女生部分变灰 - return GestureDetector( - onTap: () { - personController.gender.value = - 0; // 点击时将 gender 设置为 0(男生部分被选中) - }, - child: Column( - mainAxisSize: MainAxisSize.max, - children: [ - Container( - width: 90.rpx, - height: 90.rpx, - decoration: BoxDecoration(), - child: Container( - clipBehavior: Clip.antiAlias, - decoration: BoxDecoration( - shape: BoxShape.circle, - ), - child: ColorFiltered( - colorFilter: isFemaleGreyed - ? ColorFilter.mode( - Colors.grey - .withOpacity(0.6), - BlendMode.saturation) - : ColorFilter.mode( - Colors.transparent, - BlendMode.saturation), - child: Image.asset( - "assets/img/woman.png", - fit: BoxFit.cover, - ), - ), - ), - ), - Text( - '女'.tr, - style: - FlutterFlowTheme.of(context) - .bodyMedium - .override( - fontFamily: 'Inter', - color: isFemaleGreyed - ? themeController - .currentColor - .sc4 - : themeController - .currentColor - .sc3, - fontSize: 26.rpx, - letterSpacing: 0.0, - ), - ), - ].divide(SizedBox(height: 14.rpx)), + autofocus: false, + obscureText: false, + decoration: InputDecoration( + fillColor: Colors.transparent, + isDense: true, + labelStyle: FlutterFlowTheme.of(context) + .labelMedium + .override( + fontFamily: 'Inter', + letterSpacing: 0.0, + color: themeController + .currentColor.sc3, + ), + hintText: '人员资料.名字输入提示'.tr, + hintStyle: FlutterFlowTheme.of(context) + .labelMedium + .override( + fontFamily: 'Inter', + fontSize: 26.rpx, + letterSpacing: 0.0, + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0x00000000), + width: 1.rpx, ), - ); - }, - ), - ].divide(SizedBox(width: 170.rpx)), - ), - ), - ), - ), - ), - Padding( - padding: EdgeInsetsDirectional.fromSTEB( - 70.rpx, 50.rpx, 70.rpx, 0), - child: Container( - height: 100.rpx, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(50.rpx), - border: Border.all( - color: themeController.currentColor.sc4 - .withOpacity(0.5), - width: AppConstants().border_width, - ), - ), - child: InkWell( - onTap: () { - FocusScope.of(context).requestFocus(FocusNode()); - Future.delayed(Duration(milliseconds: 250), () { - showDateSelectionDialog( - context, - checkDate: personController.dateTime ?? - DateTime.now(), - checkChange: (DateTime d) { - personController.birthday.value = - MyUtils.formatBindTime(d); - personController.dateTime = d; - personController.updateAll(); - }, - ); - }); - }, - child: Center( - child: Text( - personController.dateTime != null - ? DateFormat("yyyy年MM月dd日") - .format(personController.dateTime!) - : '人员资料.生日输入提示'.tr, - textAlign: TextAlign.right, - style: FlutterFlowTheme.of(context) - .bodyMedium - .override( - fontFamily: 'Readex Pro', - color: personController.dateTime != null - ? themeController.currentColor.sc3 - : themeController.currentColor.sc4, - fontSize: - AppConstants().normal_text_fontSize, - letterSpacing: 0, - ), - ), - ), - ), - ), - ), - Padding( - padding: EdgeInsetsDirectional.fromSTEB( - 70.rpx, 18.rpx, 70.rpx, 0), - child: Container( - width: double.infinity, - height: 100.rpx, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(50.rpx), - border: Border.all( - color: themeController.currentColor.sc4 - .withOpacity(0.5), - width: AppConstants().border_width, - ), - ), - child: Align( - alignment: AlignmentDirectional(0, 0), - child: Stack( - alignment: Alignment.center, - children: [ - // 实际输入框(输入逻辑保留) - TextFormField( - keyboardType: TextInputType.number, - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly, - ], - initialValue: - personController.weight.value.toString(), - onChanged: (value) { - personController.weight.value = - int.tryParse(value) ?? 0; - }, - autofocus: false, - obscureText: false, - style: FlutterFlowTheme.of(context) - .bodyMedium - .override( - fontFamily: 'Inter', - color: Colors.transparent, // 隐藏输入文字 - letterSpacing: 0.0, + borderRadius: + BorderRadius.circular(8.rpx), ), - textAlign: TextAlign.center, - cursorColor: themeController.currentColor.sc3, - decoration: InputDecoration( - fillColor: Colors.transparent, - isDense: true, - hintText: '人员资料.体重输入提示'.tr, - hintStyle: FlutterFlowTheme.of(context) - .labelMedium - .override( - fontFamily: 'Inter', - color: - themeController.currentColor.sc4, - fontSize: AppConstants() - .normal_text_fontSize, - letterSpacing: 0.0, + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0x00000000), + width: 1.rpx, ), - enabledBorder: OutlineInputBorder( - borderSide: BorderSide( - color: Color(0x00000000), - width: 1.rpx, + borderRadius: + BorderRadius.circular(8.rpx), ), - borderRadius: - BorderRadius.circular(8.rpx), - ), - focusedBorder: OutlineInputBorder( - borderSide: BorderSide( - color: Color(0x00000000), - width: 1.rpx, + errorBorder: OutlineInputBorder( + borderSide: BorderSide( + color: FlutterFlowTheme.of(context) + .error, + width: 1.rpx, + ), + borderRadius: + BorderRadius.circular(8.rpx), ), - borderRadius: - BorderRadius.circular(8.rpx), - ), - errorBorder: OutlineInputBorder( - borderSide: BorderSide( - color: - FlutterFlowTheme.of(context).error, - width: 1.rpx, + focusedErrorBorder: OutlineInputBorder( + borderSide: BorderSide( + color: FlutterFlowTheme.of(context) + .error, + width: 1.rpx, + ), + borderRadius: + BorderRadius.circular(8.rpx), ), - borderRadius: - BorderRadius.circular(8.rpx), + filled: true, ), - focusedErrorBorder: OutlineInputBorder( - borderSide: BorderSide( - color: - FlutterFlowTheme.of(context).error, - width: 1.rpx, - ), - borderRadius: - BorderRadius.circular(8.rpx), - ), - filled: true, - ), - ), - Obx(() { - final weight = personController.weight.value; - return weight == 0 - ? const SizedBox.shrink() // 不显示任何内容 - : Text( - '${weight}kg', - textAlign: TextAlign.center, - style: FlutterFlowTheme.of(context) - .bodyMedium - .override( - fontFamily: 'Inter', - color: themeController - .currentColor.sc3, - fontSize: AppConstants() - .normal_text_fontSize, - ), - ); - }), - ], - ), - ), - ), - ), - Padding( - padding: - EdgeInsetsDirectional.fromSTEB(0, 117.rpx, 0, 0), - child: Container( - width: double.infinity, - decoration: BoxDecoration(), - child: Align( - alignment: AlignmentDirectional(0, 0), - child: Text( - '人员资料.疾病标题'.tr, - style: FlutterFlowTheme.of(context) - .bodyMedium - .override( - fontFamily: 'Inter', - color: Color(0xFFF3F4F5), - fontSize: 30.rpx, - letterSpacing: 0.0, - ), - ), - ), - ), - ), - Obx(() { - final selectedIds = personController.selectedDiseaseIds; - final diseases = personController.diseaseList; - return Padding( - padding: EdgeInsetsDirectional.fromSTEB( - 70.rpx, 70.rpx, 70.rpx, 0), - child: Wrap( - spacing: 20.rpx, - runSpacing: 20.rpx, - children: diseases.map((disease) { - final id = disease['_id']; - final name = disease['disease_type_name']; - final isSelected = selectedIds.contains(id); - return SelectableTagButton( - label: name, - selected: isSelected, - onTap: () { - if (isSelected) { - selectedIds.remove(id); - } else { - selectedIds.add(id); - } - personController.model.read = 0; - personController.updateAll(); - //切换语言 - // Get.updateLocale(Locale("en", "us")); - }, - ); - }).toList(), - ), - ); - }), - Padding( - padding: - EdgeInsetsDirectional.fromSTEB(0, 152.rpx, 0, 0), - child: Container( - width: double.infinity, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20.rpx), - border: Border.all( - color: themeController.currentColor.sc4 - .withOpacity(0.5), - width: AppConstants().border_width, - ), - ), - child: Padding( - padding: EdgeInsetsDirectional.fromSTEB( - 30.rpx, 30.rpx, 30.rpx, 30.rpx), - child: Row( - mainAxisSize: MainAxisSize.max, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: EdgeInsetsDirectional.fromSTEB( - 0, 8.rpx, 0, 0), - child: Icon( - Icons.arrow_back, - color: themeController.currentColor.sc4, - size: 24.rpx, - ), - ), - Expanded( - child: Text( - '人员资料.提示'.tr, style: FlutterFlowTheme.of(context) .bodyMedium .override( fontFamily: 'Inter', + letterSpacing: 0.0, color: - themeController.currentColor.sc4, - fontSize: 26.rpx, + themeController.currentColor.sc3, + ), + textAlign: TextAlign.center, + cursorColor: + themeController.currentColor.sc3, + // validator: _model.textController1Validator + // .asValidator(context), + ), + ), + ), + ), + Align( + alignment: AlignmentDirectional(0, 0), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0, 90.rpx, 0, 0), + child: Container( + width: double.infinity, + decoration: BoxDecoration(), + child: Align( + alignment: AlignmentDirectional(-1, 0), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Obx( + () { + bool isMaleGreyed = + personController.gender.value == + 0; // gender == 0 时男生部分变灰 + return GestureDetector( + onTap: () { + personController.gender.value = + 1; // 点击时将 gender 设置为 1(女生部分被选中) + }, + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Container( + width: 90.rpx, + height: 90.rpx, + decoration: BoxDecoration(), + child: Container( + clipBehavior: + Clip.antiAlias, + decoration: BoxDecoration( + shape: BoxShape.circle, + ), + child: ColorFiltered( + colorFilter: isMaleGreyed + ? ColorFilter.mode( + Colors.grey + .withOpacity( + 0.6), + BlendMode + .saturation) + : ColorFilter.mode( + Colors + .transparent, + BlendMode + .saturation), + child: Image.asset( + "assets/img/man.png", + fit: BoxFit.cover, + ), + ), + ), + ), + Text( + '男'.tr, + style: FlutterFlowTheme.of( + context) + .bodyMedium + .override( + fontFamily: 'Inter', + fontSize: 26.rpx, + letterSpacing: 0.0, + color: isMaleGreyed + ? themeController + .currentColor + .sc4 + : themeController + .currentColor + .sc3, + ), + ), + ].divide( + SizedBox(height: 14.rpx)), + ), + ); + }, + ), + + // 女性部分 + Obx( + () { + bool isFemaleGreyed = + personController.gender.value == + 1; // gender == 1 时女生部分变灰 + return GestureDetector( + onTap: () { + personController.gender.value = + 0; // 点击时将 gender 设置为 0(男生部分被选中) + }, + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Container( + width: 90.rpx, + height: 90.rpx, + decoration: BoxDecoration(), + child: Container( + clipBehavior: + Clip.antiAlias, + decoration: BoxDecoration( + shape: BoxShape.circle, + ), + child: ColorFiltered( + colorFilter: isFemaleGreyed + ? ColorFilter.mode( + Colors.grey + .withOpacity( + 0.6), + BlendMode + .saturation) + : ColorFilter.mode( + Colors + .transparent, + BlendMode + .saturation), + child: Image.asset( + "assets/img/woman.png", + fit: BoxFit.cover, + ), + ), + ), + ), + Text( + '女'.tr, + style: FlutterFlowTheme.of( + context) + .bodyMedium + .override( + fontFamily: 'Inter', + color: isFemaleGreyed + ? themeController + .currentColor + .sc4 + : themeController + .currentColor + .sc3, + fontSize: 26.rpx, + letterSpacing: 0.0, + ), + ), + ].divide( + SizedBox(height: 14.rpx)), + ), + ); + }, + ), + ].divide(SizedBox(width: 170.rpx)), + ), + ), + ), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 70.rpx, 50.rpx, 70.rpx, 0), + child: Container( + height: 100.rpx, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(50.rpx), + border: Border.all( + color: themeController.currentColor.sc4 + .withOpacity(0.5), + width: AppConstants().border_width, + ), + ), + child: InkWell( + onTap: () { + FocusScope.of(context) + .requestFocus(FocusNode()); + Future.delayed(Duration(milliseconds: 250), + () { + showDateSelectionDialog( + context, + checkDate: personController.dateTime ?? + DateTime.now(), + checkChange: (DateTime d) { + personController.birthday.value = + MyUtils.formatBindTime(d); + personController.dateTime = d; + personController.updateAll(); + }, + ); + }); + }, + child: Center( + child: Text( + personController.dateTime != null + ? DateFormat("yyyy年MM月dd日").format( + personController.dateTime!) + : '人员资料.生日输入提示'.tr, + textAlign: TextAlign.right, + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Readex Pro', + color: personController.dateTime != + null + ? themeController + .currentColor.sc3 + : themeController + .currentColor.sc4, + fontSize: AppConstants() + .normal_text_fontSize, + letterSpacing: 0, + ), + ), + ), + ), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 70.rpx, 18.rpx, 70.rpx, 0), + child: Container( + width: double.infinity, + height: 100.rpx, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(50.rpx), + border: Border.all( + color: themeController.currentColor.sc4 + .withOpacity(0.5), + width: AppConstants().border_width, + ), + ), + child: Align( + alignment: AlignmentDirectional(0, 0), + child: Stack( + alignment: Alignment.center, + children: [ + // 实际输入框(输入逻辑保留) + TextFormField( + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter + .digitsOnly, + ], + initialValue: personController + .height.value + .toString(), + onChanged: (value) { + personController.height.value = + int.tryParse(value) ?? 0; + }, + autofocus: false, + obscureText: false, + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Inter', + color: + Colors.transparent, // 隐藏输入文字 + letterSpacing: 0.0, + ), + textAlign: TextAlign.center, + cursorColor: + themeController.currentColor.sc3, + decoration: InputDecoration( + fillColor: Colors.transparent, + isDense: true, + hintText: '身高输入提示'.tr, + hintStyle: + FlutterFlowTheme.of(context) + .labelMedium + .override( + fontFamily: 'Inter', + color: themeController + .currentColor.sc4, + fontSize: AppConstants() + .normal_text_fontSize, + letterSpacing: 0.0, + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0x00000000), + width: 1.rpx, + ), + borderRadius: + BorderRadius.circular(8.rpx), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0x00000000), + width: 1.rpx, + ), + borderRadius: + BorderRadius.circular(8.rpx), + ), + errorBorder: OutlineInputBorder( + borderSide: BorderSide( + color: + FlutterFlowTheme.of(context) + .error, + width: 1.rpx, + ), + borderRadius: + BorderRadius.circular(8.rpx), + ), + focusedErrorBorder: + OutlineInputBorder( + borderSide: BorderSide( + color: + FlutterFlowTheme.of(context) + .error, + width: 1.rpx, + ), + borderRadius: + BorderRadius.circular(8.rpx), + ), + filled: true, + ), + ), + Obx(() { + final height = + personController.height.value; + return height == 0 + ? const SizedBox.shrink() // 不显示任何内容 + : Text( + '${height}cm', + textAlign: TextAlign.center, + style: + FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Inter', + color: themeController + .currentColor.sc3, + fontSize: AppConstants() + .normal_text_fontSize, + ), + ); + }), + ], + ), + ), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 70.rpx, 18.rpx, 70.rpx, 0), + child: Container( + width: double.infinity, + height: 100.rpx, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(50.rpx), + border: Border.all( + color: themeController.currentColor.sc4 + .withOpacity(0.5), + width: AppConstants().border_width, + ), + ), + child: Align( + alignment: AlignmentDirectional(0, 0), + child: Stack( + alignment: Alignment.center, + children: [ + // 实际输入框(输入逻辑保留) + TextFormField( + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter + .digitsOnly, + ], + initialValue: personController + .weight.value + .toString(), + onChanged: (value) { + personController.weight.value = + int.tryParse(value) ?? 0; + }, + autofocus: false, + obscureText: false, + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Inter', + color: + Colors.transparent, // 隐藏输入文字 + letterSpacing: 0.0, + ), + textAlign: TextAlign.center, + cursorColor: + themeController.currentColor.sc3, + decoration: InputDecoration( + fillColor: Colors.transparent, + isDense: true, + hintText: '人员资料.体重输入提示'.tr, + hintStyle: + FlutterFlowTheme.of(context) + .labelMedium + .override( + fontFamily: 'Inter', + color: themeController + .currentColor.sc4, + fontSize: AppConstants() + .normal_text_fontSize, + letterSpacing: 0.0, + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0x00000000), + width: 1.rpx, + ), + borderRadius: + BorderRadius.circular(8.rpx), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0x00000000), + width: 1.rpx, + ), + borderRadius: + BorderRadius.circular(8.rpx), + ), + errorBorder: OutlineInputBorder( + borderSide: BorderSide( + color: + FlutterFlowTheme.of(context) + .error, + width: 1.rpx, + ), + borderRadius: + BorderRadius.circular(8.rpx), + ), + focusedErrorBorder: + OutlineInputBorder( + borderSide: BorderSide( + color: + FlutterFlowTheme.of(context) + .error, + width: 1.rpx, + ), + borderRadius: + BorderRadius.circular(8.rpx), + ), + filled: true, + ), + ), + Obx(() { + final weight = + personController.weight.value; + return weight == 0 + ? const SizedBox.shrink() // 不显示任何内容 + : Text( + '${weight}kg', + textAlign: TextAlign.center, + style: + FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Inter', + color: themeController + .currentColor.sc3, + fontSize: AppConstants() + .normal_text_fontSize, + ), + ); + }), + ], + ), + ), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0, 117.rpx, 0, 0), + child: Container( + width: double.infinity, + decoration: BoxDecoration(), + child: Align( + alignment: AlignmentDirectional(0, 0), + child: Text( + '人员资料.疾病标题'.tr, + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Inter', + color: Color(0xFFF3F4F5), + fontSize: 30.rpx, letterSpacing: 0.0, ), ), ), - ].divide(SizedBox(width: 23.rpx)), + ), ), + Obx(() { + final selectedIds = + personController.selectedDiseaseIds; + final diseases = personController.diseaseList; + return Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 70.rpx, 70.rpx, 70.rpx, 0), + child: Wrap( + spacing: 20.rpx, + runSpacing: 20.rpx, + children: diseases.map((disease) { + final id = disease['_id']; + final name = disease['disease_type_name']; + final isSelected = selectedIds.contains(id); + return SelectableTagButton( + label: name, + selected: isSelected, + onTap: () { + if (isSelected) { + selectedIds.remove(id); + } else { + selectedIds.add(id); + } + personController.model.read = 0; + personController.updateAll(); + + }, + ); + }).toList(), + ), + ); + }), + ], + ), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB(0, 52.rpx, 0, 0), + child: Container( + width: double.infinity, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20.rpx), + border: Border.all( + color: themeController.currentColor.sc4 + .withOpacity(0.5), + width: AppConstants().border_width, + ), + ), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 30.rpx, 30.rpx, 30.rpx, 30.rpx), + child: Row( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0, 8.rpx, 0, 0), + child: Icon( + Icons.arrow_back, + color: themeController.currentColor.sc4, + size: 24.rpx, + ), + ), + Expanded( + child: Text( + '人员资料.提示'.tr, + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Inter', + color: themeController.currentColor.sc4, + fontSize: 26.rpx, + letterSpacing: 0.0, + ), + ), + ), + ].divide(SizedBox(width: 23.rpx)), ), ), ), - ], - ), + ), + ], ), ), ), diff --git a/lib/pages/person/update_person_page.dart b/lib/pages/person/update_person_page.dart index 33536f0..55e46cf 100644 --- a/lib/pages/person/update_person_page.dart +++ b/lib/pages/person/update_person_page.dart @@ -50,7 +50,7 @@ class _UpdatePageState extends State { } @override - Widget build(BuildContext context) { + Widget build(BuildContext context) { return LayoutBuilder( builder: (context, bodySize) => GestureDetector( onTap: () => FocusScope.of(context).unfocus(), @@ -150,587 +150,757 @@ class _UpdatePageState extends State { top: true, child: Padding( padding: EdgeInsetsDirectional.fromSTEB(30.rpx, 0, 30.rpx, 0), - child: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.max, - children: [ - Padding( - padding: EdgeInsetsDirectional.fromSTEB( - 70.rpx, 141.rpx, 70.rpx, 0), - child: Container( - width: double.infinity, - height: 100.rpx, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(50.rpx), - border: Border.all( - color: themeController.currentColor.sc4 - .withOpacity(0.5), - width: AppConstants().border_width, - ), - ), - child: Align( - alignment: AlignmentDirectional(0, 0), - child: TextFormField( - // controller: _model.textController1, - // focusNode: _model.textFieldFocusNode1, - initialValue: personController.name.value, - onChanged: (Value) { - personController.name.value = Value; - }, - autofocus: false, - obscureText: false, - decoration: InputDecoration( - fillColor: Colors.transparent, - isDense: true, - labelStyle: FlutterFlowTheme.of(context) - .labelMedium - .override( - fontFamily: 'Inter', - letterSpacing: 0.0, - color: themeController.currentColor.sc3, - ), - hintText: '人员资料.名字输入提示'.tr, - hintStyle: FlutterFlowTheme.of(context) - .labelMedium - .override( - fontFamily: 'Inter', - fontSize: 26.rpx, - letterSpacing: 0.0, - ), - enabledBorder: OutlineInputBorder( - borderSide: BorderSide( - color: Color(0x00000000), - width: 1.rpx, + child: Column( + children: [ + Expanded( + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 70.rpx, 141.rpx, 70.rpx, 0), + child: Container( + width: double.infinity, + height: 100.rpx, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(50.rpx), + border: Border.all( + color: themeController.currentColor.sc4 + .withOpacity(0.5), + width: AppConstants().border_width, ), - borderRadius: BorderRadius.circular(8.rpx), ), - focusedBorder: OutlineInputBorder( - borderSide: BorderSide( - color: Color(0x00000000), - width: 1.rpx, - ), - borderRadius: BorderRadius.circular(8.rpx), - ), - errorBorder: OutlineInputBorder( - borderSide: BorderSide( - color: FlutterFlowTheme.of(context).error, - width: 1.rpx, - ), - borderRadius: BorderRadius.circular(8.rpx), - ), - focusedErrorBorder: OutlineInputBorder( - borderSide: BorderSide( - color: FlutterFlowTheme.of(context).error, - width: 1.rpx, - ), - borderRadius: BorderRadius.circular(8.rpx), - ), - filled: true, - ), - style: FlutterFlowTheme.of(context) - .bodyMedium - .override( - fontFamily: 'Inter', - letterSpacing: 0.0, - color: themeController.currentColor.sc3, - ), - textAlign: TextAlign.center, - cursorColor: themeController.currentColor.sc3, - // validator: _model.textController1Validator - // .asValidator(context), - ), - ), - ), - ), - Align( - alignment: AlignmentDirectional(0, 0), - child: Padding( - padding: - EdgeInsetsDirectional.fromSTEB(0, 90.rpx, 0, 0), - child: Container( - width: double.infinity, - decoration: BoxDecoration(), - child: Align( - alignment: AlignmentDirectional(-1, 0), - child: Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: MainAxisAlignment.center, - children: [ - // Column( - // mainAxisSize: MainAxisSize.max, - // children: [ - // Container( - // width: 90.rpx, - // height: 90.rpx, - // decoration: BoxDecoration(), - // child: Container( - // clipBehavior: Clip.antiAlias, - // decoration: BoxDecoration( - // shape: BoxShape.circle, - // ), - // child: ColorFiltered( - // colorFilter: ColorFilter.mode( - // Colors.grey.withOpacity( - // 0.6), // 这里控制灰色度的强度 - // BlendMode.saturation, // 将图像变成灰度 - // ), - // child: Image.asset( - // "assets/img/man.png", - // fit: BoxFit.cover, - // ), - // ), - // ), - // ), - // Text( - // '男'.tr, - // style: FlutterFlowTheme.of(context) - // .bodyMedium - // .override( - // fontFamily: 'Inter', - // color: themeController - // .currentColor.sc3, - // fontSize: 26.rpx, - // letterSpacing: 0.0, - // ), - // ), - // ].divide(SizedBox(height: 14.rpx)), - // ), - // Column( - // mainAxisSize: MainAxisSize.max, - // children: [ - // Container( - // width: 90.rpx, - // height: 90.rpx, - // decoration: BoxDecoration(), - // child: Container( - // clipBehavior: Clip.antiAlias, - // decoration: BoxDecoration( - // shape: BoxShape.circle, - // ), - // child: Image.asset( - // "assets/img/woman.png", - // fit: BoxFit.cover, - // ), - // ), - // ), - // Text( - // '女'.tr, - // style: FlutterFlowTheme.of(context) - // .bodyMedium - // .override( - // fontFamily: 'Inter', - // color: themeController - // .currentColor.sc3, - // fontSize: 26.rpx, - // letterSpacing: 0.0, - // ), - // ), - // ].divide(SizedBox(height: 14.rpx)), - // ), - - Obx( - () { - bool isMaleGreyed = - personController.gender.value == - 0; // gender == 0 时男生部分变灰 - return GestureDetector( - onTap: () { - personController.gender.value = - 1; // 点击时将 gender 设置为 1(女生部分被选中) - }, - child: Column( - mainAxisSize: MainAxisSize.max, - children: [ - Container( - width: 90.rpx, - height: 90.rpx, - decoration: BoxDecoration(), - child: Container( - clipBehavior: Clip.antiAlias, - decoration: BoxDecoration( - shape: BoxShape.circle, - ), - child: ColorFiltered( - colorFilter: isMaleGreyed - ? ColorFilter.mode( - Colors.grey - .withOpacity(0.6), - BlendMode.saturation) - : ColorFilter.mode( - Colors.transparent, - BlendMode.saturation), - child: Image.asset( - "assets/img/man.png", - fit: BoxFit.cover, - ), - ), - ), - ), - Text( - '男'.tr, - style: - FlutterFlowTheme.of(context) - .bodyMedium - .override( - fontFamily: 'Inter', - fontSize: 26.rpx, - letterSpacing: 0.0, - color: isMaleGreyed - ? themeController - .currentColor - .sc4 - : themeController - .currentColor - .sc3, - ), - ), - ].divide(SizedBox(height: 14.rpx)), - ), - ); + child: Align( + alignment: AlignmentDirectional(0, 0), + child: TextFormField( + // controller: _model.textController1, + // focusNode: _model.textFieldFocusNode1, + initialValue: personController.name.value, + onChanged: (Value) { + personController.name.value = Value; }, - ), - - // 女性部分 - Obx( - () { - bool isFemaleGreyed = - personController.gender.value == - 1; // gender == 1 时女生部分变灰 - return GestureDetector( - onTap: () { - personController.gender.value = - 0; // 点击时将 gender 设置为 0(男生部分被选中) - }, - child: Column( - mainAxisSize: MainAxisSize.max, - children: [ - Container( - width: 90.rpx, - height: 90.rpx, - decoration: BoxDecoration(), - child: Container( - clipBehavior: Clip.antiAlias, - decoration: BoxDecoration( - shape: BoxShape.circle, - ), - child: ColorFiltered( - colorFilter: isFemaleGreyed - ? ColorFilter.mode( - Colors.grey - .withOpacity(0.6), - BlendMode.saturation) - : ColorFilter.mode( - Colors.transparent, - BlendMode.saturation), - child: Image.asset( - "assets/img/woman.png", - fit: BoxFit.cover, - ), - ), - ), - ), - Text( - '女'.tr, - style: - FlutterFlowTheme.of(context) - .bodyMedium - .override( - fontFamily: 'Inter', - color: isFemaleGreyed - ? themeController - .currentColor - .sc4 - : themeController - .currentColor - .sc3, - fontSize: 26.rpx, - letterSpacing: 0.0, - ), - ), - ].divide(SizedBox(height: 14.rpx)), + autofocus: false, + obscureText: false, + decoration: InputDecoration( + fillColor: Colors.transparent, + isDense: true, + labelStyle: FlutterFlowTheme.of(context) + .labelMedium + .override( + fontFamily: 'Inter', + letterSpacing: 0.0, + color: themeController + .currentColor.sc3, + ), + hintText: '人员资料.名字输入提示'.tr, + hintStyle: FlutterFlowTheme.of(context) + .labelMedium + .override( + fontFamily: 'Inter', + fontSize: 26.rpx, + letterSpacing: 0.0, + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0x00000000), + width: 1.rpx, ), - ); - }, - ), - ].divide(SizedBox(width: 170.rpx)), - ), - ), - ), - ), - ), - Padding( - padding: EdgeInsetsDirectional.fromSTEB( - 70.rpx, 50.rpx, 70.rpx, 0), - child: Container( - height: 100.rpx, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(50.rpx), - border: Border.all( - color: themeController.currentColor.sc4 - .withOpacity(0.5), - width: AppConstants().border_width, - ), - ), - child: InkWell( - onTap: () { - FocusScope.of(context).requestFocus(FocusNode()); - Future.delayed(Duration(milliseconds: 250), () { - showDateSelectionDialog( - context, - checkDate: personController.dateTime ?? - DateTime.now(), - checkChange: (DateTime d) { - personController.birthday.value = - MyUtils.formatBindTime(d); - personController.dateTime = d; - personController.updateAll(); - }, - ); - }); - }, - child: Center( - child: Text( - personController.dateTime != null - ? DateFormat("yyyy年MM月dd日") - .format(personController.dateTime!) - : '人员资料.生日输入提示'.tr, - textAlign: TextAlign.right, - style: FlutterFlowTheme.of(context) - .bodyMedium - .override( - fontFamily: 'Readex Pro', - color: personController.dateTime != null - ? themeController.currentColor.sc3 - : themeController.currentColor.sc4, - fontSize: - AppConstants().normal_text_fontSize, - letterSpacing: 0, - ), - ), - ), - ), - ), - ), - Padding( - padding: EdgeInsetsDirectional.fromSTEB( - 70.rpx, 18.rpx, 70.rpx, 0), - child: Container( - width: double.infinity, - height: 100.rpx, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(50.rpx), - border: Border.all( - color: themeController.currentColor.sc4 - .withOpacity(0.5), - width: AppConstants().border_width, - ), - ), - child: Align( - alignment: AlignmentDirectional(0, 0), - child: Stack( - alignment: Alignment.center, - children: [ - // 实际输入框(输入逻辑保留) - TextFormField( - keyboardType: TextInputType.number, - inputFormatters: [ - FilteringTextInputFormatter.digitsOnly, - ], - initialValue: - personController.weight.value.toString(), - onChanged: (value) { - personController.weight.value = - int.tryParse(value) ?? 0; - }, - autofocus: false, - obscureText: false, - style: FlutterFlowTheme.of(context) - .bodyMedium - .override( - fontFamily: 'Inter', - color: Colors.transparent, // 隐藏输入文字 - letterSpacing: 0.0, + borderRadius: + BorderRadius.circular(8.rpx), ), - textAlign: TextAlign.center, - cursorColor: themeController.currentColor.sc3, - decoration: InputDecoration( - fillColor: Colors.transparent, - isDense: true, - hintText: '人员资料.体重输入提示'.tr, - hintStyle: FlutterFlowTheme.of(context) - .labelMedium - .override( - fontFamily: 'Inter', - color: - themeController.currentColor.sc4, - fontSize: AppConstants() - .normal_text_fontSize, - letterSpacing: 0.0, + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0x00000000), + width: 1.rpx, ), - enabledBorder: OutlineInputBorder( - borderSide: BorderSide( - color: Color(0x00000000), - width: 1.rpx, + borderRadius: + BorderRadius.circular(8.rpx), ), - borderRadius: - BorderRadius.circular(8.rpx), - ), - focusedBorder: OutlineInputBorder( - borderSide: BorderSide( - color: Color(0x00000000), - width: 1.rpx, + errorBorder: OutlineInputBorder( + borderSide: BorderSide( + color: FlutterFlowTheme.of(context) + .error, + width: 1.rpx, + ), + borderRadius: + BorderRadius.circular(8.rpx), ), - borderRadius: - BorderRadius.circular(8.rpx), - ), - errorBorder: OutlineInputBorder( - borderSide: BorderSide( - color: - FlutterFlowTheme.of(context).error, - width: 1.rpx, + focusedErrorBorder: OutlineInputBorder( + borderSide: BorderSide( + color: FlutterFlowTheme.of(context) + .error, + width: 1.rpx, + ), + borderRadius: + BorderRadius.circular(8.rpx), ), - borderRadius: - BorderRadius.circular(8.rpx), + filled: true, ), - focusedErrorBorder: OutlineInputBorder( - borderSide: BorderSide( - color: - FlutterFlowTheme.of(context).error, - width: 1.rpx, - ), - borderRadius: - BorderRadius.circular(8.rpx), - ), - filled: true, - ), - ), - Obx(() { - final weight = personController.weight.value; - return weight == 0 - ? const SizedBox.shrink() // 不显示任何内容 - : Text( - '${weight}kg', - textAlign: TextAlign.center, - style: FlutterFlowTheme.of(context) - .bodyMedium - .override( - fontFamily: 'Inter', - color: themeController - .currentColor.sc3, - fontSize: AppConstants() - .normal_text_fontSize, - ), - ); - }), - ], - ), - ), - ), - ), - Padding( - padding: - EdgeInsetsDirectional.fromSTEB(0, 117.rpx, 0, 0), - child: Container( - width: double.infinity, - decoration: BoxDecoration(), - child: Align( - alignment: AlignmentDirectional(0, 0), - child: Text( - '人员资料.疾病标题'.tr, - style: FlutterFlowTheme.of(context) - .bodyMedium - .override( - fontFamily: 'Inter', - color: Color(0xFFF3F4F5), - fontSize: 30.rpx, - letterSpacing: 0.0, - ), - ), - ), - ), - ), - Obx(() { - final selectedIds = personController.selectedDiseaseIds; - final diseases = personController.diseaseList; - return Padding( - padding: EdgeInsetsDirectional.fromSTEB( - 70.rpx, 70.rpx, 70.rpx, 0), - child: Wrap( - spacing: 20.rpx, - runSpacing: 20.rpx, - children: diseases.map((disease) { - final id = disease['_id']; - final name = disease['disease_type_name']; - final isSelected = selectedIds.contains(id); - return SelectableTagButton( - label: name, - selected: isSelected, - onTap: () { - if (isSelected) { - selectedIds.remove(id); - } else { - selectedIds.add(id); - } - personController.model.read = 0; - personController.updateAll(); - //切换语言 - // Get.updateLocale(Locale("en", "us")); - }, - ); - }).toList(), - ), - ); - }), - Padding( - padding: - EdgeInsetsDirectional.fromSTEB(0, 152.rpx, 0, 0), - child: Container( - width: double.infinity, - decoration: BoxDecoration( - borderRadius: BorderRadius.circular(20.rpx), - border: Border.all( - color: themeController.currentColor.sc4 - .withOpacity(0.5), - width: AppConstants().border_width, - ), - ), - child: Padding( - padding: EdgeInsetsDirectional.fromSTEB( - 30.rpx, 30.rpx, 30.rpx, 30.rpx), - child: Row( - mainAxisSize: MainAxisSize.max, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: EdgeInsetsDirectional.fromSTEB( - 0, 8.rpx, 0, 0), - child: Icon( - Icons.arrow_back, - color: Color(0xFFE4EBF0), - size: 24.rpx, - ), - ), - Expanded( - child: Text( - '人员资料.提示'.tr, style: FlutterFlowTheme.of(context) .bodyMedium .override( fontFamily: 'Inter', + letterSpacing: 0.0, color: - themeController.currentColor.sc4, - fontSize: 26.rpx, + themeController.currentColor.sc3, + ), + textAlign: TextAlign.center, + cursorColor: + themeController.currentColor.sc3, + // validator: _model.textController1Validator + // .asValidator(context), + ), + ), + ), + ), + Align( + alignment: AlignmentDirectional(0, 0), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0, 90.rpx, 0, 0), + child: Container( + width: double.infinity, + decoration: BoxDecoration(), + child: Align( + alignment: AlignmentDirectional(-1, 0), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + // Column( + // mainAxisSize: MainAxisSize.max, + // children: [ + // Container( + // width: 90.rpx, + // height: 90.rpx, + // decoration: BoxDecoration(), + // child: Container( + // clipBehavior: Clip.antiAlias, + // decoration: BoxDecoration( + // shape: BoxShape.circle, + // ), + // child: ColorFiltered( + // colorFilter: ColorFilter.mode( + // Colors.grey.withOpacity( + // 0.6), // 这里控制灰色度的强度 + // BlendMode.saturation, // 将图像变成灰度 + // ), + // child: Image.asset( + // "assets/img/man.png", + // fit: BoxFit.cover, + // ), + // ), + // ), + // ), + // Text( + // '男'.tr, + // style: FlutterFlowTheme.of(context) + // .bodyMedium + // .override( + // fontFamily: 'Inter', + // color: themeController + // .currentColor.sc3, + // fontSize: 26.rpx, + // letterSpacing: 0.0, + // ), + // ), + // ].divide(SizedBox(height: 14.rpx)), + // ), + // Column( + // mainAxisSize: MainAxisSize.max, + // children: [ + // Container( + // width: 90.rpx, + // height: 90.rpx, + // decoration: BoxDecoration(), + // child: Container( + // clipBehavior: Clip.antiAlias, + // decoration: BoxDecoration( + // shape: BoxShape.circle, + // ), + // child: Image.asset( + // "assets/img/woman.png", + // fit: BoxFit.cover, + // ), + // ), + // ), + // Text( + // '女'.tr, + // style: FlutterFlowTheme.of(context) + // .bodyMedium + // .override( + // fontFamily: 'Inter', + // color: themeController + // .currentColor.sc3, + // fontSize: 26.rpx, + // letterSpacing: 0.0, + // ), + // ), + // ].divide(SizedBox(height: 14.rpx)), + // ), + + Obx( + () { + bool isMaleGreyed = + personController.gender.value == + 0; // gender == 0 时男生部分变灰 + return GestureDetector( + onTap: () { + personController.gender.value = + 1; // 点击时将 gender 设置为 1(女生部分被选中) + }, + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Container( + width: 90.rpx, + height: 90.rpx, + decoration: BoxDecoration(), + child: Container( + clipBehavior: + Clip.antiAlias, + decoration: BoxDecoration( + shape: BoxShape.circle, + ), + child: ColorFiltered( + colorFilter: isMaleGreyed + ? ColorFilter.mode( + Colors.grey + .withOpacity( + 0.6), + BlendMode + .saturation) + : ColorFilter.mode( + Colors + .transparent, + BlendMode + .saturation), + child: Image.asset( + "assets/img/man.png", + fit: BoxFit.cover, + ), + ), + ), + ), + Text( + '男'.tr, + style: FlutterFlowTheme.of( + context) + .bodyMedium + .override( + fontFamily: 'Inter', + fontSize: 26.rpx, + letterSpacing: 0.0, + color: isMaleGreyed + ? themeController + .currentColor + .sc4 + : themeController + .currentColor + .sc3, + ), + ), + ].divide( + SizedBox(height: 14.rpx)), + ), + ); + }, + ), + + // 女性部分 + Obx( + () { + bool isFemaleGreyed = + personController.gender.value == + 1; // gender == 1 时女生部分变灰 + return GestureDetector( + onTap: () { + personController.gender.value = + 0; // 点击时将 gender 设置为 0(男生部分被选中) + }, + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Container( + width: 90.rpx, + height: 90.rpx, + decoration: BoxDecoration(), + child: Container( + clipBehavior: + Clip.antiAlias, + decoration: BoxDecoration( + shape: BoxShape.circle, + ), + child: ColorFiltered( + colorFilter: isFemaleGreyed + ? ColorFilter.mode( + Colors.grey + .withOpacity( + 0.6), + BlendMode + .saturation) + : ColorFilter.mode( + Colors + .transparent, + BlendMode + .saturation), + child: Image.asset( + "assets/img/woman.png", + fit: BoxFit.cover, + ), + ), + ), + ), + Text( + '女'.tr, + style: FlutterFlowTheme.of( + context) + .bodyMedium + .override( + fontFamily: 'Inter', + color: isFemaleGreyed + ? themeController + .currentColor + .sc4 + : themeController + .currentColor + .sc3, + fontSize: 26.rpx, + letterSpacing: 0.0, + ), + ), + ].divide( + SizedBox(height: 14.rpx)), + ), + ); + }, + ), + ].divide(SizedBox(width: 170.rpx)), + ), + ), + ), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 70.rpx, 50.rpx, 70.rpx, 0), + child: Container( + height: 100.rpx, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(50.rpx), + border: Border.all( + color: themeController.currentColor.sc4 + .withOpacity(0.5), + width: AppConstants().border_width, + ), + ), + child: InkWell( + onTap: () { + FocusScope.of(context) + .requestFocus(FocusNode()); + Future.delayed(Duration(milliseconds: 250), + () { + showDateSelectionDialog( + context, + checkDate: personController.dateTime ?? + DateTime.now(), + checkChange: (DateTime d) { + personController.birthday.value = + MyUtils.formatBindTime(d); + personController.dateTime = d; + personController.updateAll(); + }, + ); + }); + }, + child: Center( + child: Text( + personController.dateTime != null + ? DateFormat("yyyy年MM月dd日").format( + personController.dateTime!) + : '人员资料.生日输入提示'.tr, + textAlign: TextAlign.right, + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Readex Pro', + color: personController.dateTime != + null + ? themeController + .currentColor.sc3 + : themeController + .currentColor.sc4, + fontSize: AppConstants() + .normal_text_fontSize, + letterSpacing: 0, + ), + ), + ), + ), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 70.rpx, 18.rpx, 70.rpx, 0), + child: Container( + width: double.infinity, + height: 100.rpx, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(50.rpx), + border: Border.all( + color: themeController.currentColor.sc4 + .withOpacity(0.5), + width: AppConstants().border_width, + ), + ), + child: Align( + alignment: AlignmentDirectional(0, 0), + child: Stack( + alignment: Alignment.center, + children: [ + TextFormField( + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter + .digitsOnly, + ], + initialValue: personController + .height.value + .toString(), + onChanged: (value) { + personController.height.value = + int.tryParse(value) ?? 0; + }, + autofocus: false, + obscureText: false, + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Inter', + color: + Colors.transparent, // 隐藏输入文字 + letterSpacing: 0.0, + ), + textAlign: TextAlign.center, + cursorColor: + themeController.currentColor.sc3, + decoration: InputDecoration( + fillColor: Colors.transparent, + isDense: true, + hintText: '身高输入提示'.tr, + hintStyle: + FlutterFlowTheme.of(context) + .labelMedium + .override( + fontFamily: 'Inter', + color: themeController + .currentColor.sc4, + fontSize: AppConstants() + .normal_text_fontSize, + letterSpacing: 0.0, + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0x00000000), + width: 1.rpx, + ), + borderRadius: + BorderRadius.circular(8.rpx), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0x00000000), + width: 1.rpx, + ), + borderRadius: + BorderRadius.circular(8.rpx), + ), + errorBorder: OutlineInputBorder( + borderSide: BorderSide( + color: + FlutterFlowTheme.of(context) + .error, + width: 1.rpx, + ), + borderRadius: + BorderRadius.circular(8.rpx), + ), + focusedErrorBorder: + OutlineInputBorder( + borderSide: BorderSide( + color: + FlutterFlowTheme.of(context) + .error, + width: 1.rpx, + ), + borderRadius: + BorderRadius.circular(8.rpx), + ), + filled: true, + ), + ), + Obx(() { + final height = + personController.height.value; + return height == 0 + ? const SizedBox.shrink() // 不显示任何内容 + : Text( + '${height}cm', + textAlign: TextAlign.center, + style: + FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Inter', + color: themeController + .currentColor.sc3, + fontSize: AppConstants() + .normal_text_fontSize, + ), + ); + }), + ], + ), + ), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 70.rpx, 18.rpx, 70.rpx, 0), + child: Container( + width: double.infinity, + height: 100.rpx, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(50.rpx), + border: Border.all( + color: themeController.currentColor.sc4 + .withOpacity(0.5), + width: AppConstants().border_width, + ), + ), + child: Align( + alignment: AlignmentDirectional(0, 0), + child: Stack( + alignment: Alignment.center, + children: [ + // 实际输入框(输入逻辑保留) + TextFormField( + keyboardType: TextInputType.number, + inputFormatters: [ + FilteringTextInputFormatter + .digitsOnly, + ], + initialValue: personController + .weight.value + .toString(), + onChanged: (value) { + personController.weight.value = + int.tryParse(value) ?? 0; + }, + autofocus: false, + obscureText: false, + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Inter', + color: + Colors.transparent, // 隐藏输入文字 + letterSpacing: 0.0, + ), + textAlign: TextAlign.center, + cursorColor: + themeController.currentColor.sc3, + decoration: InputDecoration( + fillColor: Colors.transparent, + isDense: true, + hintText: '人员资料.体重输入提示'.tr, + hintStyle: + FlutterFlowTheme.of(context) + .labelMedium + .override( + fontFamily: 'Inter', + color: themeController + .currentColor.sc4, + fontSize: AppConstants() + .normal_text_fontSize, + letterSpacing: 0.0, + ), + enabledBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0x00000000), + width: 1.rpx, + ), + borderRadius: + BorderRadius.circular(8.rpx), + ), + focusedBorder: OutlineInputBorder( + borderSide: BorderSide( + color: Color(0x00000000), + width: 1.rpx, + ), + borderRadius: + BorderRadius.circular(8.rpx), + ), + errorBorder: OutlineInputBorder( + borderSide: BorderSide( + color: + FlutterFlowTheme.of(context) + .error, + width: 1.rpx, + ), + borderRadius: + BorderRadius.circular(8.rpx), + ), + focusedErrorBorder: + OutlineInputBorder( + borderSide: BorderSide( + color: + FlutterFlowTheme.of(context) + .error, + width: 1.rpx, + ), + borderRadius: + BorderRadius.circular(8.rpx), + ), + filled: true, + ), + ), + Obx(() { + final weight = + personController.weight.value; + return weight == 0 + ? const SizedBox.shrink() // 不显示任何内容 + : Text( + '${weight}kg', + textAlign: TextAlign.center, + style: + FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Inter', + color: themeController + .currentColor.sc3, + fontSize: AppConstants() + .normal_text_fontSize, + ), + ); + }), + ], + ), + ), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0, 117.rpx, 0, 0), + child: Container( + width: double.infinity, + decoration: BoxDecoration(), + child: Align( + alignment: AlignmentDirectional(0, 0), + child: Text( + '人员资料.疾病标题'.tr, + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Inter', + color: Color(0xFFF3F4F5), + fontSize: 30.rpx, letterSpacing: 0.0, ), ), ), - ].divide(SizedBox(width: 23.rpx)), + ), ), + Obx(() { + final selectedIds = + personController.selectedDiseaseIds; + final diseases = personController.diseaseList; + return Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 70.rpx, 70.rpx, 70.rpx, 0), + child: Wrap( + spacing: 20.rpx, + runSpacing: 20.rpx, + children: diseases.map((disease) { + final id = disease['_id']; + final name = disease['disease_type_name']; + final isSelected = selectedIds.contains(id); + return SelectableTagButton( + label: name, + selected: isSelected, + onTap: () { + if (isSelected) { + selectedIds.remove(id); + } else { + selectedIds.add(id); + } + personController.model.read = 0; + personController.updateAll(); + //切换语言 + // Get.updateLocale(Locale("en", "us")); + }, + ); + }).toList(), + ), + ); + }), + ], + ), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB(0, 52.rpx, 0, 0), + child: Container( + width: double.infinity, + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(20.rpx), + border: Border.all( + color: themeController.currentColor.sc4 + .withOpacity(0.5), + width: AppConstants().border_width, + ), + ), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 30.rpx, 30.rpx, 30.rpx, 30.rpx), + child: Row( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0, 8.rpx, 0, 0), + child: Icon( + Icons.arrow_back, + color: themeController.currentColor.sc4, + size: 24.rpx, + ), + ), + Expanded( + child: Text( + '人员资料.提示'.tr, + style: FlutterFlowTheme.of(context) + .bodyMedium + .override( + fontFamily: 'Inter', + color: themeController.currentColor.sc4, + fontSize: 26.rpx, + letterSpacing: 0.0, + ), + ), + ), + ].divide(SizedBox(width: 23.rpx)), ), ), ), - ], - ), + ), + SizedBox( + height: 20.rpx, + ), + ], ), ), ), diff --git a/lib/pages/sleep_report/chart/DotBarChart.dart b/lib/pages/sleep_report/chart/DotBarChart.dart new file mode 100644 index 0000000..1ab0216 --- /dev/null +++ b/lib/pages/sleep_report/chart/DotBarChart.dart @@ -0,0 +1,408 @@ +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:vbvs_app/common/util/FitTool.dart'; +import 'package:vbvs_app/common/util/MyUtils.dart'; + +class DotBarChart extends StatefulWidget { + final List> showLabel; + final int threshold; + final int startTime; + final int endTime; + + const DotBarChart({ + Key? key, + required this.showLabel, + required this.threshold, + required this.startTime, + required this.endTime, + }) : super(key: key); + + @override + _DotBarChartState createState() => _DotBarChartState(); +} + +class _DotBarChartState extends State { + int? selectedIndex; + OverlayEntry? _overlayEntry; + + @override + void dispose() { + _removeOverlay(); + super.dispose(); + } + + void _removeOverlay() { + _overlayEntry?.remove(); + _overlayEntry = null; + } + + void _showTooltip(BuildContext context, Map data, + Offset position, double dotY) { + _removeOverlay(); + + final RenderBox renderBox = context.findRenderObject() as RenderBox; + final Offset globalPosition = renderBox.localToGlobal(position); + + _overlayEntry = OverlayEntry( + builder: (context) => Positioned( + left: globalPosition.dx - 60.rpx, + top: globalPosition.dy - 80.rpx, + child: Material( + color: Colors.transparent, + child: Container( + padding: EdgeInsets.all(12.rpx), + decoration: BoxDecoration( + color: themeController.currentColor.sc5, + borderRadius: BorderRadius.circular(8.rpx), + boxShadow: [ + // BoxShadow( + // color: Colors.black.withOpacity(0.6), + // blurRadius: 10.rpx, + // offset: Offset(0, 4.rpx), + // ), + BoxShadow( + color: Colors.black.withOpacity(0.5), + blurRadius: 12.rpx, + spreadRadius: 2.rpx, + offset: Offset(0, 6.rpx), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '时间: ${DateFormat('HH:mm').format(DateTime.fromMillisecondsSinceEpoch(data['time']))}', + style: TextStyle( + fontSize: 20.rpx, + color: themeController.currentColor.sc3, + ), + ), + SizedBox(height: 4.rpx), + Text( + '时长: ${data['times']}秒', + style: TextStyle( + fontSize: 20.rpx, + color: themeController.currentColor.sc3, + ), + ), + ], + ), + ), + ), + ), + ); + + Overlay.of(context)?.insert(_overlayEntry!); + } + + @override + Widget build(BuildContext context) { + if (widget.showLabel.isEmpty) return const SizedBox(); + + int maxTimes = widget.showLabel + .map((e) => e['times'] ?? 0) + .reduce((a, b) => a > b ? a : b); + + int yMax = (maxTimes / 10).ceil() * 10; + if (yMax == 0) yMax = 10; + + int maxSteps = 6; + int step = (yMax / maxSteps).ceil(); + step = ((step + 9) ~/ 10) * 10; + int displayMax = step * maxSteps; + List yLabels = List.generate(maxSteps + 1, (index) => step * index); + + DateFormat fullFormat = DateFormat('HH:mm'); + DateFormat hourFormat = DateFormat('H'); + + int maxXLabels = 11; + int totalPoints = widget.showLabel.length; + List xLabelIndices = []; + + if (totalPoints <= maxXLabels) { + xLabelIndices = List.generate(totalPoints, (i) => i); + } else { + xLabelIndices.add(0); + int middleCount = maxXLabels - 2; + double stepX = (totalPoints - 1) / (middleCount + 1); + for (int i = 1; i <= middleCount; i++) { + xLabelIndices.add((stepX * i).round()); + } + xLabelIndices.add(totalPoints - 1); + } + + double yAxisWidth = 36.rpx; + double xAxisHeight = 40.rpx; + double chartHeight = 491.rpx; + double bottomPadding = 10.rpx; + + return GestureDetector( + onTap: () { + setState(() { + selectedIndex = null; + _removeOverlay(); + }); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: chartHeight, + child: Row( + children: [ + SizedBox( + width: yAxisWidth, + height: chartHeight + bottomPadding, + child: Stack( + children: [ + Positioned( + top: 0, + right: 6.rpx, + child: Text( + '秒', + style: TextStyle( + fontSize: 18.rpx, + color: themeController.currentColor.sc4, + ), + ), + ), + ...List.generate(yLabels.length, (index) { + double yLabelsAreaHeight = + chartHeight - (30.rpx + 18.rpx) - bottomPadding; + double y = (30.rpx + 18.rpx) + + index * (yLabelsAreaHeight / (yLabels.length - 1)); + double textHeight = 18.rpx; + double topPos = y - textHeight / 2; + if (index == yLabels.length - 1) { + topPos -= bottomPadding / 2; + } + return Positioned( + right: 6.rpx, + top: topPos, + child: Text( + '${yLabels.reversed.toList()[index]}', + style: TextStyle( + fontSize: 18.rpx, + color: themeController.currentColor.sc4, + ), + ), + ); + }), + ], + ), + ), + Expanded( + child: CustomPaint( + size: Size(double.infinity, chartHeight), + painter: _DotBarChartPainter( + data: widget.showLabel, + yMax: displayMax, + threshold: widget.threshold, + yLabelsCount: yLabels.length, + yAxisTopPadding: 30.rpx + 18.rpx, + horizontalPadding: 20.rpx, + selectedIndex: selectedIndex, + ), + child: LayoutBuilder( + builder: (context, constraints) { + final double chartWidth = + constraints.maxWidth - 2 * 20.rpx; + final double xStep = widget.showLabel.length > 1 + ? chartWidth / (widget.showLabel.length - 1) + : 0; + final double drawableHeight = + chartHeight - (30.rpx + 18.rpx); + + return Stack( + children: + widget.showLabel.asMap().entries.map((entry) { + int index = entry.key; + Map data = entry.value; + int times = data['times'] ?? 0; + double x = 20.rpx + index * xStep; + double y = (30.rpx + 18.rpx) + + drawableHeight * (1 - times / displayMax); + + return Positioned( + left: x - 20.rpx, // Increase touch area + top: y - 20.rpx, // Increase touch area + child: GestureDetector( + onTap: () { + setState(() { + selectedIndex = index; + }); + _showTooltip( + context, + data, + Offset(x, y), + y, + ); + }, + child: Container( + width: 40.rpx, + height: 40.rpx, + color: Colors.transparent, + ), + ), + ); + }).toList(), + ); + }, + ), + ), + ), + ], + ), + ), + Padding( + padding: EdgeInsets.only(left: yAxisWidth), + child: SizedBox( + height: xAxisHeight, + child: Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: List.generate(widget.showLabel.length, (index) { + String label = ''; + if (xLabelIndices.contains(index)) { + DateTime dt = DateTime.fromMillisecondsSinceEpoch( + widget.showLabel[index]['time']); + if (index == 0 || index == totalPoints - 1) { + label = fullFormat.format(dt); + } else { + label = dt.hour.toString(); + } + } + return Expanded( + child: Padding( + padding: EdgeInsets.only(top: 14.rpx), + child: Text( + label, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 18.rpx, + color: themeController.currentColor.sc4, + ), + ), + ), + ); + }), + ), + ), + ), + ], + ), + ); + } +} + +class _DotBarChartPainter extends CustomPainter { + final List> data; + final int yMax; + final int threshold; + final int yLabelsCount; + final double yAxisTopPadding; + final double horizontalPadding; + final int? selectedIndex; + + _DotBarChartPainter({ + required this.data, + required this.yMax, + required this.threshold, + required this.yLabelsCount, + required this.yAxisTopPadding, + required this.horizontalPadding, + this.selectedIndex, + }); + + @override + void paint(Canvas canvas, Size size) { + final double chartWidth = size.width - 2 * horizontalPadding; + final double xStep = data.length > 1 ? chartWidth / (data.length - 1) : 0; + final double chartHeight = size.height; + final double drawableHeight = chartHeight - yAxisTopPadding; + + final Paint thresholdPaint = Paint() + ..style = PaintingStyle.stroke + ..color = stringToColor("#FF7159") + ..strokeWidth = 1.rpx; + + double thresholdY = + yAxisTopPadding + drawableHeight * (1 - threshold / yMax); + + drawDashedLine( + canvas, + Offset(0, thresholdY), + Offset(size.width, thresholdY), + thresholdPaint, + 8.rpx, + 6.rpx, + ); + + final double dotRadius = 13.rpx; + + for (int i = 0; i < data.length; i++) { + int times = data[i]['times'] ?? 0; + double x = horizontalPadding + i * xStep; + double y = yAxisTopPadding + drawableHeight * (1 - times / yMax); + + Paint dotPaint = Paint() + ..style = PaintingStyle.fill + ..color = times >= threshold + ? stringToColor("#FF7159") + : stringToColor("#00C1AA"); + + // Draw a larger circle for selected dot + if (i == selectedIndex) { + Paint borderPaint = Paint() + ..style = PaintingStyle.stroke + ..color = Colors.white + ..strokeWidth = 3.rpx; + + canvas.drawCircle(Offset(x, y), dotRadius + 1.rpx, borderPaint); + } + + canvas.drawCircle(Offset(x, y), dotRadius, dotPaint); + } + + final Paint solidLinePaint = Paint() + ..color = Colors.grey.withOpacity(0.7) + ..strokeWidth = 1.rpx + ..style = PaintingStyle.stroke; + + final Paint dashedLinePaint = Paint() + ..color = Colors.grey.withOpacity(0.4) + ..strokeWidth = 1.rpx + ..style = PaintingStyle.stroke; + + for (int i = 0; i < yLabelsCount; i++) { + double y = yAxisTopPadding + i * (drawableHeight / (yLabelsCount - 1)); + + if (i == yLabelsCount - 1) { + canvas.drawLine(Offset(0, y), Offset(size.width, y), solidLinePaint); + } else { + drawDashedLine( + canvas, + Offset(0, y), + Offset(size.width, y), + dashedLinePaint, + 8.rpx, + 6.rpx, + ); + } + } + } + + void drawDashedLine(Canvas canvas, Offset start, Offset end, Paint paint, + double dashWidth, double gapWidth) { + double dx = start.dx; + final double y = start.dy; + while (dx < end.dx) { + final double nextDx = (dx + dashWidth).clamp(start.dx, end.dx); + canvas.drawLine(Offset(dx, y), Offset(nextDx, y), paint); + dx = nextDx + gapWidth; + } + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => true; +} diff --git a/lib/pages/sleep_report/chart/FatigueCircleIndicator.dart b/lib/pages/sleep_report/chart/FatigueCircleIndicator.dart new file mode 100644 index 0000000..73778b5 --- /dev/null +++ b/lib/pages/sleep_report/chart/FatigueCircleIndicator.dart @@ -0,0 +1,147 @@ +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'; + +class FatigueCircleIndicator extends StatelessWidget { + final Map data; + + const FatigueCircleIndicator({super.key, required this.data}); + + @override + Widget build(BuildContext context) { + final double screenWidth = MediaQuery.of(context).size.width; + final double radius = (screenWidth * 0.127).clamp(95.rpx, double.infinity); + + final double strokeWidth = 14.rpx; + final double backgroundStrokeWidth = 8.rpx; + + final String name = data["name"]; + final Color color = data["color"]; + final int percent = data["percent"]; + final String explain = data["explain"]; + final Color bottomColor = data["bottomColor"]; + + return Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + width: radius * 2, + height: radius * 2, + child: Stack( + alignment: Alignment.center, + children: [ + // 合并绘制背景与进度 + CustomPaint( + size: Size(radius * 2, radius * 2), + painter: _CirclePainter( + percent: percent, + color: color, + bottomColor: bottomColor, + progressStrokeWidth: strokeWidth, + backgroundStrokeWidth: backgroundStrokeWidth, + ), + ), + // 中心文本 + Column( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + '$percent%', + style: TextStyle( + fontSize: AppConstants().normal_text_fontSize, + color: percent > 60 + ? themeController.currentColor.sc9 + : themeController.currentColor.sc3, + ), + ), + SizedBox(height: 4.rpx), + Text( + explain, + style: TextStyle( + fontSize: AppConstants().normal_text_fontSize, + color: percent > 60 + ? themeController.currentColor.sc9 + : themeController.currentColor.sc3, + ), + ), + ], + ), + ], + ), + ), + SizedBox(height: 40.rpx), + Text( + name, + style: TextStyle( + fontSize: AppConstants().normal_text_fontSize, + color: themeController.currentColor.sc3, + ), + ), + ], + ); + } +} + +class _CirclePainter extends CustomPainter { + final int percent; + final Color color; + final Color bottomColor; + final double progressStrokeWidth; + final double backgroundStrokeWidth; + + _CirclePainter({ + required this.percent, + required this.color, + required this.bottomColor, + required this.progressStrokeWidth, + required this.backgroundStrokeWidth, + }); + + @override + void paint(Canvas canvas, Size size) { + final center = Offset(size.width / 2, size.height / 2); + final radius = (size.width - progressStrokeWidth) / 2; + + // 背景环(底色,细) + final backgroundPaint = Paint() + ..color = bottomColor + ..style = PaintingStyle.stroke + ..strokeWidth = backgroundStrokeWidth + ..strokeCap = StrokeCap.round; + + canvas.drawArc( + Rect.fromCircle(center: center, radius: radius), + 90 * 3.1415926 / 180, + 2 * 3.1415926, + false, + backgroundPaint, + ); + + // 进度环(进度色,粗) + final progressPaint = Paint() + ..color = color + ..style = PaintingStyle.stroke + ..strokeWidth = progressStrokeWidth + ..strokeCap = StrokeCap.butt; + + final sweepAngle = 2 * 3.1415926 * (percent / 100); + + canvas.drawArc( + Rect.fromCircle(center: center, radius: radius), + 90 * 3.1415926 / 180, + sweepAngle, + false, + progressPaint, + ); + } + + @override + bool shouldRepaint(_CirclePainter oldDelegate) { + return oldDelegate.percent != percent || + oldDelegate.color != color || + oldDelegate.bottomColor != bottomColor || + oldDelegate.progressStrokeWidth != progressStrokeWidth || + oldDelegate.backgroundStrokeWidth != backgroundStrokeWidth; + } +} diff --git a/lib/pages/sleep_report/chart/HorizontalBarChart.dart b/lib/pages/sleep_report/chart/HorizontalBarChart.dart new file mode 100644 index 0000000..4cb75d9 --- /dev/null +++ b/lib/pages/sleep_report/chart/HorizontalBarChart.dart @@ -0,0 +1,281 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.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/pages/device_bind/componnet/bind_dialog.dart'; + +class HorizontalBarChart extends StatelessWidget { + final List> showLabel; + final bool showPercent; + + const HorizontalBarChart({ + super.key, + required this.showLabel, + this.showPercent = true, + }); + + @override + Widget build(BuildContext context) { + final data = showLabel.map((item) { + return BarData( + label: item['name'], + value: (item['percent'] ?? 0).toDouble(), + color: item['color'] ?? Colors.grey, + explain: item['explain'] ?? '', + ); + }).toList(); + + final double labelWidth = (MediaQuery.of(context).size.width * 0.5).clamp( + MediaQuery.of(context).size.width * 0.08, + MediaQuery.of(context).size.width * 0.22); + + final double barHeight = 24.0.rpx; + final double barSpacing = 40.0.rpx; + final totalHeight = data.length * (barHeight + barSpacing) + 30.rpx; + + return SizedBox( + height: totalHeight, + child: Row( + children: [ + // 左侧标签列 + SizedBox( + width: labelWidth, + child: ListView.builder( + physics: const NeverScrollableScrollPhysics(), + itemCount: data.length, + itemBuilder: (context, index) { + final bar = data[index]; + return Container( + height: barHeight + barSpacing, + alignment: Alignment.centerRight, + child: LabelWithSvg( + label: bar.label, + explain: bar.explain, + ), + ); + }, + ), + ), + SizedBox( + width: 16.rpx, + ), + // 右侧柱状图区 + Expanded( + child: Stack( + children: [ + // 网格线背景层 + CustomPaint( + size: Size(double.infinity, totalHeight), + painter: GridPainter(totalHeight: totalHeight), + ), + + // 柱状图列表 + ListView.builder( + physics: const NeverScrollableScrollPhysics(), + itemCount: data.length, + itemBuilder: (context, index) { + final bar = data[index]; + return SizedBox( + height: barHeight + barSpacing, + child: CustomPaint( + painter: SingleBarPainter( + value: bar.value, + color: bar.color, + showPercent: showPercent, + ), + ), + ); + }, + ) + ], + ), + ), + ], + ), + ); + } +} + +class BarData { + final String label; + final double value; + final Color color; + final String explain; + + BarData({ + required this.label, + required this.value, + required this.color, + required this.explain, + }); +} + +class LabelWithSvg extends StatelessWidget { + final String label; + final String explain; + + const LabelWithSvg({ + super.key, + required this.label, + required this.explain, + }); + + @override + Widget build(BuildContext context) { + final textStyle = + TextStyle(color: themeController.currentColor.sc3, fontSize: 26.rpx); + + return Row( + mainAxisAlignment: MainAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Flexible( + child: Text( + label, + style: textStyle, + overflow: TextOverflow.ellipsis, + ), + ), + // SizedBox(width: 8.rpx), + ClickableContainer( + backgroundColor: Colors.transparent, + highlightColor: Colors.white, + padding: + EdgeInsetsDirectional.fromSTEB(14.rpx, 14.rpx, 14.rpx, 14.rpx), + borderRadius: 0.rpx, + onTap: () { + // Get.toNamed("/deviceShareListPage", arguments: explain); + showTipDialog( + context, + Container( + child: Text( + explain, + style: TextStyle( + fontSize: 26.rpx, + color: themeController.currentColor.sc3, + ), + ), + ), + ); + }, + child: SizedBox( + width: 17.rpx, + height: 17.rpx, + child: SvgPicture.asset( + 'assets/img/icon/explain.svg', + fit: BoxFit.cover, + color: Colors.white, + ), + ), + ), + ], + ); + } +} + +class SingleBarPainter extends CustomPainter { + final double value; + final Color color; + final bool showPercent; + final double maxValue = 100; + + SingleBarPainter({ + required this.value, + required this.color, + required this.showPercent, + }); + + @override + void paint(Canvas canvas, Size size) { + final barHeight = 24.0.rpx; + final rightPadding = 20.0.rpx; + final chartWidth = size.width - rightPadding; + + final left = 0.0; + final right = left + (value / maxValue) * chartWidth; + final rect = Rect.fromLTWH( + left, (size.height - barHeight) / 2, right - left, barHeight); + final paint = Paint()..color = color; + canvas.drawRect(rect, paint); + + if (showPercent) { + final textStyle = + TextStyle(color: themeController.currentColor.sc3, fontSize: 26.rpx); + final textPainter = TextPainter(textDirection: TextDirection.ltr); + textPainter.text = + TextSpan(text: '${value.toStringAsFixed(0)}%', style: textStyle); + textPainter.layout(); + canvas.save(); + canvas.clipRect(Rect.fromLTWH( + left, (size.height - barHeight) / 2, chartWidth, barHeight)); + textPainter.paint( + canvas, + Offset(right + 4.0.rpx, (size.height - textPainter.height) / 2), + ); + canvas.restore(); + } + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => true; +} + +class GridPainter extends CustomPainter { + final double totalHeight; + final int gridCount = 5; + final double maxValue = 100; + final double bottomPadding = 30.0.rpx; + + GridPainter({required this.totalHeight}); + + @override + void paint(Canvas canvas, Size size) { + final Paint gridPaint = Paint() + ..color = Colors.grey.withOpacity(0.3) + ..strokeWidth = 1.0.rpx; + + final double rightPadding = 20.0.rpx; + final double chartWidth = size.width - rightPadding; + final double chartHeight = totalHeight - bottomPadding; + + for (int i = 0; i <= gridCount; i++) { + double dx = (i / gridCount) * chartWidth; + _drawDashedLine( + canvas, Offset(dx, 0), Offset(dx, chartHeight), gridPaint); + + final percent = (i / gridCount) * maxValue; + final TextPainter textPainter = TextPainter( + textDirection: TextDirection.ltr, + text: TextSpan( + text: '${percent.toInt()}', + style: TextStyle(color: Colors.grey, fontSize: 18.rpx), + ), + ); + textPainter.layout(); + textPainter.paint( + canvas, Offset(dx - textPainter.width / 2, chartHeight + 4.0.rpx)); + } + } + + void _drawDashedLine(Canvas canvas, Offset start, Offset end, Paint paint) { + const double dashWidth = 6.0; + const double dashSpace = 4.0; + + final double totalHeight = (end.dy - start.dy).abs(); + double currentY = start.dy; + + while (currentY < end.dy) { + final double nextY = currentY + dashWidth; + canvas.drawLine( + Offset(start.dx, currentY), + Offset(start.dx, nextY > end.dy ? end.dy : nextY), + paint, + ); + currentY = nextY + dashSpace; + } + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => false; +} diff --git a/lib/pages/sleep_report/chart/LineChartByRange.dart b/lib/pages/sleep_report/chart/LineChartByRange.dart new file mode 100644 index 0000000..b2e341b --- /dev/null +++ b/lib/pages/sleep_report/chart/LineChartByRange.dart @@ -0,0 +1,252 @@ +import 'package:flutter/material.dart'; +import 'package:intl/intl.dart'; +import 'package:vbvs_app/common/util/FitTool.dart'; +import 'package:vbvs_app/common/util/MyUtils.dart'; +import 'dart:ui' as ui; +import 'dart:math'; + +class LineChartByRange extends StatelessWidget { + final List> showLabel; + final int startTime; + final int endTime; + + const LineChartByRange({ + Key? key, + required this.showLabel, + required this.startTime, + required this.endTime, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + if (showLabel.isEmpty) return const SizedBox(); + + int maxTimes = + showLabel.map((e) => e['times'] ?? 0).reduce((a, b) => a > b ? a : b); + int yMax = (maxTimes / 10).ceil() * 10; + if (yMax == 0) yMax = 10; + + DateTime minTime = DateTime.fromMillisecondsSinceEpoch(startTime); + DateTime maxTime = DateTime.fromMillisecondsSinceEpoch(endTime); + + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + height: 500.rpx, + child: CustomPaint( + size: Size(double.infinity, 500.rpx), + painter: _LineChartByRangePainter( + data: showLabel, + yMax: yMax, + minTime: minTime, + maxTime: maxTime, + ), + ), + ), + ], + ); + } +} + +class _LineChartByRangePainter extends CustomPainter { + final List> data; + final int yMax; + final DateTime minTime; + final DateTime maxTime; + + _LineChartByRangePainter({ + required this.data, + required this.yMax, + required this.minTime, + required this.maxTime, + }); + + @override + void paint(Canvas canvas, Size size) { + double padding = 20.rpx; + double labelInset = 12.rpx; // X轴标签缩进距离 + + // 绘图X轴起止点,考虑内缩labelInset + final double xStart = padding + labelInset; + final double xEnd = size.width - padding - labelInset; + final double chartWidth = xEnd - xStart; + + double chartHeight = size.height - 30.rpx; + + int totalDuration = + maxTime.millisecondsSinceEpoch - minTime.millisecondsSinceEpoch; + if (totalDuration <= 0) return; + + Paint linePaint = Paint() + ..style = PaintingStyle.stroke + ..strokeWidth = 3.rpx + ..color = stringToColor("#00C1AA") + ..strokeCap = StrokeCap.round; + + Paint fillCirclePaint = Paint() + ..style = PaintingStyle.fill + ..color = stringToColor("#00C1AA"); + + // 1. 先绘制数据线段及起止点圆点 + for (var item in data) { + int start = item['startTime']; + int end = item['endTime']; + int times = item['times']; + + double startX = xStart + + chartWidth * (start - minTime.millisecondsSinceEpoch) / totalDuration; + double endX = xStart + + chartWidth * (end - minTime.millisecondsSinceEpoch) / totalDuration; + double y = chartHeight * (1 - times / yMax); + + // 画线段 + canvas.drawLine(Offset(startX, y), Offset(endX, y), linePaint); + + // 画起点圆点 + canvas.drawCircle(Offset(startX, y), 4.rpx, fillCirclePaint); + + // 画终点圆点 + canvas.drawCircle(Offset(endX, y), 4.rpx, fillCirclePaint); + } + + // 2. Y轴辅助线及文字 + Paint axisPaint = Paint() + ..color = Colors.grey.withOpacity(0.4) + ..strokeWidth = 1.rpx; + + for (int i = 0; i <= 6; i++) { + double y = chartHeight * i / 6; + + if (i == 6) { + // 实线 + canvas.drawLine(Offset(xStart, y), Offset(xEnd, y), axisPaint); + } else { + // 虚线 + drawDashedLine( + canvas, + Offset(xStart, y), + Offset(xEnd, y), + axisPaint, + dashWidth: 8.rpx, + dashSpace: 6.rpx, + ); + } + + // Y轴文字 + TextPainter tp = TextPainter( + text: TextSpan( + text: '${yMax - (yMax * i / 6).round()}', + style: TextStyle( + fontSize: 18.rpx, color: themeController.currentColor.sc4), + ), + textDirection: ui.TextDirection.ltr, + ); + tp.layout(); + tp.paint(canvas, Offset(0, y - tp.height / 2)); + } + + // 3. X轴线 + canvas.drawLine( + Offset(xStart, chartHeight), Offset(xEnd, chartHeight), axisPaint); + + // 4. 画X轴时间点对应的垂直虚线辅助线 + int totalHours = maxTime.difference(minTime).inHours; + int startHour = minTime.hour; + + for (int i = 1; i < totalHours; i++) { + double x = xStart + chartWidth * i / totalHours; + + // 垂直虚线 + drawDashedLine( + canvas, + Offset(x, 0), + Offset(x, chartHeight), + axisPaint, + dashWidth: 4.rpx, + dashSpace: 4.rpx, + ); + } + + // 5. 画左侧完整时分 (HH:mm),往内缩 labelInset + String leftLabel = DateFormat('HH:mm').format(minTime); + TextPainter leftTp = TextPainter( + text: TextSpan( + text: leftLabel, + style: TextStyle( + fontSize: 18.rpx, + color: themeController.currentColor.sc4, + ), + ), + textDirection: ui.TextDirection.ltr, + ); + leftTp.layout(); + leftTp.paint(canvas, + Offset(padding + labelInset - leftTp.width / 2, chartHeight + 8.rpx)); + + // 6. 画右侧完整时分 (HH:mm),往内缩 labelInset + String rightLabel = DateFormat('HH:mm').format(maxTime); + TextPainter rightTp = TextPainter( + text: TextSpan( + text: rightLabel, + style: TextStyle( + fontSize: 18.rpx, + color: themeController.currentColor.sc4, + ), + ), + textDirection: ui.TextDirection.ltr, + ); + rightTp.layout(); + rightTp.paint( + canvas, + Offset(size.width - padding - labelInset - rightTp.width / 2, + chartHeight + 8.rpx)); + + // 7. 中间小时数字(23, 0, 1, 2, ...) + for (int i = 1; i < totalHours; i++) { + double x = xStart + chartWidth * i / totalHours; + + int hourLabelNum = (startHour + i) % 24; + String hourLabel = '$hourLabelNum'; + + TextPainter tp = TextPainter( + text: TextSpan( + text: hourLabel, + style: TextStyle( + fontSize: 18.rpx, + color: themeController.currentColor.sc4, + ), + ), + textDirection: ui.TextDirection.ltr, + ); + tp.layout(); + + tp.paint(canvas, Offset(x - tp.width / 2, chartHeight + 8.rpx)); + } + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => true; + + void drawDashedLine( + Canvas canvas, + Offset start, + Offset end, + Paint paint, { + required double dashWidth, + required double dashSpace, + }) { + final dx = end.dx - start.dx; + final dy = end.dy - start.dy; + final distance = sqrt(dx * dx + dy * dy); + final direction = Offset(dx / distance, dy / distance); + + double drawn = 0; + while (drawn < distance) { + final from = start + direction * drawn; + final to = start + direction * (drawn + dashWidth).clamp(0, distance); + canvas.drawLine(from, to, paint); + drawn += dashWidth + dashSpace; + } + } +} diff --git a/lib/pages/sleep_report/component/SegmentedCirclePainter.dart b/lib/pages/sleep_report/chart/SegmentedCirclePainter.dart similarity index 100% rename from lib/pages/sleep_report/component/SegmentedCirclePainter.dart rename to lib/pages/sleep_report/chart/SegmentedCirclePainter.dart diff --git a/lib/pages/sleep_report/chart/StatusBarWithIndicator.dart b/lib/pages/sleep_report/chart/StatusBarWithIndicator.dart new file mode 100644 index 0000000..4632c38 --- /dev/null +++ b/lib/pages/sleep_report/chart/StatusBarWithIndicator.dart @@ -0,0 +1,112 @@ +import 'package:flutter/material.dart'; +import 'package:vbvs_app/common/util/FitTool.dart'; + +class StatusBarWithIndicator extends StatelessWidget { + final int selectKey; + final List> showLabel; + final IconData icon; + final double gap; // 每段之间的间距 + + const StatusBarWithIndicator({ + super.key, + required this.selectKey, + required this.showLabel, + this.icon = Icons.favorite, + this.gap = 8.0, // 默认 8.rpx 间距 + }); + + @override + Widget build(BuildContext context) { + return LayoutBuilder(builder: (context, constraints) { + final totalWidth = constraints.maxWidth; + final itemCount = showLabel.length; + + // 每条线的宽度 = (总宽度 - 总间隔)/ 项数 + final totalGap = (itemCount - 1) * gap.rpx; + final itemWidth = (totalWidth - totalGap) / itemCount; + + // 找到选中项的 index + final selectedIndex = showLabel.indexWhere((e) => e['key'] == selectKey); + final iconLeft = selectedIndex >= 0 + ? selectedIndex * (itemWidth + gap.rpx) + itemWidth / 2 + : 0.0; + + return SizedBox( + width: double.infinity, + child: Stack( + clipBehavior: Clip.none, + children: [ + if (selectedIndex >= 0) + Positioned( + left: iconLeft, + top: -20.rpx, + child: Transform.translate( + offset: Offset(-22.5.rpx, 0), // 图片宽度 45.rpx,居中偏移 + child: Container( + width: 45.rpx, + height: 76.rpx, + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage('assets/img/tip_arrow.gif'), + fit: BoxFit.cover, + ), + ), + ), + ), + ), + Padding( + padding: EdgeInsets.only(top: 50.rpx), + child: Column( + children: [ + // 条形段(带间距) + Row( + children: showLabel.asMap().entries.map((entry) { + int index = entry.key; + var item = entry.value; + + return Container( + width: itemWidth, + height: 15.rpx, + margin: EdgeInsets.only( + left: index == 0 ? 0 : gap.rpx, + ), + decoration: BoxDecoration( + color: item['color'], + borderRadius: BorderRadius.circular(0.rpx), + ), + ); + }).toList(), + ), + SizedBox(height: 12.rpx), + // 名称文字 + Row( + children: showLabel.asMap().entries.map((entry) { + int index = entry.key; + var item = entry.value; + + return Container( + width: itemWidth, + margin: EdgeInsets.only( + left: index == 0 ? 0 : gap.rpx, + ), + alignment: Alignment.center, + child: Text( + item['name'], + style: TextStyle( + fontSize: 24.rpx, + color: Colors.white, + ), + textAlign: TextAlign.center, + ), + ); + }).toList(), + ), + ], + ), + ), + ], + ), + ); + }); + } +} diff --git a/lib/pages/sleep_report/chart/VerticalBarList.dart b/lib/pages/sleep_report/chart/VerticalBarList.dart new file mode 100644 index 0000000..f814228 --- /dev/null +++ b/lib/pages/sleep_report/chart/VerticalBarList.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; +import 'package:vbvs_app/common/util/FitTool.dart'; + +class VerticalBarList extends StatelessWidget { + final List> showLabel; + final double maxBarHeight; + final double barWidth; + + const VerticalBarList({ + super.key, + required this.showLabel, + this.maxBarHeight = 100.0, // 柱子的最大高度 + this.barWidth = 20.0, // 每根柱子的宽度 + }); + + @override + Widget build(BuildContext context) { + return Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: showLabel.map((item) { + final percent = item['percent'] ?? 0; + final color = item['color'] ?? Colors.grey; + final name = item['name'] ?? ''; + + final barHeight = maxBarHeight * (percent / 100.0); + + return Padding( + padding: EdgeInsets.symmetric(horizontal: 8.rpx), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // 柱子 + Container( + width: barWidth.rpx, + height: barHeight.rpx, + decoration: BoxDecoration( + color: color, + borderRadius: BorderRadius.circular(4.rpx), + ), + ), + SizedBox(height: 8.rpx), + // 文字竖排 + Text( + name, + style: TextStyle( + fontSize: 24.rpx, + color: Colors.white, + ), + textAlign: TextAlign.center, + ), + ], + ), + ); + }).toList(), + ); + } +} diff --git a/lib/pages/sleep_report/component/BreathPauseWidget.dart b/lib/pages/sleep_report/component/BreathPauseWidget.dart new file mode 100644 index 0000000..5c20ced --- /dev/null +++ b/lib/pages/sleep_report/component/BreathPauseWidget.dart @@ -0,0 +1,141 @@ +import 'package:ef/ef.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.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/pages/device_bind/componnet/bind_dialog.dart'; +import 'package:vbvs_app/pages/sleep_report/chart/DotBarChart.dart'; + +class BreathPauseWidget extends StatefulWidget { + BreathPauseWidget({super.key}); + + @override + State createState() => _BreathPauseWidgetState(); +} + +class _BreathPauseWidgetState 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) { + // var showLabel = [ + // {"time": 1744547251000, "times": 20}, + // {"time": 1744550851000, "times": 50}, + // {"time": 1744554451000, "times": 20}, + // {"time": 1744558051000, "times": 30}, + // {"time": 1744561651000, "times": 20}, + // {"time": 1744565251000, "times": 10}, + // {"time": 1744568851000, "times": 20}, + // {"time": 1744572451000, "times": 20}, + // {"time": 1744583251000, "times": 100}, + // {"time": 1744586851000, "times": 20}, + // ]; + var showLabel = [ + {"time": 1744547251000, "times": 25}, + {"time": 1744550851000, "times": 27}, + {"time": 1744554451000, "times": 40}, + {"time": 1744558051000, "times": 28}, + {"time": 1744561651000, "times": 15}, + {"time": 1744565251000, "times": 48}, + {"time": 1744568851000, "times": 25}, + {"time": 1744572451000, "times": 17}, + {"time": 1744583251000, "times": 35}, + {"time": 1744586851000, "times": 40}, + ]; + var threshold = 40; + var startTime = 1744641151000; + var endTime = 1744677151000; + 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, 0.rpx), + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "呼吸暂停监测".tr, + style: TextStyle( + color: themeController.currentColor.sc3, + fontSize: AppConstants().title_text_fontSize), + ), + ClickableContainer( + backgroundColor: Colors.transparent, + highlightColor: Colors.white, // 或设置为你需要的水波纹颜色 + padding: EdgeInsetsDirectional.fromSTEB( + 14.rpx, 0.rpx, 14.rpx, 0), // + borderRadius: 0.rpx, // 圆形点击区域 + onTap: () { + showTipDialog( + context, + Container( + child: Text( + "呼吸暂停监测介绍。", + style: TextStyle( + fontSize: 26.rpx, + color: themeController.currentColor.sc3, + ), + ), + ), + ); + }, + child: Container( + padding: EdgeInsetsDirectional.fromSTEB( + 0, 0.rpx, 0.rpx, 0), // 外部 padding 移到内部 + width: 28.rpx, + height: 28.rpx, + child: SvgPicture.asset( + 'assets/img/icon/explain.svg', + fit: BoxFit.cover, + color: themeController.currentColor.sc4, + ), + ), + ), + ], + ), + ), + SizedBox( + height: 32.rpx, + ), + Padding( + padding: + EdgeInsetsDirectional.fromSTEB(0.rpx, 0.rpx, 0.rpx, 0.rpx), + child: DotBarChart( + showLabel: showLabel, + threshold: threshold, + startTime: startTime, + endTime: endTime, + ), + ), + SizedBox( + height: 52.rpx, + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/sleep_report/component/DiseasePercentsWidget.dart b/lib/pages/sleep_report/component/DiseasePercentsWidget.dart new file mode 100644 index 0000000..f37fe16 --- /dev/null +++ b/lib/pages/sleep_report/component/DiseasePercentsWidget.dart @@ -0,0 +1,152 @@ +import 'package:ef/ef.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.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/pages/device_bind/componnet/bind_dialog.dart'; +import 'package:vbvs_app/pages/sleep_report/chart/HorizontalBarChart.dart'; + +class DiseasePercentsWidget extends StatefulWidget { + DiseasePercentsWidget({super.key}); + + @override + State createState() => _DiseasePercentsWidgetState(); +} + +class _DiseasePercentsWidgetState extends State { + var showLabel = [ + { + "key": 1, + "name": "心脏病", + "color": stringToColor("#00C1AA"), + "percent": 45, + "explain": "心脏病是指心脏的结构或功能异常,可能导致心脏无法有效地泵血。" + }, + { + "key": 2, + "name": "高血压", + "color": stringToColor("#00C1AA"), + "percent": 32, + "explain": "高血压是指血液在动脉中流动时对血管壁施加的压力过高。" + }, + { + "key": 3, + "name": "糖尿病", + "color": stringToColor("#00C1AA"), + "percent": 50, + "explain": "糖尿病是一种代谢性疾病,导致血糖水平异常升高。" + }, + { + "key": 4, + "name": "甲亢", + "color": stringToColor("#FF7159"), + "percent": 80, + "explain": "甲亢是指甲状腺分泌过多的甲状腺激素,导致新陈代谢加速。" + }, + { + "key": 5, + "name": "消化系统", + "color": stringToColor("#00C1AA"), + "percent": 12, + "explain": "消化系统是身体中处理食物的机构,是造成疾病和疾病症状的来源。", + }, + { + "key": 6, + "name": "呼吸系统", + "color": stringToColor("#00C1AA"), + "percent": 62, + "explain": "呼吸系统是负责气体交换的器官系统,包括鼻、喉、气管和肺等。", + }, + ]; + @override + void setState(VoidCallback callback) { + super.setState(callback); + } + + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + + @override + Widget build(BuildContext context) { + 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: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "慢性病风险指数".tr, + style: TextStyle( + color: themeController.currentColor.sc3, + fontSize: AppConstants().title_text_fontSize), + ), + ClickableContainer( + backgroundColor: Colors.transparent, + highlightColor: Colors.white, // 或设置为你需要的水波纹颜色 + padding: EdgeInsetsDirectional.fromSTEB( + 14.rpx, 0.rpx, 14.rpx, 0), // + borderRadius: 0.rpx, // 圆形点击区域 + onTap: () { + // 你的点击逻辑 + showTipDialog( + context, + Container( + child: Text( + "慢性病风险指数介绍。", + style: TextStyle( + fontSize: 26.rpx, + color: themeController.currentColor.sc3, + ), + ), + ), + ); + }, + child: Container( + padding: EdgeInsetsDirectional.fromSTEB( + 0, 0.rpx, 0.rpx, 0), // 外部 padding 移到内部 + width: 28.rpx, + height: 28.rpx, + child: SvgPicture.asset( + 'assets/img/icon/explain.svg', + fit: BoxFit.cover, + color: themeController.currentColor.sc4, + ), + ), + ), + ], + ), + ), + SizedBox( + height: 34.rpx, + ), + Container( + child: HorizontalBarChart( + showLabel: showLabel, + showPercent: true, + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/sleep_report/component/HeartHealthWidget.dart b/lib/pages/sleep_report/component/HeartHealthWidget.dart new file mode 100644 index 0000000..3123da4 --- /dev/null +++ b/lib/pages/sleep_report/component/HeartHealthWidget.dart @@ -0,0 +1,148 @@ +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/pages/device_bind/componnet/bind_dialog.dart'; +import 'package:vbvs_app/pages/sleep_report/chart/FatigueCircleIndicator.dart'; + +class HeartHealthWidget extends StatefulWidget { + HeartHealthWidget({super.key}); + + @override + State createState() => _HeartHealthWidgetState(); +} + +class _HeartHealthWidgetState 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) { + var showLabel = [ + { + "name": "焦虑抑郁", + "color": Color(0xFF4CAF50), + "percent": "7%", + "explain": "低风险" + }, + { + "name": "过度疲劳", + "color": stringToColor("#FF7159"), + "percent": "69%", + "explain": "高风险" + }, + ]; + 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, 0.rpx), + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "心理健康评估".tr, + style: TextStyle( + color: themeController.currentColor.sc3, + fontSize: AppConstants().title_text_fontSize), + ), + ClickableContainer( + backgroundColor: Colors.transparent, + highlightColor: Colors.white, // 或设置为你需要的水波纹颜色 + padding: EdgeInsetsDirectional.fromSTEB( + 14.rpx, 0.rpx, 14.rpx, 0), // + borderRadius: 0.rpx, // 圆形点击区域 + onTap: () { + showTipDialog( + context, + Container( + child: Text( + "心理健康评估介绍。", + style: TextStyle( + fontSize: 26.rpx, + color: themeController.currentColor.sc3, + ), + ), + ), + ); + }, + child: Container( + padding: EdgeInsetsDirectional.fromSTEB( + 0, 0.rpx, 0.rpx, 0), // 外部 padding 移到内部 + width: 28.rpx, + height: 28.rpx, + child: SvgPicture.asset( + 'assets/img/icon/explain.svg', + fit: BoxFit.cover, + color: themeController.currentColor.sc4, + ), + ), + ), + ], + ), + ), + SizedBox( + height: 104.rpx, + ), + Padding( + padding: + EdgeInsetsDirectional.fromSTEB(30.rpx, 0.rpx, 30.rpx, 0.rpx), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + FatigueCircleIndicator( + data: { + "name": "焦虑抑郁", + "color": stringToColor("#00C1AA"), + "percent": 7, + "explain": "低风险", + "bottomColor": Colors.grey, + }, + ), + FatigueCircleIndicator( + data: { + "name": "过度疲劳", + "color": stringToColor("#FF7159"), + "percent": 69, + "explain": "高风险", + "bottomColor": Colors.grey, + }, + ), + ].divide(SizedBox( + width: 110.rpx, + )), + ), + ), + SizedBox( + height: 72.rpx, + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/sleep_report/component/HeartPointWidget.dart b/lib/pages/sleep_report/component/HeartPointWidget.dart new file mode 100644 index 0000000..9073894 --- /dev/null +++ b/lib/pages/sleep_report/component/HeartPointWidget.dart @@ -0,0 +1,117 @@ +import 'package:ef/ef.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.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/pages/device_bind/componnet/bind_dialog.dart'; +import 'package:vbvs_app/pages/sleep_report/chart/StatusBarWithIndicator.dart'; + +class HeartPointWidget extends StatefulWidget { + HeartPointWidget({super.key}); + + @override + State createState() => _HeartPointWidgetState(); +} + +class _HeartPointWidgetState 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) { + 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: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "心率散点图".tr, + style: TextStyle( + color: themeController.currentColor.sc3, + fontSize: AppConstants().title_text_fontSize), + ), + ClickableContainer( + backgroundColor: Colors.transparent, + highlightColor: Colors.white, // 或设置为你需要的水波纹颜色 + padding: EdgeInsetsDirectional.fromSTEB( + 14.rpx, 0.rpx, 14.rpx, 0), // + borderRadius: 0.rpx, // 圆形点击区域 + onTap: () { + showTipDialog( + context, + Container( + child: Text( + "心率散点图介绍。", + style: TextStyle( + fontSize: 26.rpx, + color: themeController.currentColor.sc3, + ), + ), + ), + ); + }, + child: Container( + padding: EdgeInsetsDirectional.fromSTEB( + 0, 0.rpx, 0.rpx, 0), // 外部 padding 移到内部 + width: 28.rpx, + height: 28.rpx, + child: SvgPicture.asset( + 'assets/img/icon/explain.svg', + fit: BoxFit.cover, + color: themeController.currentColor.sc4, + ), + ), + ), + ], + ), + ), + SizedBox( + height: 83.rpx, + ), + Padding( + padding: + EdgeInsetsDirectional.fromSTEB(30.rpx, 0.rpx, 30.rpx, 0.rpx), + child: StatusBarWithIndicator( + selectKey: 2, + showLabel: [ + {"key": 1, "name": "正常", "color": Color(0xFF4CAF50)}, + {"key": 2, "name": "一般", "color": Color(0xFF8BC34A)}, + {"key": 3, "name": "注意", "color": Color(0xFFFFC107)}, + {"key": 4, "name": "警告", "color": Color(0xFFF44336)}, + ], + ), + ), + SizedBox( + height: 56.rpx, + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/sleep_report/component/HrvWidget.dart b/lib/pages/sleep_report/component/HrvWidget.dart new file mode 100644 index 0000000..338768f --- /dev/null +++ b/lib/pages/sleep_report/component/HrvWidget.dart @@ -0,0 +1,117 @@ +import 'package:ef/ef.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.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/pages/device_bind/componnet/bind_dialog.dart'; +import 'package:vbvs_app/pages/sleep_report/chart/StatusBarWithIndicator.dart'; + +class HrvWidget extends StatefulWidget { + HrvWidget({super.key}); + + @override + State createState() => _HrvWidgetState(); +} + +class _HrvWidgetState 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) { + 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: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "心率变异性(HRV)".tr, + style: TextStyle( + color: themeController.currentColor.sc3, + fontSize: AppConstants().title_text_fontSize), + ), + ClickableContainer( + backgroundColor: Colors.transparent, + highlightColor: Colors.white, // 或设置为你需要的水波纹颜色 + padding: EdgeInsetsDirectional.fromSTEB( + 14.rpx, 0.rpx, 14.rpx, 0), // + borderRadius: 0.rpx, // 圆形点击区域 + onTap: () { + showTipDialog( + context, + Container( + child: Text( + "心率变异性(HRV)介绍。", + style: TextStyle( + fontSize: 26.rpx, + color: themeController.currentColor.sc3, + ), + ), + ), + ); + }, + child: Container( + padding: EdgeInsetsDirectional.fromSTEB( + 0, 0.rpx, 0.rpx, 0), // 外部 padding 移到内部 + width: 28.rpx, + height: 28.rpx, + child: SvgPicture.asset( + 'assets/img/icon/explain.svg', + fit: BoxFit.cover, + color: themeController.currentColor.sc4, + ), + ), + ), + ], + ), + ), + SizedBox( + height: 83.rpx, + ), + Padding( + padding: + EdgeInsetsDirectional.fromSTEB(30.rpx, 0.rpx, 30.rpx, 0.rpx), + child: StatusBarWithIndicator( + selectKey: 2, + showLabel: [ + {"key": 1, "name": "正常", "color": Color(0xFF4CAF50)}, + {"key": 2, "name": "一般", "color": Color(0xFF8BC34A)}, + {"key": 3, "name": "注意", "color": Color(0xFFFFC107)}, + {"key": 4, "name": "警告", "color": Color(0xFFF44336)}, + ], + ), + ), + SizedBox( + height: 56.rpx, + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/sleep_report/component/SkinPercentWidget.dart b/lib/pages/sleep_report/component/SkinPercentWidget.dart new file mode 100644 index 0000000..e4ab1db --- /dev/null +++ b/lib/pages/sleep_report/component/SkinPercentWidget.dart @@ -0,0 +1,117 @@ +import 'package:ef/ef.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.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/pages/device_bind/componnet/bind_dialog.dart'; +import 'package:vbvs_app/pages/sleep_report/chart/StatusBarWithIndicator.dart'; + +class SkinPercentWidget extends StatefulWidget { + SkinPercentWidget({super.key}); + + @override + State createState() => _SkinPercentWidgetState(); +} + +class _SkinPercentWidgetState 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) { + 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: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "皮肤指数".tr, + style: TextStyle( + color: themeController.currentColor.sc3, + fontSize: AppConstants().title_text_fontSize), + ), + ClickableContainer( + backgroundColor: Colors.transparent, + highlightColor: Colors.white, // 或设置为你需要的水波纹颜色 + padding: EdgeInsetsDirectional.fromSTEB( + 14.rpx, 0.rpx, 14.rpx, 0), // + borderRadius: 0.rpx, // 圆形点击区域 + onTap: () { + showTipDialog( + context, + Container( + child: Text( + "皮肤指数介绍。", + style: TextStyle( + fontSize: 26.rpx, + color: themeController.currentColor.sc3, + ), + ), + ), + ); + }, + child: Container( + padding: EdgeInsetsDirectional.fromSTEB( + 0, 0.rpx, 0.rpx, 0), // 外部 padding 移到内部 + width: 28.rpx, + height: 28.rpx, + child: SvgPicture.asset( + 'assets/img/icon/explain.svg', + fit: BoxFit.cover, + color: themeController.currentColor.sc4, + ), + ), + ), + ], + ), + ), + SizedBox( + height: 83.rpx, + ), + Padding( + padding: + EdgeInsetsDirectional.fromSTEB(30.rpx, 0.rpx, 30.rpx, 0.rpx), + child: StatusBarWithIndicator( + selectKey: 2, + showLabel: [ + {"key": 1, "name": "正常", "color": Color(0xFF4CAF50)}, + {"key": 2, "name": "一般", "color": Color(0xFF8BC34A)}, + {"key": 3, "name": "注意", "color": Color(0xFFFFC107)}, + {"key": 4, "name": "警告", "color": Color(0xFFF44336)}, + ], + ), + ), + SizedBox( + height: 56.rpx, + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/sleep_report/component/SleepScoreWidget.dart b/lib/pages/sleep_report/component/SleepScoreWidget.dart index 111de20..d12c1c8 100644 --- a/lib/pages/sleep_report/component/SleepScoreWidget.dart +++ b/lib/pages/sleep_report/component/SleepScoreWidget.dart @@ -3,7 +3,7 @@ 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/pages/sleep_report/component/SegmentedCirclePainter.dart'; +import 'package:vbvs_app/pages/sleep_report/chart/SegmentedCirclePainter.dart'; class SleepScoreWidget extends StatefulWidget { const SleepScoreWidget({super.key}); diff --git a/lib/pages/sleep_report/component/SnoreViewWidget.dart b/lib/pages/sleep_report/component/SnoreViewWidget.dart new file mode 100644 index 0000000..1a2fe58 --- /dev/null +++ b/lib/pages/sleep_report/component/SnoreViewWidget.dart @@ -0,0 +1,124 @@ +import 'package:ef/ef.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.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/pages/device_bind/componnet/bind_dialog.dart'; +import 'package:vbvs_app/pages/sleep_report/chart/LineChartByRange.dart'; + +class SnoreViewWidgetWidget extends StatefulWidget { + SnoreViewWidgetWidget({super.key}); + + @override + State createState() => _SnoreViewWidgetWidgetState(); +} + +class _SnoreViewWidgetWidgetState 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) { + var showLabel = [ + {"startTime": 1744644751000, "endTime": 1744648351000, "times": 25}, + {"startTime": 1744650031000, "endTime": 1744653631000, "times": 27}, + {"startTime": 1744655011000, "endTime": 1744662211000, "times": 60}, + // {"startTime": 1744657291000, "endTime": 1744657411000, "times": 28}, + // {"startTime": 1744661011000, "endTime": 1744661131000, "times": 15}, + // {"startTime": 1744668331000, "endTime": 1744668511000, "times": 48}, + // {"startTime": 1744673431000, "endTime": 1744673551000, "times": 25}, + ]; + var startTime = 1744641151000; + var endTime = 1744677151000; + 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, 0.rpx), + child: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "呼吸暂停监测".tr, + style: TextStyle( + color: themeController.currentColor.sc3, + fontSize: AppConstants().title_text_fontSize), + ), + ClickableContainer( + backgroundColor: Colors.transparent, + highlightColor: Colors.white, // 或设置为你需要的水波纹颜色 + padding: EdgeInsetsDirectional.fromSTEB( + 14.rpx, 0.rpx, 14.rpx, 0), // + borderRadius: 0.rpx, // 圆形点击区域 + onTap: () { + showTipDialog( + context, + Container( + child: Text( + "呼吸暂停监测介绍。", + style: TextStyle( + fontSize: 26.rpx, + color: themeController.currentColor.sc3, + ), + ), + ), + ); + }, + child: Container( + padding: EdgeInsetsDirectional.fromSTEB( + 0, 0.rpx, 0.rpx, 0), // 外部 padding 移到内部 + width: 28.rpx, + height: 28.rpx, + child: SvgPicture.asset( + 'assets/img/icon/explain.svg', + fit: BoxFit.cover, + color: themeController.currentColor.sc4, + ), + ), + ), + ], + ), + ), + SizedBox( + height: 32.rpx, + ), + Padding( + padding: + EdgeInsetsDirectional.fromSTEB(0.rpx, 0.rpx, 0.rpx, 0.rpx), + child: LineChartByRange( + showLabel: showLabel, + startTime: startTime, + endTime: endTime, + ), + ), + SizedBox( + height: 52.rpx, + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/sleep_report/component/ZiZhuShenJingPercentWidget.dart b/lib/pages/sleep_report/component/ZiZhuShenJingPercentWidget.dart new file mode 100644 index 0000000..f09a8e4 --- /dev/null +++ b/lib/pages/sleep_report/component/ZiZhuShenJingPercentWidget.dart @@ -0,0 +1,119 @@ +import 'package:ef/ef.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_svg/svg.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/pages/device_bind/componnet/bind_dialog.dart'; +import 'package:vbvs_app/pages/sleep_report/chart/StatusBarWithIndicator.dart'; + +class ZiZhuShenJingPercentWidget extends StatefulWidget { + ZiZhuShenJingPercentWidget({super.key}); + + @override + State createState() => + _ZiZhuShenJingPercentWidgetState(); +} + +class _ZiZhuShenJingPercentWidgetState + 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) { + 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: Column( + mainAxisSize: MainAxisSize.max, + children: [ + Container( + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Text( + "自主神经平衡指数".tr, + style: TextStyle( + color: themeController.currentColor.sc3, + fontSize: AppConstants().title_text_fontSize), + ), + ClickableContainer( + backgroundColor: Colors.transparent, + highlightColor: Colors.white, // 或设置为你需要的水波纹颜色 + padding: EdgeInsetsDirectional.fromSTEB( + 14.rpx, 0.rpx, 14.rpx, 0), // + borderRadius: 0.rpx, // 圆形点击区域 + onTap: () { + showTipDialog( + context, + Container( + child: Text( + "自主神经平衡指数监测介绍。", + style: TextStyle( + fontSize: 26.rpx, + color: themeController.currentColor.sc3, + ), + ), + ), + ); + }, + child: Container( + padding: EdgeInsetsDirectional.fromSTEB( + 0, 0.rpx, 0.rpx, 0), // 外部 padding 移到内部 + width: 28.rpx, + height: 28.rpx, + child: SvgPicture.asset( + 'assets/img/icon/explain.svg', + fit: BoxFit.cover, + color: themeController.currentColor.sc4, + ), + ), + ), + ], + ), + ), + SizedBox( + height: 83.rpx, + ), + Padding( + padding: + EdgeInsetsDirectional.fromSTEB(30.rpx, 0.rpx, 30.rpx, 0.rpx), + child: StatusBarWithIndicator( + selectKey: 3, + showLabel: [ + {"key": 1, "name": "正常", "color": Color(0xFF4CAF50)}, + {"key": 2, "name": "一般", "color": Color(0xFF8BC34A)}, + {"key": 3, "name": "注意", "color": Color(0xFFFFC107)}, + {"key": 4, "name": "警告", "color": Color(0xFFF44336)}, + ], + ), + ), + SizedBox( + height: 56.rpx, + ), + ], + ), + ), + ); + } +} diff --git a/lib/pages/sleep_report/new_sleep_report_page.dart b/lib/pages/sleep_report/new_sleep_report_page.dart index f588ffd..45200b7 100644 --- a/lib/pages/sleep_report/new_sleep_report_page.dart +++ b/lib/pages/sleep_report/new_sleep_report_page.dart @@ -9,7 +9,14 @@ import 'package:vbvs_app/component/tool/ClickableContainer.dart'; import 'package:vbvs_app/controller/date/CalendarController.dart'; import 'package:vbvs_app/controller/sleep/sleep_report_controller.dart'; import 'package:vbvs_app/pages/common/selectDialog.dart'; +import 'package:vbvs_app/pages/sleep_report/component/BreathPauseWidget.dart'; +import 'package:vbvs_app/pages/sleep_report/component/DiseasePercentsWidget.dart'; +import 'package:vbvs_app/pages/sleep_report/component/HeartHealthWidget.dart'; +import 'package:vbvs_app/pages/sleep_report/component/HeartPointWidget.dart'; +import 'package:vbvs_app/pages/sleep_report/component/SkinPercentWidget.dart'; import 'package:vbvs_app/pages/sleep_report/component/SleepScoreWidget.dart'; +import 'package:vbvs_app/pages/sleep_report/component/SnoreViewWidget.dart'; +import 'package:vbvs_app/pages/sleep_report/component/ZiZhuShenJingPercentWidget.dart'; class NewSleepReportPage extends StatefulWidget { var date; @@ -32,6 +39,7 @@ class _NewSleepReportPageState extends State { DateTime.fromMillisecondsSinceEpoch(widget.date); sleepReportController.selectedDate.value = DateTime.fromMillisecondsSinceEpoch(widget.date); + sleepReportController.model.type = 1; super.initState(); } @@ -309,7 +317,7 @@ class _NewSleepReportPageState extends State { width: double.infinity, child: Padding( padding: EdgeInsetsDirectional.fromSTEB( - 30.rpx, 57.rpx, 30.rpx, 57.rpx), + 30.rpx, 32.rpx, 30.rpx, 32.rpx), child: Obx(() { var date = sleepReportController.selectedDate; return getTimeWidget(); @@ -497,7 +505,65 @@ class _NewSleepReportPageState extends State { child: SleepScoreWidget(), ), ), - ], + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 30.rpx, 0.rpx, 30.rpx, 0), + child: Container( + width: double.infinity, + child: HeartPointWidget(), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 30.rpx, 0.rpx, 30.rpx, 0), + child: Container( + width: double.infinity, + child: SnoreViewWidgetWidget(), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 30.rpx, 0.rpx, 30.rpx, 0), + child: Container( + width: double.infinity, + child: BreathPauseWidget(), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 30.rpx, 0.rpx, 30.rpx, 0), + child: Container( + width: double.infinity, + child: HeartHealthWidget(), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 30.rpx, 0.rpx, 30.rpx, 0), + child: Container( + width: double.infinity, + child: DiseasePercentsWidget(), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 30.rpx, 0.rpx, 30.rpx, 0), + child: Container( + width: double.infinity, + child: SkinPercentWidget(), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 30.rpx, 0.rpx, 30.rpx, 0), + child: Container( + width: double.infinity, + child: ZiZhuShenJingPercentWidget(), + ), + ), + ].divide(SizedBox( + height: 25.rpx, + )), ), ), ), @@ -508,124 +574,144 @@ class _NewSleepReportPageState extends State { } Widget getTimeWidget() { - if (sleepReportController.model.type == 1) { - //日报 - return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Container( - width: 28.rpx, - height: 28.rpx, - // width: double.infinity, - decoration: BoxDecoration(), - ), - Container( - child: Row( - children: [ - ClickableContainer( - backgroundColor: Colors.transparent, - highlightColor: themeController.currentColor.sc3, - padding: EdgeInsets.all(10.rpx), // 增加点击热区 - borderRadius: 8.rpx, - onTap: () { - sleepReportController.selectedDate.value = - sleepReportController.selectedDate.value! - .subtract(const Duration(days: 1)); - calendarController.selectedDate.value = - sleepReportController.selectedDate.value; - sleepReportController.updateAll(); - calendarController.updateAll(); - }, - child: SizedBox( - width: 9.rpx, - height: 14.rpx, - child: SvgPicture.asset( - 'assets/img/icon/arrow_left.svg', - color: themeController.currentColor.sc3, - ), - ), + final selectedDate = sleepReportController.selectedDate.value!; + final type = sleepReportController.model.type; + + String displayText = ''; + if (type == 1) { + // 日报 + displayText = + MyUtils.getFormatChineseTime(selectedDate.millisecondsSinceEpoch); + } else if (type == 2) { + // 周报 + final startOfWeek = + selectedDate.subtract(Duration(days: selectedDate.weekday - 1)); + final endOfWeek = startOfWeek.add(const Duration(days: 6)); + displayText = + '${MyUtils.getFormatChineseTime(startOfWeek.millisecondsSinceEpoch, showWeekday: false)}-${MyUtils.getFormatChineseTime(endOfWeek.millisecondsSinceEpoch, showWeekday: false)}'; + } else if (type == 3) { + // 月报 + displayText = + '${selectedDate.year}年${selectedDate.month.toString().padLeft(2, '0')}月'; + } + + void onLeftArrowTap() { + if (type == 1) { + sleepReportController.selectedDate.value = + selectedDate.subtract(const Duration(days: 1)); + } else if (type == 2) { + sleepReportController.selectedDate.value = + selectedDate.subtract(const Duration(days: 7)); + } else if (type == 3) { + sleepReportController.selectedDate.value = DateTime( + selectedDate.year, + selectedDate.month - 1, + selectedDate.day, + ); + } + calendarController.selectedDate.value = + sleepReportController.selectedDate.value; + sleepReportController.updateAll(); + calendarController.updateAll(); + } + + void onRightArrowTap() { + if (type == 1) { + sleepReportController.selectedDate.value = + selectedDate.add(const Duration(days: 1)); + } else if (type == 2) { + sleepReportController.selectedDate.value = + selectedDate.add(const Duration(days: 7)); + } else if (type == 3) { + sleepReportController.selectedDate.value = DateTime( + selectedDate.year, + selectedDate.month + 1, + selectedDate.day, + ); + } + calendarController.selectedDate.value = + sleepReportController.selectedDate.value; + sleepReportController.updateAll(); + calendarController.updateAll(); + } + + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox(width: 28.rpx, height: 28.rpx), // 占位 + Row( + children: [ + ClickableContainer( + backgroundColor: Colors.transparent, + highlightColor: themeController.currentColor.sc3, + padding: EdgeInsets.all(10.rpx), + borderRadius: 8.rpx, + onTap: onLeftArrowTap, + child: SizedBox( + width: 9.rpx, + height: 14.rpx, + child: SvgPicture.asset( + 'assets/img/icon/arrow_left.svg', + color: themeController.currentColor.sc3, ), - Container( - child: Text( - MyUtils.getFormatChineseTime(sleepReportController - .selectedDate.value!.millisecondsSinceEpoch), - style: TextStyle( - fontSize: AppConstants().normal_text_fontSize, - color: themeController.currentColor.sc3, - ), - ), - ), - ClickableContainer( - backgroundColor: Colors.transparent, - highlightColor: themeController.currentColor.sc3, - padding: EdgeInsets.all(10.rpx), // 增加点击热区 - borderRadius: 8.rpx, - onTap: () { - sleepReportController.selectedDate.value = - sleepReportController.selectedDate.value! - .subtract(const Duration(days: -1)); - calendarController.selectedDate.value = - sleepReportController.selectedDate.value; - sleepReportController.updateAll(); - calendarController.updateAll(); - }, - child: SizedBox( - width: 9.rpx, - height: 14.rpx, - child: SvgPicture.asset( - 'assets/img/icon/arrow_right.svg', - color: themeController.currentColor.sc3, - ), - ), - ), - ].divide(SizedBox( - width: 26.rpx, - )), - ), - ), - ClickableContainer( - backgroundColor: Colors.transparent, - highlightColor: themeController.currentColor.sc3, - padding: EdgeInsetsDirectional.fromSTEB( - 0.rpx, - 0.rpx, - 0.rpx, - 0.rpx, - ), - borderRadius: 0, - onTap: () { - showSleepCalendarBottomSheet( - timestamp: sleepReportController - .selectedDate.value!.millisecondsSinceEpoch, - context: context, - onDateSelected: (selectedDate) { - print("选中日期:"); - print(selectedDate); - sleepReportController.selectedDate.value = selectedDate; - calendarController.selectedDate.value = selectedDate; - sleepReportController.updateAll(); - calendarController.updateAll(); - }); - }, - child: SizedBox( - width: 28.rpx, - height: 28.rpx, - child: SvgPicture.asset( - 'assets/img/icon/share.svg', - fit: BoxFit.cover, - color: themeController.currentColor.sc3, ), ), - ) - ], - ); - } - if (sleepReportController.model.type == 2) { - //周报 - } - if (sleepReportController.model.type == 3) { - //月报 - } - return Container(); + Padding( + padding: EdgeInsets.symmetric(horizontal: 26.rpx), + child: Text( + displayText, + style: TextStyle( + fontSize: AppConstants().normal_text_fontSize, + color: themeController.currentColor.sc3, + ), + ), + ), + ClickableContainer( + backgroundColor: Colors.transparent, + highlightColor: themeController.currentColor.sc3, + padding: EdgeInsets.all(10.rpx), + borderRadius: 8.rpx, + onTap: onRightArrowTap, + child: SizedBox( + width: 9.rpx, + height: 14.rpx, + child: SvgPicture.asset( + 'assets/img/icon/arrow_right.svg', + color: themeController.currentColor.sc3, + ), + ), + ), + ], + ), + ClickableContainer( + backgroundColor: Colors.transparent, + highlightColor: themeController.currentColor.sc3, + padding: EdgeInsets.zero, + borderRadius: 0, + onTap: () { + showSleepCalendarBottomSheet( + type: sleepReportController.model.type, + timestamp: selectedDate.millisecondsSinceEpoch, + context: context, + onDateSelected: (newDate) { + sleepReportController.selectedDate.value = newDate; + calendarController.selectedDate.value = newDate; + sleepReportController.updateAll(); + calendarController.updateAll(); + }, + ); + }, + child: SizedBox( + width: 28.rpx, + height: 28.rpx, + child: SvgPicture.asset( + 'assets/img/icon/share.svg', + fit: BoxFit.cover, + color: themeController.currentColor.sc3, + ), + ), + ), + ], + ); } } diff --git a/lib/pages/user/setting_page.dart b/lib/pages/user/setting_page.dart index 5d430c0..1f1b3e0 100644 --- a/lib/pages/user/setting_page.dart +++ b/lib/pages/user/setting_page.dart @@ -97,7 +97,7 @@ class _SettingPageState extends State { children: [ Padding( padding: EdgeInsetsDirectional.fromSTEB( - 30.rpx, 25.rpx, 30.rpx, 0), + 0.rpx, 25.rpx, 0.rpx, 0), child: Container( width: double.infinity, decoration: BoxDecoration(