diff --git a/assets/langs/en_US.json b/assets/langs/en_US.json index e747c03..4c3738b 100644 --- a/assets/langs/en_US.json +++ b/assets/langs/en_US.json @@ -624,6 +624,9 @@ "手机号登录": "Phone Login", "邮箱登录": "Email Login", "输入邮箱": "Enter Email", - "隐私协议加载失败": "Privacy Agreement Failed to Load", - "请检查网络连接后重试": "Please check the network connection and try again" + "隐私协议加载失败": "Privacy Agreement Failed to Load", + "请检查网络连接后重试": "Please check the network connection and try again", + "开始": "startTime", + "结束": "endTime", + "时长": "duration:" } \ No newline at end of file diff --git a/assets/langs/zh_CN.json b/assets/langs/zh_CN.json index 778348c..a466b6f 100644 --- a/assets/langs/zh_CN.json +++ b/assets/langs/zh_CN.json @@ -615,18 +615,18 @@ "原邮箱号": "原邮箱号", "用户拒绝授权": "用户拒绝授权", "用户取消授权": "用户取消授权", - "请输入邮箱号":"请输入邮箱号", - "中国":"中国", - "香港":"香港", - "选择区号":"选择区号", - "输入邮箱号码":"输入邮箱号码", - "通知设置":"通知设置", - "注意!关闭后将无法接受任何消息":"注意!关闭后将无法接受任何消息", - "手机号登录":"手机号登录", - "邮箱登录":"邮箱登录", - "输入邮箱":"输入邮箱", - "隐私协议加载失败": "隐私协议加载失败", - "请检查网络连接后重试": "请检查网络连接后重试" - - + "请输入邮箱号": "请输入邮箱号", + "中国": "中国", + "香港": "香港", + "选择区号": "选择区号", + "输入邮箱号码": "输入邮箱号码", + "通知设置": "通知设置", + "注意!关闭后将无法接受任何消息": "注意!关闭后将无法接受任何消息", + "手机号登录": "手机号登录", + "邮箱登录": "邮箱登录", + "输入邮箱": "输入邮箱", + "隐私协议加载失败": "隐私协议加载失败", + "请检查网络连接后重试": "请检查网络连接后重试", + "开始": "开始", + "结束": "结束" } \ No newline at end of file diff --git a/assets/langs/zh_TW.json b/assets/langs/zh_TW.json index a5c624f..ef4e4d1 100644 --- a/assets/langs/zh_TW.json +++ b/assets/langs/zh_TW.json @@ -623,5 +623,8 @@ "邮箱登录": "電子郵件登入", "输入邮箱": "輸入電子郵件", "隐私协议加载失败": "隱私協議加載失敗", - "请检查网络连接后重试": "請檢查網絡連接後重試" + "请检查网络连接后重试": "請檢查網絡連接後重試", + "开始": "開始", + "结束": "結束", + "时长": "時長:" } \ No newline at end of file diff --git a/assets/mhlangs/en_US.json b/assets/mhlangs/en_US.json index 6e031d3..14962bd 100644 --- a/assets/mhlangs/en_US.json +++ b/assets/mhlangs/en_US.json @@ -642,5 +642,9 @@ "安卓启用网络提示": "The network is not enabled. Please enable the network connection", "ios启用网络提示": "The network is not enabled. Please enable the network connection", "隐私协议加载失败": "Privacy Agreement Failed to Load", - "请检查网络连接后重试": "Please check the network connection and try again" + "请检查网络连接后重试": "Please check the network connection and try again", + "开始": "startTime", + "结束": "endTime", + "时长": "duration:", + "请选择时区": "Please select a time zone" } \ No newline at end of file diff --git a/assets/mhlangs/zh_CN.json b/assets/mhlangs/zh_CN.json index 571b660..a223ade 100644 --- a/assets/mhlangs/zh_CN.json +++ b/assets/mhlangs/zh_CN.json @@ -649,6 +649,9 @@ "安卓启用网络提示": "网络未开启,请打开网络连接", "ios启用网络提示": "网络未开启,请打开网络连接", "隐私协议加载失败": "隐私协议加载失败", - "请检查网络连接后重试": "请检查网络连接后重试" - + "请检查网络连接后重试": "请检查网络连接后重试", + "开始": "开始", + "结束": "结束", + "时长": "时长:", + "请选择时区": "请选择时区" } \ No newline at end of file diff --git a/assets/mhlangs/zh_TW.json b/assets/mhlangs/zh_TW.json index 42f0c51..c36db9c 100644 --- a/assets/mhlangs/zh_TW.json +++ b/assets/mhlangs/zh_TW.json @@ -642,5 +642,9 @@ "安卓启用网络提示": "請開啟網絡,再使用APP", "ios启用网络提示": "請開啟網絡,再使用APP", "隐私协议加载失败": "隱私協議加載失敗", - "请检查网络连接后重试": "請檢查網絡連接後重試" + "请检查网络连接后重试": "請檢查網絡連接後重試", + "开始": "開始", + "结束": "結束", + "时长": "時長:", + "请选择时区": "請選擇時區" } \ No newline at end of file diff --git a/assets/miniapp/mhtControl_1.0.90.zip b/assets/miniapp/mhtControl_1.0.90.zip index c16e1c0..3d5ba18 100644 Binary files a/assets/miniapp/mhtControl_1.0.90.zip and b/assets/miniapp/mhtControl_1.0.90.zip differ diff --git a/lib/common/color/ServiceConstant.dart b/lib/common/color/ServiceConstant.dart index d6317c3..d28a99c 100644 --- a/lib/common/color/ServiceConstant.dart +++ b/lib/common/color/ServiceConstant.dart @@ -59,4 +59,6 @@ class ServiceConstant { static const String bgUrl = "https://vsbst-api.he-info.cn/vsbs_sotrage/background-image/taihe.png"; + static const String localTimeZone = + "/api/city/data/utc/info"; } diff --git a/lib/common/color/appConstants.dart b/lib/common/color/appConstants.dart index 681ce45..287ea99 100644 --- a/lib/common/color/appConstants.dart +++ b/lib/common/color/appConstants.dart @@ -7,6 +7,37 @@ import 'package:vbvs_app/enum/APPPackageType.dart'; class AppConstants { // App-related constants + + // 1. 纯字符串列表格式 + static const List integerTimeZones = [ + 'UTC-12', + 'UTC-11', + 'UTC-10', + 'UTC-9', + 'UTC-8', + 'UTC-7', + 'UTC-6', + 'UTC-5', + 'UTC-4', + 'UTC-3', + 'UTC-2', + 'UTC-1', + 'UTC+0', + 'UTC+1', + 'UTC+2', + 'UTC+3', + 'UTC+4', + 'UTC+5', + 'UTC+6', + 'UTC+7', + 'UTC+8', + 'UTC+9', + 'UTC+10', + 'UTC+11', + 'UTC+12', + 'UTC+13', + 'UTC+14', + ]; static const int code_time = 60; //验证码倒计时 static const int limit = 10; //分页数量 diff --git a/lib/common/util/eventType.dart b/lib/common/util/eventType.dart index 468214f..3aaee3b 100644 --- a/lib/common/util/eventType.dart +++ b/lib/common/util/eventType.dart @@ -12,3 +12,5 @@ class SwitchLanguageEvent { final String language; SwitchLanguageEvent(this.language); } + +class ScrollNotificationEvent {} \ No newline at end of file diff --git a/lib/controller/message/message_controller.dart b/lib/controller/message/message_controller.dart index 112a72c..82a8989 100644 --- a/lib/controller/message/message_controller.dart +++ b/lib/controller/message/message_controller.dart @@ -50,6 +50,8 @@ class MessageController extends GetControllerEx { int bodyPage = 1; int systemPage = 1; + bool isLoadingMore = false; // 添加加载标志位 + Future loadMore(String type) async { if (type == "app_vsm") { bodyPage++; diff --git a/lib/controller/mh_controller/people_info_controller.dart b/lib/controller/mh_controller/people_info_controller.dart index cabea99..117afe5 100644 --- a/lib/controller/mh_controller/people_info_controller.dart +++ b/lib/controller/mh_controller/people_info_controller.dart @@ -55,6 +55,7 @@ class PeopleInfoController extends GetControllerEx { final CityModelController cityController = Get.find(); RxList diseaseList = [].obs; + RxString timeZone = "".obs; //选择时区 @override Future onInit() async { @@ -247,4 +248,35 @@ class PeopleInfoController extends GetControllerEx { } return ApiResponse(code: -1, msg: "未知错误".tr); // Default return statement } + + getTimeZoneByLocalTime() async { + String serviceAddress = ServiceConstant.service_address; + String serviceName = ServiceConstant.server_service; + String serviceApi = ServiceConstant.localTimeZone; + + // 获取当前本地时间并格式化为字符串 + DateTime now = DateTime.now(); + String time = DateFormat("yyyy-MM-dd HH:mm:ss").format(now); + + // 构建查询URL,对时间参数进行URL编码 + String queryUrl = + "${serviceAddress}${serviceName}${serviceApi}?time=${Uri.encodeComponent(time)}"; + + String serverTimeUtc = ""; + // 发起请求 + await requestWithLog( + logTitle: "查询本地时区", + method: MyHttpMethod.get, + queryUrl: queryUrl, + onSuccess: (res) { + print("本地时间: $time"); + print("接口返回: $res"); + serverTimeUtc = res.data; + }, + onFailure: (res) { + print("查询时区失败"); + }, + ); + return serverTimeUtc; + } } diff --git a/lib/controller/person/person_controller.dart b/lib/controller/person/person_controller.dart index 0759f32..811c9b9 100644 --- a/lib/controller/person/person_controller.dart +++ b/lib/controller/person/person_controller.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'package:EasyDartModule/EasyDartModule.dart'; import 'package:ef/ef.dart'; +import 'package:flutterflow_ui/flutterflow_ui.dart'; import 'package:json_annotation/json_annotation.dart'; import 'package:vbvs_app/common/color/ServiceConstant.dart'; import 'package:vbvs_app/common/color/appConstants.dart'; @@ -9,6 +10,7 @@ import 'package:vbvs_app/common/color/app_uri_status.dart'; import 'package:vbvs_app/common/pojo/city.dart'; import 'package:vbvs_app/common/util/DailyLogUtils.dart'; import 'package:vbvs_app/common/util/MyUtils.dart'; +import 'package:vbvs_app/common/util/requestWithLog.dart'; import 'package:vbvs_app/enum/APPPackageType.dart'; import 'package:vbvs_app/model/api_response.dart'; @@ -50,6 +52,7 @@ class PersonController extends GetControllerEx { RxString height = "".obs; DateTime? dateTime = DateTime.now(); //选择时间 CityModel? cityModel; + RxString timeZone = "".obs; //选择时区 RxList diseaseList = [].obs; RxString update_person_mac = "".obs; @@ -79,10 +82,10 @@ class PersonController extends GetControllerEx { queryUrl += "?lang=$language"; } } - if (name.value.isEmpty) { - apiResponse.msg = "请输入姓名".tr; - return apiResponse; - } + // if (name.value.isEmpty) { + // apiResponse.msg = "请输入姓名".tr; + // return apiResponse; + // } if (birthday.value.isEmpty) { apiResponse.msg = "请选择生日".tr; return apiResponse; @@ -95,10 +98,14 @@ class PersonController extends GetControllerEx { apiResponse.msg = "请输入身高".tr; return apiResponse; } - if (cityModel == null || cityModel!.id == null) { - apiResponse.msg = "请选择城市".tr; + if (timeZone == null || timeZone.value.isEmpty) { + apiResponse.msg = "请选择时区".tr; return apiResponse; } + // if (cityModel == null || cityModel!.id == null) { + // apiResponse.msg = "请选择城市".tr; + // return apiResponse; + // } var data = { "id": currentPersonId.value, @@ -110,8 +117,8 @@ class PersonController extends GetControllerEx { "weight": weight!.value, "height": height.value, "disease": selectedDiseaseIds.value, - "city_id": cityModel!.id, - "UTC": cityModel!.UTC, + "city_id": cityModel?.id, + "UTC": timeZone.value, }; var response = await EasyDartModule.dio.put(queryUrl, data: jsonEncode(data)); @@ -240,4 +247,35 @@ class PersonController extends GetControllerEx { } return ApiResponse(code: -1, msg: "未知错误".tr); // Default return statement } + + getTimeZoneByLocalTime() async { + String serviceAddress = ServiceConstant.service_address; + String serviceName = ServiceConstant.server_service; + String serviceApi = ServiceConstant.localTimeZone; + + // 获取当前本地时间并格式化为字符串 + DateTime now = DateTime.now(); + String time = DateFormat("yyyy-MM-dd HH:mm:ss").format(now); + + // 构建查询URL,对时间参数进行URL编码 + String queryUrl = + "${serviceAddress}${serviceName}${serviceApi}?time=${Uri.encodeComponent(time)}"; + + String serverTimeUtc = ""; + // 发起请求 + await requestWithLog( + logTitle: "查询本地时区", + method: MyHttpMethod.get, + queryUrl: queryUrl, + onSuccess: (res) { + print("本地时间: $time"); + print("接口返回: $res"); + serverTimeUtc = res.data; + }, + onFailure: (res) { + print("查询时区失败"); + }, + ); + return serverTimeUtc; + } } diff --git a/lib/controller/repair/repair_controller.dart b/lib/controller/repair/repair_controller.dart index df49e3c..34becf1 100644 --- a/lib/controller/repair/repair_controller.dart +++ b/lib/controller/repair/repair_controller.dart @@ -40,7 +40,7 @@ class RepairController extends GetControllerEx { attr = GetModel(RepairModel()).obs; } - RxDouble device_type = 0.0.obs; + RxInt device_type = 0.obs; RxList repairList = [].obs; RxString name = "".obs; diff --git a/lib/pages/common/selectDialog.dart b/lib/pages/common/selectDialog.dart index 557d379..92c0d83 100644 --- a/lib/pages/common/selectDialog.dart +++ b/lib/pages/common/selectDialog.dart @@ -69,6 +69,66 @@ Widget getOnePickers( }) { ThemeController themeController = Get.find(); final bool isEn = Get.locale?.languageCode.startsWith('en') ?? false; + final dynamicKey = ValueKey('picker_${arr.length}_${selectedIndex.value}'); + return Obx(() { + return CupertinoPicker.builder( + key: pickerKey ?? dynamicKey, + itemExtent: 90.rpx, + useMagnifier: false, + magnification: 1, + diameterRatio: 1.3, + squeeze: 1, + scrollController: + FixedExtentScrollController(initialItem: selectedIndex.value), + selectionOverlay: Container(), + onSelectedItemChanged: (int index) { + selectedIndex.value = index; + if (onChanged != null) onChanged(index); + }, + childCount: arr.length, + itemBuilder: (context, index) { + final bool isSelected = index == selectedIndex.value; + + // 显示文本 + String displayText; + if (isMonthName && isEn && arr[index] is int) { + displayText = DateFormat.MMMM('en').format(DateTime(0, arr[index])); + } else { + displayText = "${arr[index]}$unit"; + } + + return Center( + child: Text( + displayText, + style: TextStyle( + fontFamily: 'Readex Pro', + color: isSelected + ? (selectedColor ?? + themeController.currentColor.sc3) // ✅ 优先使用外部颜色 + : const Color(0xFF9AA0B3), + fontSize: 30.rpx, + fontWeight: FontWeight.normal, + ), + ), + ); + }, + ); + }); +} + +Widget getOnePickersCity( + BuildContext context, + List arr, + RxInt selectedIndex, { + String unit = '', + bool looping = false, + void Function(int)? onChanged, + bool isMonthName = false, + Key? pickerKey, + Color? selectedColor, // ✅ 新增:选中颜色(可选) +}) { + ThemeController themeController = Get.find(); + final bool isEn = Get.locale?.languageCode.startsWith('en') ?? false; return Obx(() { final dynamicKey = ValueKey('picker_${arr.length}_${selectedIndex.value}'); @@ -132,9 +192,8 @@ Widget getOnePickersSpe( }) { ThemeController themeController = Get.find(); final bool isEn = Get.locale?.languageCode.startsWith('en') ?? false; - + final dynamicKey = ValueKey('picker_${arr.length}_${selectedIndex.value}'); return Obx(() { - final dynamicKey = ValueKey('picker_${arr.length}_${selectedIndex.value}'); return CupertinoPicker.builder( key: pickerKey ?? dynamicKey, itemExtent: 90.rpx, @@ -1777,8 +1836,7 @@ Future showCustomConfirmOfWebViewDialog( bool showCancel = false, String cancelName = "取消", ConfirmDialogIcon icon = ConfirmDialogIcon.warn, - int type = 1 - }) async { + int type = 1}) async { return showDialog( context: context, barrierDismissible: true, @@ -1865,7 +1923,9 @@ Future showCustomConfirmOfWebViewDialog( onTap: () { Get.back(result: "confirm"); }, - colors: type == 3?AppConstants().mhtNormalButton:AppConstants().thNormalButton, // 渐变背景 + colors: type == 3 + ? AppConstants().mhtNormalButton + : AppConstants().thNormalButton, // 渐变背景 gradientDirection: GradientDirection.horizontal, child: Container( // width: MediaQuery.sizeOf(context).width * 0.5, // 宽度占屏幕一半 diff --git a/lib/pages/device/BodyDeviceWidget.dart b/lib/pages/device/BodyDeviceWidget.dart index 3622eef..960f335 100644 --- a/lib/pages/device/BodyDeviceWidget.dart +++ b/lib/pages/device/BodyDeviceWidget.dart @@ -5,8 +5,10 @@ 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/color/app_uri_status.dart'; +import 'package:vbvs_app/common/util/EventBus.dart'; import 'package:vbvs_app/common/util/FitTool.dart'; import 'package:vbvs_app/common/util/MyUtils.dart'; +import 'package:vbvs_app/common/util/eventType.dart'; import 'package:vbvs_app/component/NullDataComponentWidget.dart'; import 'package:vbvs_app/component/tool/ClickableContainer.dart'; import 'package:vbvs_app/component/tool/CustomCard.dart'; @@ -641,16 +643,19 @@ class _BodyDevicePageState extends State { : Padding( padding: EdgeInsetsDirectional.fromSTEB( 30.rpx, 26.rpx, 30.rpx, 0), - child: SingleChildScrollView( - controller: _myDeviceScrollController, - child: Column( - mainAxisSize: MainAxisSize.max, - children: myDeviceList - .map((device) => - DeviceDataComponentWidget( - device: device)) - .toList() - .divide(SizedBox(height: 25.rpx)), + child: _wrapWithScrollListener( + SingleChildScrollView( + controller: _myDeviceScrollController, + child: Column( + mainAxisSize: MainAxisSize.max, + children: myDeviceList + .map((device) => + DeviceDataComponentWidget( + device: device)) + .toList() + .divide( + SizedBox(height: 25.rpx)), + ), ), ), ); @@ -668,17 +673,20 @@ class _BodyDevicePageState extends State { : Padding( padding: EdgeInsetsDirectional.fromSTEB( 30.rpx, 26.rpx, 30.rpx, 0), - child: SingleChildScrollView( - controller: - _cloudDeviceScrollController, - child: Column( - mainAxisSize: MainAxisSize.max, - children: cloudDeviceList - .map((device) => - DeviceDataComponentWidget( - device: device)) - .toList() - .divide(SizedBox(height: 25.rpx)), + child: _wrapWithScrollListener( + SingleChildScrollView( + controller: + _cloudDeviceScrollController, + child: Column( + mainAxisSize: MainAxisSize.max, + children: cloudDeviceList + .map((device) => + DeviceDataComponentWidget( + device: device)) + .toList() + .divide( + SizedBox(height: 25.rpx)), + ), ), ), ); @@ -743,3 +751,18 @@ class _BodyDevicePageState extends State { ); } } + +// 在你的父页面类中添加这个方法 +Widget _wrapWithScrollListener(Widget child) { + return NotificationListener( + onNotification: (ScrollNotification notification) { + if (notification is ScrollStartNotification || + notification is ScrollUpdateNotification) { + // 发送全局滚动事件 + EventBus().emit(ScrollNotificationEvent()); + } + return false; + }, + child: child, + ); +} diff --git a/lib/pages/device/component/DeviceDataComponentWidget.dart b/lib/pages/device/component/DeviceDataComponentWidget.dart index 8bb0827..300c81e 100644 --- a/lib/pages/device/component/DeviceDataComponentWidget.dart +++ b/lib/pages/device/component/DeviceDataComponentWidget.dart @@ -25,7 +25,6 @@ import 'package:vbvs_app/enum/BindType.dart'; import 'package:vbvs_app/model/api_response.dart'; import 'package:vbvs_app/pages/device_bind/componnet/bind_dialog.dart'; - class DeviceDataComponentWidget extends StatefulWidget { final Map device; @@ -44,6 +43,7 @@ class _DeviceDataComponentWidgetState extends State { bool _isPopupOpen = false; var lisObj; + late StreamSubscription _scrollSubscription; @override void dispose() { @@ -64,6 +64,12 @@ class _DeviceDataComponentWidgetState extends State { cityController.cityList = []; await initializeCityData(); }); + _scrollSubscription = + EventBus().on().listen((event) { + if (_isPopupOpen) { + _closePopup(); + } + }); } void _showPopup() { @@ -628,7 +634,7 @@ class _DeviceDataComponentWidgetState extends State { maxWidth: MediaQuery.sizeOf(context).width * 0.6, ), child: Text( - '${widget.device['person']?['name'] ?? '未命名'.tr}', + '${(widget.device['person']?['name'] as String?)?.isNotEmpty == true ? widget.device['person']!['name'] : '体征检测设备'.tr}', style: TextStyle( fontFamily: 'Inter', fontSize: 30.rpx, @@ -1180,6 +1186,8 @@ class _DeviceDataComponentWidgetState extends State { personController.dateTime = MyUtils.formatBirthdayTime( widget.device['person']['birthday']); + personController.timeZone.value = + widget.device['person']['UTC'] ?? ''; if (widget.device['person']['city_id'] != null) { // 根据city_id查找完整的城市数据 final int cityId = @@ -1208,6 +1216,7 @@ class _DeviceDataComponentWidgetState extends State { personController.weight.value = ""; personController.diseaseList.value = []; personController.cityModel = null; + personController.timeZone.value = ""; } await Get.toNamed("/updatePersonPage", arguments: widget.device['bind_type']); @@ -1701,4 +1710,12 @@ class _DeviceDataComponentWidgetState extends State { } return false; } + + void _closePopup() { + setState(() { + _isPopupOpen = false; + }); + _popupEntry?.remove(); + _popupEntry = null; + } } diff --git a/lib/pages/device/component/MessageSetting.dart b/lib/pages/device/component/MessageSetting.dart index 75d8714..dd7b9fd 100644 --- a/lib/pages/device/component/MessageSetting.dart +++ b/lib/pages/device/component/MessageSetting.dart @@ -659,7 +659,7 @@ class _MessageSettingPageState extends State { children: [ Padding( padding: EdgeInsetsDirectional.fromSTEB( - 40.rpx, 40.rpx, 40.rpx, 20.rpx), + 30.rpx, 40.rpx, 30.rpx, 20.rpx), child: Row( mainAxisAlignment: MainAxisAlignment.start, children: [ @@ -681,9 +681,9 @@ class _MessageSettingPageState extends State { (data["real"] as List)[i].containsKey("name")) Padding( padding: EdgeInsetsDirectional.fromSTEB( - 40.rpx, + 30.rpx, i == 0 ? 20.rpx : 0, // 第一个元素顶部有20.rpx间距 - 40.rpx, + 30.rpx, i == (data["real"] as List).length - 1 ? 20.rpx : 0.rpx), @@ -781,7 +781,7 @@ class _MessageSettingPageState extends State { children: [ Padding( padding: EdgeInsetsDirectional.fromSTEB( - 40.rpx, 40.rpx, 40.rpx, 20.rpx), + 30.rpx, 40.rpx, 30.rpx, 20.rpx), child: Row( mainAxisAlignment: MainAxisAlignment.start, children: [ @@ -803,9 +803,9 @@ class _MessageSettingPageState extends State { (data["report"] as List)[i].containsKey("name")) Padding( padding: EdgeInsetsDirectional.fromSTEB( - 40.rpx, + 30.rpx, i == 0 ? 20.rpx : 0, // 第一个元素顶部有20.rpx间距 - 40.rpx, + 30.rpx, i == (data["report"] as List).length - 1 ? 20.rpx : 0.rpx), diff --git a/lib/pages/device/component/SingleMessageSetting.dart b/lib/pages/device/component/SingleMessageSetting.dart index b93a260..c76fa5a 100644 --- a/lib/pages/device/component/SingleMessageSetting.dart +++ b/lib/pages/device/component/SingleMessageSetting.dart @@ -10,6 +10,7 @@ import 'package:vbvs_app/common/util/MyUtils.dart'; import 'package:vbvs_app/common/util/requestWithLog.dart'; import 'package:vbvs_app/component/base/GradientSwitch.dart'; import 'package:vbvs_app/component/tool/ClickableContainer.dart'; +import 'package:vbvs_app/component/tool/NewTopSlideNotification.dart'; import 'package:vbvs_app/component/tool/TopSlideNotification.dart'; import 'package:vbvs_app/controller/message/common_message_setting_controller.dart'; import 'package:vbvs_app/controller/message/message_setting_controller.dart'; @@ -197,895 +198,947 @@ class _MessageSettingPageState extends State { @override Widget build(BuildContext context) { - return LayoutBuilder( - builder: (context, bodysize) => GestureDetector( - child: Container( - decoration: BoxDecoration( - image: DecorationImage( - image: AssetImage('assets/img/bgNoImg.png'), - fit: BoxFit.fill, - ), - ), - child: Scaffold( - backgroundColor: Colors.transparent, - appBar: AppBar( - backgroundColor: themeController.currentColor.sc17, - automaticallyImplyLeading: false, - iconTheme: IconThemeData( - color: themeController.currentColor.sc3, - ), - titleSpacing: 0, - title: Container( - width: double.infinity, - height: 180.rpx, - child: Stack( - alignment: Alignment.center, - children: [ - /// 居中标题 - Text( - '${widget.data['name']}'.tr, - style: TextStyle( - fontFamily: 'ReadexPro', - color: themeController.currentColor.sc3, - letterSpacing: 0, - fontSize: 30.rpx, - ), - ), - Positioned( - left: 0, - child: returnIconButtomAddCallback(() {}), - ), - ], + return WillPopScope( + child: LayoutBuilder( + builder: (context, bodysize) => GestureDetector( + child: Container( + decoration: BoxDecoration( + image: DecorationImage( + image: AssetImage('assets/img/bgNoImg.png'), + fit: BoxFit.fill, ), ), - actions: [], - centerTitle: false, - ), - body: GestureDetector( - child: SafeArea( - top: true, - child: Padding( - padding: EdgeInsetsDirectional.fromSTEB(0.rpx, 0, 0.rpx, 0), - child: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, + child: Scaffold( + backgroundColor: Colors.transparent, + appBar: AppBar( + backgroundColor: themeController.currentColor.sc17, + automaticallyImplyLeading: false, + iconTheme: IconThemeData( + color: themeController.currentColor.sc3, + ), + titleSpacing: 0, + title: Container( + width: double.infinity, + height: 180.rpx, + child: Stack( + alignment: Alignment.center, children: [ - Padding( - padding: - EdgeInsetsDirectional.fromSTEB(0, 30.rpx, 0, 0), - child: Container( - width: double.infinity, - constraints: BoxConstraints( - minHeight: 90.rpx, - ), - decoration: BoxDecoration( - color: themeController.currentColor.sc5), - child: Padding( - padding: EdgeInsetsDirectional.fromSTEB( - 30.rpx, 15.rpx, 30.rpx, 15.rpx), - child: Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Text( - "全部消息".tr, - style: TextStyle( - color: - themeController.currentColor.sc3, - fontSize: AppConstants() - .title_text_fontSize), - ), - ), - Obx(() { - return GradientSwitch( - value: getAllMessageSwitch(widget.data), - onChanged: (val) { - if (commonMessageSettingController - .model.setting == - 0) { - TopSlideNotification.show(context, - text: "请先在设置里的消息通知打开全部消息配置".tr, - textColor: themeController - .currentColor.sc9); - return; - } - if (messageSettingController - .model.setting == - 0) { - TopSlideNotification.show(context, - text: "该设备消息提醒已关闭".tr, - textColor: themeController - .currentColor.sc9); - return; - } - String serviceAddress = - ServiceConstant.service_address; - String serviceName = - ServiceConstant.server_service; - String serviceApi = - ServiceConstant.user_setting; - String mac = - widget.data['device']['mac']; - String type = - "user_device_message_setting_$mac"; - String queryUrl = - "${serviceAddress}${serviceName}${serviceApi}"; - int targetId = widget.data['id']; - int configIndex = - messageSettingController - .model.device_type_setting - .indexWhere((item) => - item['id'] == targetId); - if (configIndex != -1) { - // 更新找到的配置项 - int newSetting = val ? 1 : 0; - messageSettingController - .model.device_type_setting[ - configIndex]['setting'] = - newSetting; - - // 根据 setting 的值更新 appSetting 和 serviceSetting - messageSettingController - .model.device_type_setting[ - configIndex]['appSetting'] = - newSetting; - messageSettingController - .model.device_type_setting[ - configIndex] - ['serviceSetting'] = newSetting; - } else { - messageSettingController - .model.device_type_setting - .add({ - "id": widget.data['id'], - "setting": val ? 1 : 0, - "max": widget.data['max'], - "appSetting": 1, - "serviceSetting": 1, - }); - } - var data = { - "type": type, - "setting": messageSettingController - .model.setting, - "device_type_setting": - messageSettingController - .model.device_type_setting, - }; - requestWithLog( - logTitle: "更新消息推送状态", - method: MyHttpMethod.put, - queryUrl: queryUrl, - data: data, - onSuccess: (res) { - _fetchDeviceMessageSetting(); - messageSettingController - .updateAll(); - }); - }, - activeGradient: LinearGradient( - colors: [ - themeController.currentColor.sc1, - themeController.currentColor.sc2 - ], - ), - activeThumbColor: Colors.white, - inactiveThumbColor: - stringToColor("#A2A4A9"), - inactiveColor: stringToColor("#161B28"), - ); - }), - ], - ), - ), + /// 居中标题 + Text( + '${widget.data['name']}'.tr, + style: TextStyle( + fontFamily: 'ReadexPro', + color: themeController.currentColor.sc3, + letterSpacing: 0, + fontSize: 30.rpx, ), ), - Padding( - padding: EdgeInsetsDirectional.fromSTEB( - 30.rpx, 21.rpx, 30.rpx, 0), - child: Container( - decoration: BoxDecoration( - color: themeController.currentColor.sc5, - borderRadius: BorderRadius.circular( - AppConstants().normal_container_radius), - ), - child: Padding( + Positioned( + left: 0, + child: returnIconButtomAddCallback(() { + NewTopSlideNotification.show(text: "保存成功".tr); + }), + ), + ], + ), + ), + actions: [], + centerTitle: false, + ), + body: GestureDetector( + child: SafeArea( + top: true, + child: Padding( + padding: + EdgeInsetsDirectional.fromSTEB(0.rpx, 0, 0.rpx, 0), + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Padding( padding: EdgeInsetsDirectional.fromSTEB( - 30.rpx, 40.rpx, 30.rpx, 54.rpx), + 0, 30.rpx, 0, 0), child: Container( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Text( - "APP消息".tr, - style: TextStyle( - color: themeController - .currentColor.sc3, - fontSize: AppConstants() - .title_text_fontSize), - ), + width: double.infinity, + constraints: BoxConstraints( + minHeight: 90.rpx, + ), + decoration: BoxDecoration( + color: themeController.currentColor.sc5), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 30.rpx, 15.rpx, 30.rpx, 15.rpx), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + "全部消息".tr, + style: TextStyle( + color: themeController + .currentColor.sc3, + fontSize: AppConstants() + .title_text_fontSize), ), - Obx(() { - return GradientSwitch( - value: getAPPMessageSwitch( - widget.data), - onChanged: (val) { - if (commonMessageSettingController - .model.setting == - 0) { - TopSlideNotification.show( - context, - text: "请先在设置里的消息通知打开全部消息配置" - .tr, - textColor: themeController - .currentColor.sc9); - return; - } - if (commonMessageSettingController - .model.appSetting == - 0) { - TopSlideNotification.show( - context, - text: "请先在设置里的消息通知打开APP消息配置" - .tr, - textColor: themeController - .currentColor.sc9); - return; - } - if (messageSettingController - .model.setting == - 0) { - TopSlideNotification.show( - context, - text: "请先打开消息提醒设置".tr, - textColor: themeController - .currentColor.sc9); - return; - } - String serviceAddress = - ServiceConstant - .service_address; - String serviceName = - ServiceConstant - .server_service; - String serviceApi = - ServiceConstant.user_setting; - String mac = - widget.data['device']['mac']; - String type = - "user_device_message_setting_$mac"; - String queryUrl = - "${serviceAddress}${serviceName}${serviceApi}"; - int targetId = widget.data['id']; - int configIndex = - messageSettingController - .model.device_type_setting - .indexWhere((item) => - item['id'] == - targetId); - if (configIndex != -1) { - // 更新找到的配置项 - messageSettingController.model - .device_type_setting[ - configIndex][ - 'appSetting'] = val ? 1 : 0; + ), + Obx(() { + return GradientSwitch( + value: + getAllMessageSwitch(widget.data), + onChanged: (val) { + if (commonMessageSettingController + .model.setting == + 0) { + TopSlideNotification.show(context, + text: + "请先在设置里的消息通知打开全部消息配置".tr, + textColor: themeController + .currentColor.sc9); + return; + } + if (messageSettingController + .model.setting == + 0) { + TopSlideNotification.show(context, + text: "该设备消息提醒已关闭".tr, + textColor: themeController + .currentColor.sc9); + return; + } + String serviceAddress = + ServiceConstant.service_address; + String serviceName = + ServiceConstant.server_service; + String serviceApi = + ServiceConstant.user_setting; + String mac = + widget.data['device']['mac']; + String type = + "user_device_message_setting_$mac"; + String queryUrl = + "${serviceAddress}${serviceName}${serviceApi}"; + int targetId = widget.data['id']; + int configIndex = + messageSettingController + .model.device_type_setting + .indexWhere((item) => + item['id'] == targetId); + if (configIndex != -1) { + // 更新找到的配置项 + int newSetting = val ? 1 : 0; + messageSettingController.model + .device_type_setting[ + configIndex]['setting'] = + newSetting; - // 获取当前的 serviceSetting 值 - int serviceSetting = + // 根据 setting 的值更新 appSetting 和 serviceSetting + messageSettingController.model + .device_type_setting[ + configIndex] + ['appSetting'] = newSetting; + messageSettingController.model + .device_type_setting[ + configIndex][ + 'serviceSetting'] = newSetting; + } else { + messageSettingController + .model.device_type_setting + .add({ + "id": widget.data['id'], + "setting": val ? 1 : 0, + "max": widget.data['max'], + "appSetting": 1, + "serviceSetting": 1, + }); + } + var data = { + "type": type, + "setting": + messageSettingController + .model.setting, + "device_type_setting": + messageSettingController.model + .device_type_setting, + }; + requestWithLog( + logTitle: "更新消息推送状态", + method: MyHttpMethod.put, + queryUrl: queryUrl, + data: data, + onSuccess: (res) { + _fetchDeviceMessageSetting(); + messageSettingController + .updateAll(); + }); + }, + activeGradient: LinearGradient( + colors: [ + themeController.currentColor.sc1, + themeController.currentColor.sc2 + ], + ), + activeThumbColor: Colors.white, + inactiveThumbColor: + stringToColor("#A2A4A9"), + inactiveColor: + stringToColor("#161B28"), + ); + }), + ], + ), + ), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 30.rpx, 21.rpx, 30.rpx, 0), + child: Container( + decoration: BoxDecoration( + color: themeController.currentColor.sc5, + borderRadius: BorderRadius.circular( + AppConstants().normal_container_radius), + ), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 30.rpx, 40.rpx, 30.rpx, 54.rpx), + child: Container( + child: Column( + crossAxisAlignment: + CrossAxisAlignment.start, + children: [ + Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + "APP消息".tr, + style: TextStyle( + color: themeController + .currentColor.sc3, + fontSize: AppConstants() + .title_text_fontSize), + ), + ), + Obx(() { + return GradientSwitch( + value: getAPPMessageSwitch( + widget.data), + onChanged: (val) { + if (commonMessageSettingController + .model.setting == + 0) { + TopSlideNotification.show( + context, + text: + "请先在设置里的消息通知打开全部消息配置" + .tr, + textColor: + themeController + .currentColor + .sc9); + return; + } + if (commonMessageSettingController + .model.appSetting == + 0) { + TopSlideNotification.show( + context, + text: + "请先在设置里的消息通知打开APP消息配置" + .tr, + textColor: + themeController + .currentColor + .sc9); + return; + } + if (messageSettingController + .model.setting == + 0) { + TopSlideNotification.show( + context, + text: "请先打开消息提醒设置".tr, + textColor: + themeController + .currentColor + .sc9); + return; + } + String serviceAddress = + ServiceConstant + .service_address; + String serviceName = + ServiceConstant + .server_service; + String serviceApi = + ServiceConstant + .user_setting; + String mac = widget + .data['device']['mac']; + String type = + "user_device_message_setting_$mac"; + String queryUrl = + "${serviceAddress}${serviceName}${serviceApi}"; + int targetId = + widget.data['id']; + int configIndex = + messageSettingController + .model + .device_type_setting + .indexWhere((item) => + item['id'] == + targetId); + if (configIndex != -1) { + // 更新找到的配置项 messageSettingController .model .device_type_setting[ - configIndex][ - 'serviceSetting'] ?? - 0; + configIndex] + ['appSetting'] = + val ? 1 : 0; - // 更新 setting 字段:只要有一个为1,setting就为1 - messageSettingController.model - .device_type_setting[ - configIndex] - ['setting'] = (val || - serviceSetting == 1) - ? 1 - : 0; - } else { - messageSettingController - .model.device_type_setting - .add({ - "id": widget.data['id'], - "setting": 1, - "max": widget.data['max'], - "appSetting": val, - "serviceSetting": 1, - }); - } - var data = { - "type": type, - "setting": - messageSettingController - .model.setting, - "device_type_setting": - messageSettingController - .model - .device_type_setting, - }; - requestWithLog( - logTitle: "更新消息推送状态", - method: MyHttpMethod.put, - queryUrl: queryUrl, - data: data, - onSuccess: (res) { - _fetchDeviceMessageSetting(); - messageSettingController - .updateAll(); - }); - }, - activeGradient: LinearGradient( - colors: [ - themeController - .currentColor.sc1, - themeController.currentColor.sc2 - ], - ), - activeThumbColor: Colors.white, - inactiveThumbColor: - stringToColor("#A2A4A9"), - inactiveColor: - stringToColor("#161B28"), - ); - }), - ], - ), - Text( - "APP消息介绍".tr, - style: TextStyle( - color: - themeController.currentColor.sc4, - fontSize: AppConstants() - .normal_text_fontSize), - ), - // Row( - // mainAxisSize: MainAxisSize.max, - // mainAxisAlignment: - // MainAxisAlignment.spaceBetween, - // children: [ - // Expanded( - // child: Text( - // "短信消息".tr, - // style: TextStyle( - // color: themeController - // .currentColor.sc3, - // fontSize: AppConstants() - // .title_text_fontSize), - // ), - // ), - // Obx(() { - // return GradientSwitch( - // value: commonMessageSettingController - // .model.setting == - // 1 - // ? (messageSettingController - // .model.setting == - // 1 - // ? (messageSettingController - // .model - // .serviceSetting == - // 1 - // ? true - // : false) - // : false) - // : false, - // onChanged: (val) { - // if (commonMessageSettingController - // .model.setting == - // 0) { - // TopSlideNotification.show( - // context, - // text: "请先在设置里的消息通知打开全部消息配置" - // .tr, - // textColor: themeController - // .currentColor.sc9); - // return; - // } - // if (messageSettingController - // .model.setting == - // 0) { - // TopSlideNotification.show( - // context, - // text: "请先打开消息提醒设置".tr, - // textColor: themeController - // .currentColor.sc9); - // return; - // } - // String serviceAddress = - // ServiceConstant - // .service_address; - // String serviceName = - // ServiceConstant - // .server_service; - // String serviceApi = - // ServiceConstant.user_setting; - // String mac = widget.data['mac']; - // String type = - // "user_device_message_setting_$mac"; - // String queryUrl = - // "${serviceAddress}${serviceName}${serviceApi}"; - // var data = { - // "type": type, - // "setting": - // messageSettingController - // .model.setting, - // "appSetting": - // messageSettingController - // .model.appSetting, - // "serviceSetting": - // val == true ? 1 : 0, - // }; - // requestWithLog( - // logTitle: "更新消息推送状态", - // method: MyHttpMethod.put, - // queryUrl: queryUrl, - // data: data, - // onSuccess: (res) { - // _fetchDeviceMessageSetting(); - // messageSettingController - // .updateAll(); - // }); - // }, - // activeGradient: LinearGradient( - // colors: [ - // themeController - // .currentColor.sc1, - // themeController.currentColor.sc2 - // ], - // ), - // activeThumbColor: Colors.white, - // inactiveThumbColor: - // stringToColor("#A2A4A9"), - // inactiveColor: - // stringToColor("#161B28"), - // ); - // }), - // ], - // ), - // Text( - // "短信消息介绍".tr, - // style: TextStyle( - // color: - // themeController.currentColor.sc4, - // fontSize: AppConstants() - // .normal_text_fontSize), - // ), - // Row( - // mainAxisSize: MainAxisSize.max, - // mainAxisAlignment: - // MainAxisAlignment.spaceBetween, - // children: [ - // Expanded( - // child: Text( - // "电话语音提醒".tr, - // style: TextStyle( - // color: themeController - // .currentColor.sc3, - // fontSize: AppConstants() - // .title_text_fontSize), - // ), - // ), - // Obx(() { - // return GradientSwitch( - // value: commonMessageSettingController - // .model.setting == - // 1 - // ? (messageSettingController - // .model.setting == - // 1 - // ? (messageSettingController - // .model - // .serviceSetting == - // 1 - // ? true - // : false) - // : false) - // : false, - // onChanged: (val) { - // if (commonMessageSettingController - // .model.setting == - // 0) { - // TopSlideNotification.show( - // context, - // text: "请先在设置里的消息通知打开全部消息配置" - // .tr, - // textColor: themeController - // .currentColor.sc9); - // return; - // } - // if (messageSettingController - // .model.setting == - // 0) { - // TopSlideNotification.show( - // context, - // text: "请先打开消息提醒设置".tr, - // textColor: themeController - // .currentColor.sc9); - // return; - // } - // String serviceAddress = - // ServiceConstant - // .service_address; - // String serviceName = - // ServiceConstant - // .server_service; - // String serviceApi = - // ServiceConstant.user_setting; - // String mac = widget.data['mac']; - // String type = - // "user_device_message_setting_$mac"; - // String queryUrl = - // "${serviceAddress}${serviceName}${serviceApi}"; - // var data = { - // "type": type, - // "setting": - // messageSettingController - // .model.setting, - // "appSetting": - // messageSettingController - // .model.appSetting, - // "serviceSetting": - // val == true ? 1 : 0, - // }; - // requestWithLog( - // logTitle: "更新消息推送状态", - // method: MyHttpMethod.put, - // queryUrl: queryUrl, - // data: data, - // onSuccess: (res) { - // _fetchDeviceMessageSetting(); - // messageSettingController - // .updateAll(); - // }); - // }, - // activeGradient: LinearGradient( - // colors: [ - // themeController - // .currentColor.sc1, - // themeController.currentColor.sc2 - // ], - // ), - // activeThumbColor: Colors.white, - // inactiveThumbColor: - // stringToColor("#A2A4A9"), - // inactiveColor: - // stringToColor("#161B28"), - // ); - // }), - // ], - // ), - // Text( - // "电话语音提醒介绍".tr, - // style: TextStyle( - // color: - // themeController.currentColor.sc4, - // fontSize: AppConstants() - // .normal_text_fontSize), - // ), + // 获取当前的 serviceSetting 值 + int serviceSetting = + messageSettingController + .model + .device_type_setting[ + configIndex] + [ + 'serviceSetting'] ?? + 0; - Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Text( - "服务号消息".tr, - style: TextStyle( - color: themeController - .currentColor.sc3, - fontSize: AppConstants() - .title_text_fontSize), - ), - ), - Obx(() { - return GradientSwitch( - value: getServiceMessageSwitch( - widget.data), - onChanged: (val) { - if (commonMessageSettingController - .model.setting == - 0) { - TopSlideNotification.show( - context, - text: "请先在设置里的消息通知打开全部消息配置" - .tr, - textColor: themeController - .currentColor.sc9); - return; - } - if (messageSettingController - .model.setting == - 0) { - TopSlideNotification.show( - context, - text: "请先打开消息提醒设置".tr, - textColor: themeController - .currentColor.sc9); - return; - } - String serviceAddress = - ServiceConstant - .service_address; - String serviceName = - ServiceConstant - .server_service; - String serviceApi = - ServiceConstant.user_setting; - String mac = - widget.data['device']['mac']; - String type = - "user_device_message_setting_$mac"; - String queryUrl = - "${serviceAddress}${serviceName}${serviceApi}"; - int targetId = widget.data['id']; - int configIndex = - messageSettingController - .model.device_type_setting - .indexWhere((item) => - item['id'] == - targetId); - if (configIndex != -1) { - // 更新找到的配置项 - messageSettingController.model + // 更新 setting 字段:只要有一个为1,setting就为1 + messageSettingController + .model .device_type_setting[ configIndex] - ['serviceSetting'] = - val ? 1 : 0; + ['setting'] = (val || + serviceSetting == 1) + ? 1 + : 0; + } else { + messageSettingController + .model + .device_type_setting + .add({ + "id": widget.data['id'], + "setting": 1, + "max": widget.data['max'], + "appSetting": val, + "serviceSetting": 1, + }); + } + var data = { + "type": type, + "setting": + messageSettingController + .model.setting, + "device_type_setting": + messageSettingController + .model + .device_type_setting, + }; + requestWithLog( + logTitle: "更新消息推送状态", + method: MyHttpMethod.put, + queryUrl: queryUrl, + data: data, + onSuccess: (res) { + _fetchDeviceMessageSetting(); + messageSettingController + .updateAll(); + }); + }, + activeGradient: LinearGradient( + colors: [ + themeController + .currentColor.sc1, + themeController + .currentColor.sc2 + ], + ), + activeThumbColor: Colors.white, + inactiveThumbColor: + stringToColor("#A2A4A9"), + inactiveColor: + stringToColor("#161B28"), + ); + }), + ], + ), + Text( + "APP消息介绍".tr, + style: TextStyle( + color: themeController + .currentColor.sc4, + fontSize: AppConstants() + .normal_text_fontSize), + ), + // Row( + // mainAxisSize: MainAxisSize.max, + // mainAxisAlignment: + // MainAxisAlignment.spaceBetween, + // children: [ + // Expanded( + // child: Text( + // "短信消息".tr, + // style: TextStyle( + // color: themeController + // .currentColor.sc3, + // fontSize: AppConstants() + // .title_text_fontSize), + // ), + // ), + // Obx(() { + // return GradientSwitch( + // value: commonMessageSettingController + // .model.setting == + // 1 + // ? (messageSettingController + // .model.setting == + // 1 + // ? (messageSettingController + // .model + // .serviceSetting == + // 1 + // ? true + // : false) + // : false) + // : false, + // onChanged: (val) { + // if (commonMessageSettingController + // .model.setting == + // 0) { + // TopSlideNotification.show( + // context, + // text: "请先在设置里的消息通知打开全部消息配置" + // .tr, + // textColor: themeController + // .currentColor.sc9); + // return; + // } + // if (messageSettingController + // .model.setting == + // 0) { + // TopSlideNotification.show( + // context, + // text: "请先打开消息提醒设置".tr, + // textColor: themeController + // .currentColor.sc9); + // return; + // } + // String serviceAddress = + // ServiceConstant + // .service_address; + // String serviceName = + // ServiceConstant + // .server_service; + // String serviceApi = + // ServiceConstant.user_setting; + // String mac = widget.data['mac']; + // String type = + // "user_device_message_setting_$mac"; + // String queryUrl = + // "${serviceAddress}${serviceName}${serviceApi}"; + // var data = { + // "type": type, + // "setting": + // messageSettingController + // .model.setting, + // "appSetting": + // messageSettingController + // .model.appSetting, + // "serviceSetting": + // val == true ? 1 : 0, + // }; + // requestWithLog( + // logTitle: "更新消息推送状态", + // method: MyHttpMethod.put, + // queryUrl: queryUrl, + // data: data, + // onSuccess: (res) { + // _fetchDeviceMessageSetting(); + // messageSettingController + // .updateAll(); + // }); + // }, + // activeGradient: LinearGradient( + // colors: [ + // themeController + // .currentColor.sc1, + // themeController.currentColor.sc2 + // ], + // ), + // activeThumbColor: Colors.white, + // inactiveThumbColor: + // stringToColor("#A2A4A9"), + // inactiveColor: + // stringToColor("#161B28"), + // ); + // }), + // ], + // ), + // Text( + // "短信消息介绍".tr, + // style: TextStyle( + // color: + // themeController.currentColor.sc4, + // fontSize: AppConstants() + // .normal_text_fontSize), + // ), + // Row( + // mainAxisSize: MainAxisSize.max, + // mainAxisAlignment: + // MainAxisAlignment.spaceBetween, + // children: [ + // Expanded( + // child: Text( + // "电话语音提醒".tr, + // style: TextStyle( + // color: themeController + // .currentColor.sc3, + // fontSize: AppConstants() + // .title_text_fontSize), + // ), + // ), + // Obx(() { + // return GradientSwitch( + // value: commonMessageSettingController + // .model.setting == + // 1 + // ? (messageSettingController + // .model.setting == + // 1 + // ? (messageSettingController + // .model + // .serviceSetting == + // 1 + // ? true + // : false) + // : false) + // : false, + // onChanged: (val) { + // if (commonMessageSettingController + // .model.setting == + // 0) { + // TopSlideNotification.show( + // context, + // text: "请先在设置里的消息通知打开全部消息配置" + // .tr, + // textColor: themeController + // .currentColor.sc9); + // return; + // } + // if (messageSettingController + // .model.setting == + // 0) { + // TopSlideNotification.show( + // context, + // text: "请先打开消息提醒设置".tr, + // textColor: themeController + // .currentColor.sc9); + // return; + // } + // String serviceAddress = + // ServiceConstant + // .service_address; + // String serviceName = + // ServiceConstant + // .server_service; + // String serviceApi = + // ServiceConstant.user_setting; + // String mac = widget.data['mac']; + // String type = + // "user_device_message_setting_$mac"; + // String queryUrl = + // "${serviceAddress}${serviceName}${serviceApi}"; + // var data = { + // "type": type, + // "setting": + // messageSettingController + // .model.setting, + // "appSetting": + // messageSettingController + // .model.appSetting, + // "serviceSetting": + // val == true ? 1 : 0, + // }; + // requestWithLog( + // logTitle: "更新消息推送状态", + // method: MyHttpMethod.put, + // queryUrl: queryUrl, + // data: data, + // onSuccess: (res) { + // _fetchDeviceMessageSetting(); + // messageSettingController + // .updateAll(); + // }); + // }, + // activeGradient: LinearGradient( + // colors: [ + // themeController + // .currentColor.sc1, + // themeController.currentColor.sc2 + // ], + // ), + // activeThumbColor: Colors.white, + // inactiveThumbColor: + // stringToColor("#A2A4A9"), + // inactiveColor: + // stringToColor("#161B28"), + // ); + // }), + // ], + // ), + // Text( + // "电话语音提醒介绍".tr, + // style: TextStyle( + // color: + // themeController.currentColor.sc4, + // fontSize: AppConstants() + // .normal_text_fontSize), + // ), - // 获取当前的 appSetting 值 - int appSetting = + Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + "服务号消息".tr, + style: TextStyle( + color: themeController + .currentColor.sc3, + fontSize: AppConstants() + .title_text_fontSize), + ), + ), + Obx(() { + return GradientSwitch( + value: getServiceMessageSwitch( + widget.data), + onChanged: (val) { + if (commonMessageSettingController + .model.setting == + 0) { + TopSlideNotification.show( + context, + text: + "请先在设置里的消息通知打开全部消息配置" + .tr, + textColor: + themeController + .currentColor + .sc9); + return; + } + if (messageSettingController + .model.setting == + 0) { + TopSlideNotification.show( + context, + text: "请先打开消息提醒设置".tr, + textColor: + themeController + .currentColor + .sc9); + return; + } + String serviceAddress = + ServiceConstant + .service_address; + String serviceName = + ServiceConstant + .server_service; + String serviceApi = + ServiceConstant + .user_setting; + String mac = widget + .data['device']['mac']; + String type = + "user_device_message_setting_$mac"; + String queryUrl = + "${serviceAddress}${serviceName}${serviceApi}"; + int targetId = + widget.data['id']; + int configIndex = + messageSettingController + .model + .device_type_setting + .indexWhere((item) => + item['id'] == + targetId); + if (configIndex != -1) { + // 更新找到的配置项 + messageSettingController + .model + .device_type_setting[ + configIndex] + ['serviceSetting'] = + val ? 1 : 0; + + // 获取当前的 appSetting 值 + int appSetting = + messageSettingController + .model + .device_type_setting[ + configIndex] + [ + 'appSetting'] ?? + 0; + + // 更新 setting 字段 + // 只要 appSetting 或 serviceSetting 中有一个为1,setting就为1 + // 只有当两者都为0时,setting才为0 + messageSettingController + .model + .device_type_setting[ + configIndex][ + 'setting'] = (appSetting == + 1 || + val) + ? 1 + : 0; + } else { + messageSettingController + .model + .device_type_setting + .add({ + "id": widget.data['id'], + "setting": 1, + "max": widget.data['max'], + "appSetting": 1, + "serviceSetting": val, + }); + } + var data = { + "type": type, + "setting": + messageSettingController + .model.setting, + "device_type_setting": + messageSettingController + .model + .device_type_setting, + }; + requestWithLog( + logTitle: "更新消息推送状态", + method: MyHttpMethod.put, + queryUrl: queryUrl, + data: data, + onSuccess: (res) { + _fetchDeviceMessageSetting(); + messageSettingController + .updateAll(); + }); + }, + activeGradient: LinearGradient( + colors: [ + themeController + .currentColor.sc1, + themeController + .currentColor.sc2 + ], + ), + activeThumbColor: Colors.white, + inactiveThumbColor: + stringToColor("#A2A4A9"), + inactiveColor: + stringToColor("#161B28"), + ); + }), + ], + ), + Text( + "服务号消息介绍".tr, + style: TextStyle( + color: themeController + .currentColor.sc4, + fontSize: AppConstants() + .normal_text_fontSize), + ), + ].divide(SizedBox( + height: 49.rpx, + )), + ), + ), + ), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 0, 30.rpx, 0, 0), + child: Container( + width: double.infinity, + constraints: BoxConstraints( + minHeight: 90.rpx, + ), + decoration: BoxDecoration( + color: themeController.currentColor.sc5), + child: Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 30.rpx, 15.rpx, 30.rpx, 15.rpx), + child: Row( + mainAxisSize: MainAxisSize.max, + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Text( + "报警条件设置".tr, + style: TextStyle( + color: themeController + .currentColor.sc3, + fontSize: AppConstants() + .title_text_fontSize), + ), + ), + ClickableContainer( + backgroundColor: Colors.transparent, + highlightColor: Colors.transparent, + padding: EdgeInsets.all( + AppConstants().text_padding), + onTap: () { + String serviceAddress = + ServiceConstant.service_address; + String serviceName = + ServiceConstant.server_service; + String serviceApi = + ServiceConstant.user_setting; + String mac = + widget.data['device']['mac']; + String type = + "user_device_message_setting_$mac"; + String queryUrl = + "${serviceAddress}${serviceName}${serviceApi}"; + int targetId = widget.data['id']; + + Map? defaultConfig; + + defaultConfig = + messageSettingController + .messageType['real'] + ?.firstWhere( + (item) => + item['id'] == + targetId, + orElse: () => null); + if (defaultConfig == null) { + defaultConfig = + messageSettingController + .messageType['report'] + ?.firstWhere( + (item) => + item['id'] == + targetId, + orElse: () => null); + } + int configIndex = + messageSettingController + .model.device_type_setting + .indexWhere((item) => + item['id'] == targetId); + if (defaultConfig != null) { + if (configIndex != -1) { + // 更新找到的配置项 - 恢复所有默认字段 + // 创建一个新的 map,包含所有默认字段 + Map + updatedConfig = { + ...defaultConfig, // 展开所有默认字段 + // 保留原有的配置开关,但恢复其他字段 + 'setting': messageSettingController + .model + .device_type_setting[ + configIndex]['setting'] ?? + 1, + 'appSetting': messageSettingController .model .device_type_setting[ configIndex] ['appSetting'] ?? - 0; - - // 更新 setting 字段 - // 只要 appSetting 或 serviceSetting 中有一个为1,setting就为1 - // 只有当两者都为0时,setting才为0 - messageSettingController.model - .device_type_setting[ - configIndex] - ['setting'] = (appSetting == - 1 || - val) - ? 1 - : 0; - } else { - messageSettingController - .model.device_type_setting - .add({ - "id": widget.data['id'], - "setting": 1, - "max": widget.data['max'], - "appSetting": 1, - "serviceSetting": val, - }); - } - var data = { - "type": type, - "setting": + 1, + 'serviceSetting': messageSettingController - .model.setting, - "device_type_setting": - messageSettingController - .model - .device_type_setting, + .model + .device_type_setting[ + configIndex][ + 'serviceSetting'] ?? + 1, }; - requestWithLog( - logTitle: "更新消息推送状态", - method: MyHttpMethod.put, - queryUrl: queryUrl, - data: data, - onSuccess: (res) { - _fetchDeviceMessageSetting(); - messageSettingController - .updateAll(); - }); - }, - activeGradient: LinearGradient( - colors: [ - themeController - .currentColor.sc1, - themeController.currentColor.sc2 - ], - ), - activeThumbColor: Colors.white, - inactiveThumbColor: - stringToColor("#A2A4A9"), - inactiveColor: - stringToColor("#161B28"), - ); - }), - ], - ), - Text( - "服务号消息介绍".tr, - style: TextStyle( - color: - themeController.currentColor.sc4, - fontSize: AppConstants() - .normal_text_fontSize), - ), - ].divide(SizedBox( - height: 49.rpx, - )), + + // 替换原有的配置 + messageSettingController.model + .device_type_setting[ + configIndex] = updatedConfig; + } else { + // 如果不存在,创建一个新的配置项,使用默认值 + messageSettingController + .model.device_type_setting + .add({ + ...defaultConfig, // 展开所有默认字段 + 'setting': 1, + 'appSetting': 1, + 'serviceSetting': 1, + }); + } + + // 更新控制器,触发 UI 刷新 + messageSettingController.update(); + } + var data = { + "type": type, + "setting": messageSettingController + .model.setting, + "device_type_setting": + messageSettingController + .model.device_type_setting, + }; + requestWithLog( + logTitle: "更新消息推送状态", + method: MyHttpMethod.put, + queryUrl: queryUrl, + data: data, + onSuccess: (res) { + _fetchDeviceMessageSetting(); + messageSettingController + .updateAll(); + }); + }, + child: Text( + "恢复默认".tr, + style: TextStyle( + color: themeController + .currentColor.sc2, + fontSize: AppConstants() + .middler_text_fontSize), + ), + ), + ], + ), ), ), ), - ), + Obx(() { + var aa = messageSettingController.messageType; + print("${aa}"); + return Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 30.rpx, 30.rpx, 30.rpx, 0), + child: getContentById(widget.data, context), + ); + }), + SizedBox( + height: 60.rpx, + ) + ], ), - Padding( - padding: - EdgeInsetsDirectional.fromSTEB(0, 30.rpx, 0, 0), - child: Container( - width: double.infinity, - constraints: BoxConstraints( - minHeight: 90.rpx, - ), - decoration: BoxDecoration( - color: themeController.currentColor.sc5), - child: Padding( - padding: EdgeInsetsDirectional.fromSTEB( - 30.rpx, 15.rpx, 30.rpx, 15.rpx), - child: Row( - mainAxisSize: MainAxisSize.max, - mainAxisAlignment: - MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Text( - "报警条件设置".tr, - style: TextStyle( - color: - themeController.currentColor.sc3, - fontSize: AppConstants() - .title_text_fontSize), - ), - ), - ClickableContainer( - backgroundColor: Colors.transparent, - highlightColor: Colors.transparent, - padding: EdgeInsets.all( - AppConstants().text_padding), - onTap: () { - String serviceAddress = - ServiceConstant.service_address; - String serviceName = - ServiceConstant.server_service; - String serviceApi = - ServiceConstant.user_setting; - String mac = widget.data['device']['mac']; - String type = - "user_device_message_setting_$mac"; - String queryUrl = - "${serviceAddress}${serviceName}${serviceApi}"; - int targetId = widget.data['id']; - - Map? defaultConfig; - - defaultConfig = messageSettingController - .messageType['real'] - ?.firstWhere( - (item) => item['id'] == targetId, - orElse: () => null); - if (defaultConfig == null) { - defaultConfig = messageSettingController - .messageType['report'] - ?.firstWhere( - (item) => - item['id'] == targetId, - orElse: () => null); - } - int configIndex = messageSettingController - .model.device_type_setting - .indexWhere( - (item) => item['id'] == targetId); - if (defaultConfig != null) { - if (configIndex != -1) { - // 更新找到的配置项 - 恢复所有默认字段 - // 创建一个新的 map,包含所有默认字段 - Map updatedConfig = { - ...defaultConfig, // 展开所有默认字段 - // 保留原有的配置开关,但恢复其他字段 - 'setting': messageSettingController - .model - .device_type_setting[ - configIndex]['setting'] ?? - 1, - 'appSetting': messageSettingController - .model - .device_type_setting[ - configIndex]['appSetting'] ?? - 1, - 'serviceSetting': - messageSettingController.model - .device_type_setting[ - configIndex] - ['serviceSetting'] ?? - 1, - }; - - // 替换原有的配置 - messageSettingController - .model.device_type_setting[ - configIndex] = updatedConfig; - } else { - // 如果不存在,创建一个新的配置项,使用默认值 - messageSettingController - .model.device_type_setting - .add({ - ...defaultConfig, // 展开所有默认字段 - 'setting': 1, - 'appSetting': 1, - 'serviceSetting': 1, - }); - } - - // 更新控制器,触发 UI 刷新 - messageSettingController.update(); - } - var data = { - "type": type, - "setting": messageSettingController - .model.setting, - "device_type_setting": - messageSettingController - .model.device_type_setting, - }; - requestWithLog( - logTitle: "更新消息推送状态", - method: MyHttpMethod.put, - queryUrl: queryUrl, - data: data, - onSuccess: (res) { - _fetchDeviceMessageSetting(); - messageSettingController - .updateAll(); - }); - }, - child: Text( - "恢复默认".tr, - style: TextStyle( - color: - themeController.currentColor.sc2, - fontSize: AppConstants() - .middler_text_fontSize), - ), - ), - ], - ), - ), - ), - ), - Obx(() { - var aa = messageSettingController.messageType; - print("${aa}"); - return Padding( - padding: EdgeInsetsDirectional.fromSTEB( - 30.rpx, 30.rpx, 30.rpx, 0), - child: getContentById(widget.data, context), - ); - }), - SizedBox( - height: 60.rpx, - ) - ], + ), ), ), ), @@ -1093,8 +1146,10 @@ class _MessageSettingPageState extends State { ), ), ), - ), - ); + onWillPop: () async { + NewTopSlideNotification.show(text: "保存成功".tr); + return true; + }); } void _fetchMessageCommonConfig() { diff --git a/lib/pages/device/component/messageTool.dart b/lib/pages/device/component/messageTool.dart index e275a9a..bb16216 100644 --- a/lib/pages/device/component/messageTool.dart +++ b/lib/pages/device/component/messageTool.dart @@ -96,7 +96,7 @@ getContentById(data, BuildContext context) { ), child: Padding( padding: - EdgeInsetsDirectional.fromSTEB(40.rpx, 20.rpx, 40.rpx, 20.rpx), + EdgeInsetsDirectional.fromSTEB(30.rpx, 20.rpx, 30.rpx, 20.rpx), child: Column( children: [ /// --- 心率小于 --- @@ -379,8 +379,8 @@ getContentById(data, BuildContext context) { } else if (id == 100002) { // 呼吸异常 - int min = 10; // 默认 - int max = 40; // 默认 + int min = 8; // 默认 + int max = 25; // 默认 int interval = 600; // 默认 // 若有用户配置 @@ -434,7 +434,7 @@ getContentById(data, BuildContext context) { ), child: Padding( padding: - EdgeInsetsDirectional.fromSTEB(40.rpx, 20.rpx, 40.rpx, 20.rpx), + EdgeInsetsDirectional.fromSTEB(30.rpx, 20.rpx, 30.rpx, 20.rpx), child: Column( children: [ /// ---- 呼吸小于 ---- @@ -1025,7 +1025,7 @@ getContentById(data, BuildContext context) { } else if (id == 100005) { // 未起床 // 1. 优先从 device_type_setting 中查找用户配置 - String time = "23:00"; // 默认值 + String time = "7:00"; // 默认值 // 2. 如果找到了用户配置,使用用户配置中的 time 值 if (userConfig != null) { @@ -1187,7 +1187,7 @@ getContentById(data, BuildContext context) { ); } else if (id == 200001) { // 1. 优先从 device_type_setting 中查找用户配置 - int score = 60; // 默认值 + int score = 50; // 默认值 // 2. 如果找到了用户配置,使用用户配置中的 max 值 if (userConfig != null) { print('找到用户配置: $userConfig'); @@ -1195,7 +1195,7 @@ getContentById(data, BuildContext context) { if (userConfig['max'] != null) { score = userConfig['max'] is int ? userConfig['max'] - : int.tryParse(userConfig['max'].toString()) ?? 60; + : int.tryParse(userConfig['max'].toString()) ?? score; print('从用户配置获取睡眠得分阈值: $score'); } } else { @@ -1207,7 +1207,7 @@ getContentById(data, BuildContext context) { if (defaultConfig['max'] != null) { score = defaultConfig['max'] is int ? defaultConfig['max'] - : int.tryParse(defaultConfig['max'].toString()) ?? 60; + : int.tryParse(defaultConfig['max'].toString()) ?? score; print('从默认配置获取睡眠得分阈值: $score'); } } else { @@ -1235,8 +1235,8 @@ getContentById(data, BuildContext context) { onTap: () async { final currentScore = score; final initialScore = currentScore != null - ? int.tryParse(currentScore.toString()) ?? 60 - : 60; + ? int.tryParse(currentScore.toString()) ?? score + : score; FocusScope.of(context).requestFocus(FocusNode()); Future.delayed(const Duration(milliseconds: 250), () { showScorePickerDialog( diff --git a/lib/pages/device_bind/after/after_update_person_page.dart b/lib/pages/device_bind/after/after_update_person_page.dart index 35bb45c..c82e7d7 100644 --- a/lib/pages/device_bind/after/after_update_person_page.dart +++ b/lib/pages/device_bind/after/after_update_person_page.dart @@ -6,6 +6,7 @@ import 'package:flutterflow_ui/flutterflow_ui.dart'; import 'package:vbvs_app/common/color/ServiceConstant.dart'; import 'package:vbvs_app/common/color/appConstants.dart'; import 'package:vbvs_app/common/color/app_uri_status.dart'; +import 'package:vbvs_app/common/pojo/city.dart'; import 'package:vbvs_app/common/util/FitTool.dart'; import 'package:vbvs_app/common/util/MyUtils.dart'; import 'package:vbvs_app/common/util/requestWithLog.dart'; @@ -19,6 +20,7 @@ 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/api_response.dart'; +import 'package:vbvs_app/pages/person/select_city.dart'; import 'package:vbvs_app/pages/person/select_time.dart'; class AfterUpdatePersonPage extends StatefulWidget { @@ -35,10 +37,21 @@ class _AfterUpdatePersonPageState extends State { PersonController personController = Get.find(); BodyDeviceController bodyDeviceController = Get.find(); ThemeController themeController = Get.find(); + final CityModelController cityController = Get.find(); + late Future> cityDataFuture; @override void initState() { super.initState(); + personController.selectedDiseaseIds.value = []; + personController.name.value = ''; + personController.gender.value = 1; + personController.birthday.value = ""; + personController.weight.value = ""; + personController.height.value = ""; + personController.dateTime = null; + personController.cityModel = null; + personController.timeZone.value = ""; // personController.dateTime = null; personController.getDiseaseData().then((apiResponse) { if (apiResponse.code != HttpStatusCodes.ok) { @@ -49,6 +62,13 @@ class _AfterUpdatePersonPageState extends State { ); } }); + cityDataFuture = cityController.loadAndSetCityData().then((success) { + return cityController.cityList; + }); + personController.getTimeZoneByLocalTime().then((value) { + personController.timeZone.value = value; + personController.updateAll(); + }); } @override @@ -366,6 +386,282 @@ class _AfterUpdatePersonPageState extends State { ), ), ), + // 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(); + // }, + // title: "生日".tr, + // ); + // }); + // }, + // child: Center( + // child: Text( + // personController.dateTime != null + // ? DateFormat("yyyy年MM月dd日").format( + // personController.dateTime!) + // : '人员资料.生日输入提示'.tr, + // textAlign: TextAlign.right, + // style: TextStyle( + // 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 = value; + // }, + // autofocus: false, + // obscureText: false, + // style: TextStyle( + // 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: TextStyle( + // 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( + // width: 1.rpx, + // ), + // borderRadius: + // BorderRadius.circular(8.rpx), + // ), + // focusedErrorBorder: + // OutlineInputBorder( + // borderSide: BorderSide( + // width: 1.rpx, + // ), + // borderRadius: + // BorderRadius.circular(8.rpx), + // ), + // filled: true, + // ), + // ), + // Obx(() { + // final height = + // personController.height.value; + // return (height == null || + // height.isEmpty) + // ? const SizedBox.shrink() // 不显示任何内容 + // : Text( + // '${height}cm', + // textAlign: TextAlign.center, + // style: TextStyle( + // 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 = value; + // }, + // autofocus: false, + // obscureText: false, + // style: TextStyle( + // 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: TextStyle( + // 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( + // width: 1.rpx, + // ), + // borderRadius: + // BorderRadius.circular(8.rpx), + // ), + // focusedErrorBorder: + // OutlineInputBorder( + // borderSide: BorderSide( + // width: 1.rpx, + // ), + // borderRadius: + // BorderRadius.circular(8.rpx), + // ), + // filled: true, + // ), + // ), + // Obx(() { + // final weight = + // personController.weight?.value; + // return (weight == null || + // weight.isEmpty) + // ? const SizedBox.shrink() // 不显示任何内容 + // : Text( + // '${weight}kg', + // textAlign: TextAlign.center, + // style: TextStyle( + // fontFamily: 'Inter', + // color: themeController + // .currentColor.sc3, + // fontSize: AppConstants() + // .normal_text_fontSize, + // ), + // ); + // }), + // ], + // ), + // ), + // ), + // ), + Padding( padding: EdgeInsetsDirectional.fromSTEB( 70.rpx, 50.rpx, 70.rpx, 0), @@ -395,36 +691,82 @@ class _AfterUpdatePersonPageState extends State { personController.dateTime = d; personController.updateAll(); }, - title: "生日".tr, + title: "选择生日".tr, ); }); }, - child: Center( - child: Text( - personController.dateTime != null - ? DateFormat("yyyy年MM月dd日").format( - personController.dateTime!) - : '人员资料.生日输入提示'.tr, - textAlign: TextAlign.right, - style: TextStyle( - fontFamily: 'Readex Pro', - color: personController.dateTime != null - ? themeController.currentColor.sc3 - : themeController.currentColor.sc4, - fontSize: - AppConstants().normal_text_fontSize, - letterSpacing: 0, + child: Stack( + children: [ + // 文字居中 + Center( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text.rich( + TextSpan( + children: [ + TextSpan( + text: personController + .dateTime != + null + ? DateFormat( + "yyyy/MM/dd") + .format( + personController + .dateTime!) + : '人员资料.生日输入提示'.tr, + style: TextStyle( + fontFamily: 'Readex Pro', + color: personController + .dateTime != + null + ? themeController + .currentColor.sc3 + : themeController + .currentColor.sc4, + fontSize: AppConstants() + .normal_text_fontSize, + letterSpacing: 0, + ), + ), + TextSpan( + text: ' *', + style: TextStyle( + color: themeController + .currentColor.sc9, + fontSize: AppConstants() + .normal_text_fontSize, + fontWeight: + FontWeight.bold, + ), + ), + ], + ), + ), + ], + ), ), - ), + // 箭头居右 + Positioned( + right: 30.rpx, + top: 0, + bottom: 0, + child: Icon( + Icons.expand_more, + color: + themeController.currentColor.sc4, + size: 30.rpx, + ), + ), + ], ), ), ), ), Padding( padding: EdgeInsetsDirectional.fromSTEB( - 70.rpx, 18.rpx, 70.rpx, 0), + 70.rpx, 25.rpx, 70.rpx, 0), child: Container( - width: double.infinity, height: 100.rpx, decoration: BoxDecoration( borderRadius: BorderRadius.circular(50.rpx), @@ -434,97 +776,103 @@ class _AfterUpdatePersonPageState extends State { 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 = value; + child: InkWell( + onTap: () { + final currentHeight = + personController.height.value; + final initialHeight = currentHeight != null + ? int.tryParse( + currentHeight.toString()) ?? + 170 + : 170; + FocusScope.of(context) + .requestFocus(FocusNode()); + Future.delayed( + const Duration(milliseconds: 250), () { + showHeightPickerDialog( + context, + title: "选择身高".tr, + initialHeight: initialHeight, + onConfirm: (int selectedHeight) { + personController.height.value = + selectedHeight.toString(); + personController.updateAll(); + print("身高: $selectedHeight cm"); }, - autofocus: false, - obscureText: false, - style: TextStyle( - 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: TextStyle( - fontFamily: 'Inter', - color: themeController - .currentColor.sc4, - fontSize: AppConstants() - .normal_text_fontSize, - letterSpacing: 0.0, - ), - enabledBorder: OutlineInputBorder( - borderSide: BorderSide( - color: Color(0x00000000), - width: 1.rpx, + ); + }); + }, + child: Stack( + children: [ + // 文字居中 + Center( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text.rich( + TextSpan( + children: [ + TextSpan( + text: personController + .height + .value != + null && + personController + .height + .value + .isNotEmpty + ? personController + .height.value + + "cm".tr + : '身高输入提示'.tr, + style: TextStyle( + fontFamily: 'Readex Pro', + color: personController + .height + .value != + null && + personController + .height + .value + .isNotEmpty + ? themeController + .currentColor.sc3 + : themeController + .currentColor.sc4, + fontSize: AppConstants() + .normal_text_fontSize, + letterSpacing: 0, + ), + ), + TextSpan( + text: ' *', + style: TextStyle( + color: themeController + .currentColor.sc9, + fontSize: AppConstants() + .normal_text_fontSize, + fontWeight: + FontWeight.bold, + ), + ), + ], + ), ), - borderRadius: - BorderRadius.circular(8.rpx), - ), - focusedBorder: OutlineInputBorder( - borderSide: BorderSide( - color: Color(0x00000000), - width: 1.rpx, - ), - borderRadius: - BorderRadius.circular(8.rpx), - ), - errorBorder: OutlineInputBorder( - borderSide: BorderSide( - width: 1.rpx, - ), - borderRadius: - BorderRadius.circular(8.rpx), - ), - focusedErrorBorder: - OutlineInputBorder( - borderSide: BorderSide( - width: 1.rpx, - ), - borderRadius: - BorderRadius.circular(8.rpx), - ), - filled: true, + ], + ), + ), + // 箭头居右 + Positioned( + right: 30.rpx, + top: 0, + bottom: 0, + child: Icon( + Icons.expand_more, + color: + themeController.currentColor.sc4, + size: 30.rpx, ), ), - Obx(() { - final height = - personController.height.value; - return (height == null || - height.isEmpty) - ? const SizedBox.shrink() // 不显示任何内容 - : Text( - '${height}cm', - textAlign: TextAlign.center, - style: TextStyle( - fontFamily: 'Inter', - color: themeController - .currentColor.sc3, - fontSize: AppConstants() - .normal_text_fontSize, - ), - ); - }), ], ), ), @@ -532,9 +880,8 @@ class _AfterUpdatePersonPageState extends State { ), Padding( padding: EdgeInsetsDirectional.fromSTEB( - 70.rpx, 18.rpx, 70.rpx, 0), + 70.rpx, 25.rpx, 70.rpx, 0), child: Container( - width: double.infinity, height: 100.rpx, decoration: BoxDecoration( borderRadius: BorderRadius.circular(50.rpx), @@ -544,98 +891,316 @@ class _AfterUpdatePersonPageState extends State { 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 = value; + child: InkWell( + onTap: () { + final currentWeight = + personController.weight.value; + final initialWeight = currentWeight != null + ? int.tryParse( + currentWeight.toString()) ?? + 60 + : 60; + FocusScope.of(context) + .requestFocus(FocusNode()); + Future.delayed( + const Duration(milliseconds: 250), () { + showWeightPickerDialog( + context, + title: "选择体重".tr, + initialWeight: "$initialWeight", + onConfirm: (int selectedWeight) { + personController.weight.value = + selectedWeight.toString(); + personController.updateAll(); + print("体重: $selectedWeight kg"); }, - autofocus: false, - obscureText: false, - style: TextStyle( - 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: TextStyle( - fontFamily: 'Inter', - color: themeController - .currentColor.sc4, - fontSize: AppConstants() - .normal_text_fontSize, - letterSpacing: 0.0, - ), - enabledBorder: OutlineInputBorder( - borderSide: BorderSide( - color: Color(0x00000000), - width: 1.rpx, + ); + }); + }, + child: Stack( + children: [ + // 文字居中 + Center( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text.rich( + TextSpan( + children: [ + TextSpan( + text: personController + .weight + .value != + null && + personController + .weight + .value + .isNotEmpty + ? personController + .weight.value + + "kg".tr + : '人员资料.体重输入提示'.tr, + style: TextStyle( + fontFamily: 'Readex Pro', + color: personController + .weight + .value != + null && + personController + .weight + .value + .isNotEmpty + ? themeController + .currentColor.sc3 + : themeController + .currentColor.sc4, + fontSize: AppConstants() + .normal_text_fontSize, + letterSpacing: 0, + ), + ), + TextSpan( + text: ' *', + style: TextStyle( + color: themeController + .currentColor.sc9, + fontSize: AppConstants() + .normal_text_fontSize, + fontWeight: + FontWeight.bold, + ), + ), + ], + ), ), - borderRadius: - BorderRadius.circular(8.rpx), - ), - focusedBorder: OutlineInputBorder( - borderSide: BorderSide( - color: Color(0x00000000), - width: 1.rpx, - ), - borderRadius: - BorderRadius.circular(8.rpx), - ), - errorBorder: OutlineInputBorder( - borderSide: BorderSide( - width: 1.rpx, - ), - borderRadius: - BorderRadius.circular(8.rpx), - ), - focusedErrorBorder: - OutlineInputBorder( - borderSide: BorderSide( - width: 1.rpx, - ), - borderRadius: - BorderRadius.circular(8.rpx), - ), - filled: true, + ], ), ), - Obx(() { - final weight = - personController.weight?.value; - return (weight == null || - weight.isEmpty) - ? const SizedBox.shrink() // 不显示任何内容 - : Text( - '${weight}kg', - textAlign: TextAlign.center, - style: TextStyle( - fontFamily: 'Inter', - color: themeController - .currentColor.sc3, - fontSize: AppConstants() - .normal_text_fontSize, + // 箭头居右 + Positioned( + right: 30.rpx, + top: 0, + bottom: 0, + child: Icon( + Icons.expand_more, + color: + themeController.currentColor.sc4, + size: 30.rpx, + ), + ), + ], + ), + ), + ), + ), + + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 70.rpx, 25.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: () { + final timeZone = + personController.timeZone.value; + final initialTimeZone = timeZone; + + FocusScope.of(context) + .requestFocus(FocusNode()); + Future.delayed( + const Duration(milliseconds: 250), () { + showTimeZonePickerDialog( + context, + title: "选择时区".tr, + initialTimeZone: initialTimeZone, + onConfirm: (String selectedtimezone) { + personController.timeZone.value = + selectedtimezone.toString(); + personController.updateAll(); + print("时区: $selectedtimezone"); + }, + ); + }); + }, + child: Obx(() { + return Stack( + children: [ + // 文字居中 + Center( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text.rich( + TextSpan( + children: [ + TextSpan( + text: personController + .timeZone + .value != + "" + ? personController + .timeZone.value + : '选择时区'.tr, + style: TextStyle( + fontFamily: + 'Readex Pro', + color: personController + .timeZone + .value != + "" + ? themeController + .currentColor + .sc3 + : themeController + .currentColor + .sc4, + fontSize: AppConstants() + .normal_text_fontSize, + letterSpacing: 0, + ), + ), + TextSpan( + text: ' *', + style: TextStyle( + color: themeController + .currentColor.sc9, + fontSize: AppConstants() + .normal_text_fontSize, + fontWeight: + FontWeight.bold, + ), + ), + ], ), - ); - }), + ), + ], + ), + ), + // 箭头居右 + Positioned( + right: 30.rpx, + top: 0, + bottom: 0, + child: Icon( + Icons.expand_more, + color: themeController + .currentColor.sc4, + size: 30.rpx, + ), + ), + ], + ); + }), + ), + ), + ), + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 70.rpx, 25.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), + () { + // 使用当前选中的城市数据,如果没有则创建默认 + CityModel currentCity = + personController.cityModel ?? + CityModel(); + showCitySelectionDialog( + context, + selectedCity: currentCity, + onCityChanged: (CityModel newCity) { + // 处理城市选择变化 + print( + 'Selected city: ${newCity.toJson()}'); + personController.cityModel = newCity; + personController.updateAll(); + }, + title: "选择城市".tr, + cityDataFuture: + cityDataFuture, // 传入预加载的数据 + colors: CitySelectionColors( + // pickerBackgroundColor: + // stringToColor("#003058"), + // confirmTextColor: + // stringToColor("#84F5FF"), + selectedCityColor: + themeController.currentColor.sc2, + selectedTextColor: Colors.white, + ), + ); + }); + }, + child: Stack( + children: [ + // 文字居中 + Center( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + _getDetailedCityDisplayText( + personController.cityModel), + textAlign: TextAlign.center, + style: TextStyle( + fontFamily: 'Readex Pro', + color: + personController + .cityModel != + null + ? themeController + .currentColor.sc3 + : themeController + .currentColor.sc4, + fontSize: AppConstants() + .normal_text_fontSize, + letterSpacing: 0, + ), + ), + // Text( + // ' *', + // style: TextStyle( + // color: themeController + // .currentColor.sc9, + // fontSize: AppConstants() + // .normal_text_fontSize, + // fontWeight: FontWeight.bold, + // ), + // ), + ], + ), + ), + // 箭头居右 + Positioned( + right: 30.rpx, + top: 0, + bottom: 0, + child: Icon( + Icons.expand_more, + color: + themeController.currentColor.sc4, + size: 30.rpx, + ), + ), ], ), ), @@ -643,7 +1208,7 @@ class _AfterUpdatePersonPageState extends State { ), Padding( padding: EdgeInsetsDirectional.fromSTEB( - 0, 117.rpx, 0, 0), + 0, 60.rpx, 0, 0), child: Container( width: double.infinity, decoration: BoxDecoration(), @@ -840,4 +1405,27 @@ class _AfterUpdatePersonPageState extends State { onFailure: (res) {}, ); } + + String _getDetailedCityDisplayText(CityModel? cityModel) { + if (cityModel == null) { + return '选择城市'.tr; + } + + // 根据数据层级显示不同的格式 + if (cityModel.city != null && cityModel.city!.isNotEmpty) { + // 三级数据:国家-省份-城市 + return '${cityModel.country ?? ''}-${cityModel.province ?? ''}-${cityModel.city ?? cityModel.value ?? ''}'; + } else if (cityModel.province != null && cityModel.province!.isNotEmpty) { + // 二级数据:国家-省份 + return '${cityModel.country ?? ''}-${cityModel.province ?? cityModel.value ?? ''}'; + } else if (cityModel.country != null && cityModel.country!.isNotEmpty) { + // 一级数据:国家 + return cityModel.country!; + } else if (cityModel.value != null && cityModel.value!.isNotEmpty) { + // 只有 value 字段 + return cityModel.value!; + } else { + return '选择城市'.tr; + } + } } diff --git a/lib/pages/device_bind/device_type.dart b/lib/pages/device_bind/device_type.dart index 3be4ebe..f0b20a8 100644 --- a/lib/pages/device_bind/device_type.dart +++ b/lib/pages/device_bind/device_type.dart @@ -384,7 +384,7 @@ class _EPageState extends State { } Widget _buildDeviceCard(BuildContext context, - {required String title, required String imageUrl, required double type}) { + {required String title, required String imageUrl, required int type}) { if (type != 1) { return Container(); } diff --git a/lib/pages/device_bind/device_type_list.dart b/lib/pages/device_bind/device_type_list.dart index 7fa3c41..e024394 100644 --- a/lib/pages/device_bind/device_type_list.dart +++ b/lib/pages/device_bind/device_type_list.dart @@ -134,7 +134,7 @@ class _DeviceTypeListPageState extends State { } Widget _buildDeviceCard(BuildContext context, - {required String title, required String imageUrl, required double type}) { + {required String title, required String imageUrl, required int type}) { if (type != 1) { return Container(); } diff --git a/lib/pages/login/other_login.dart b/lib/pages/login/other_login.dart index e8a45d2..7698ae9 100644 --- a/lib/pages/login/other_login.dart +++ b/lib/pages/login/other_login.dart @@ -728,8 +728,8 @@ class _OtherLoginPageState extends State { weatherModelController = Get.find(); await weatherModelController .getCurrentLocation(); - await weatherModelController - .getCurrentWeather(); + // await weatherModelController + // .getCurrentWeather(); } } } catch (e) { diff --git a/lib/pages/main_bottom/home_page.dart b/lib/pages/main_bottom/home_page.dart index 27a43de..f3d9109 100644 --- a/lib/pages/main_bottom/home_page.dart +++ b/lib/pages/main_bottom/home_page.dart @@ -884,6 +884,7 @@ class _HomePageState extends State { homeController.model.type = 1; deviceController.model.type = 1; + homeController.updateAll(); await deviceController .getDeviceList(); await deviceController @@ -942,6 +943,7 @@ class _HomePageState extends State { onTap: () async { homeController.model.type = 2; deviceController.model.type = 2; + homeController.updateAll(); await deviceController .getDeviceList(); await deviceController diff --git a/lib/pages/main_bottom/message_page.dart b/lib/pages/main_bottom/message_page.dart index a31b9d6..3ed0665 100644 --- a/lib/pages/main_bottom/message_page.dart +++ b/lib/pages/main_bottom/message_page.dart @@ -58,6 +58,16 @@ class _MessagePageState extends State { } void _onTabChanged(int index) { + int currentIndex = messageController.model.type == 1 ? 0 : 1; + + // 只有当切换到不同tab时才重置加载状态 + if (currentIndex != index) { + messageController.isLoadingMore = false; + + messageController.bodyPage = 1; + messageController.systemPage = 1; + } + messageController.model.type = index == 0 ? 1 : 2; messageController.updateAll(); _fetchMessageData(); @@ -298,56 +308,47 @@ class _MessagePageState extends State { ), backgroundColor: Colors.transparent, body: SafeArea( - top: true, - child: PageView( - controller: _pageController, - onPageChanged: _onPageChanged, - children: [ - Obx(() { - final list = messageController.messageList.value; - return list.isEmpty - ? const NullDataWidget() - : _buildMessageListView(list, "app_vsm"); - }), - Obx(() { - final list = messageController.messageList.value; - return list.isEmpty - ? const NullDataWidget() - : _buildMessageListView(list, "app_system"); - }), - ], - ), - ), + top: true, + child: Scrollbar( + child: PageView( + controller: _pageController, + onPageChanged: _onPageChanged, + children: [ + Obx(() { + final list = messageController.messageList.value; + return list.isEmpty + ? const NullDataWidget() + : _buildMessageListView(list, "app_vsm"); + }), + Obx(() { + final list = messageController.messageList.value; + return list.isEmpty + ? const NullDataWidget() + : _buildMessageListView(list, "app_system"); + }), + ], + ), + )), ), ), ), ); } - // Widget _buildMessageListView(List dataList) { - // return Container( - // width: double.infinity, - // padding: EdgeInsets.symmetric(horizontal: 30.rpx), - // child: SingleChildScrollView( - // child: Column( - // children: [ - // SizedBox(height: 30.rpx), - // ...dataList - // .map((item) => MessageWidgetWidget(data: item)) - // .toList() - // .divide(SizedBox(height: 30.rpx)), - // SizedBox(height: 30.rpx), - // ], - // ), - // ), - // ); - // } Widget _buildMessageListView(List dataList, String type) { return NotificationListener( onNotification: (scrollInfo) { - if (scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent) { + // 检查是否滚动到底部,并且没有正在加载 + if (scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent && + !messageController.isLoadingMore) { + messageController.isLoadingMore = true; // 滑到底部,加载下一页 - messageController.loadMore(type); + messageController.loadMore(type).then((_) { + // 加载完成后重置标志位 + messageController.isLoadingMore = false; + }).catchError((_) { + messageController.isLoadingMore = false; + }); } return true; }, diff --git a/lib/pages/main_bottom/message_page_return.dart b/lib/pages/main_bottom/message_page_return.dart index b5f388b..a89eec7 100644 --- a/lib/pages/main_bottom/message_page_return.dart +++ b/lib/pages/main_bottom/message_page_return.dart @@ -48,6 +48,16 @@ class _MessageReturnPageState extends State { } void _onTabChanged(int index) { + int currentIndex = messageController.model.type == 1 ? 0 : 1; + + // 只有当切换到不同tab时才重置加载状态 + if (currentIndex != index) { + messageController.isLoadingMore = false; + + messageController.bodyPage = 1; + messageController.systemPage = 1; + } + messageController.model.type = index == 0 ? 1 : 2; messageController.updateAll(); _fetchMessageData(); @@ -297,7 +307,8 @@ class _MessageReturnPageState extends State { height: 4.rpx, decoration: BoxDecoration( color: themeController.currentColor.sc2, - borderRadius: BorderRadius.circular(2.rpx), + borderRadius: + BorderRadius.circular(2.rpx), ), ), ); @@ -338,13 +349,21 @@ class _MessageReturnPageState extends State { ), ); } - + Widget _buildMessageListView(List dataList, String type) { return NotificationListener( onNotification: (scrollInfo) { - if (scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent) { + // 检查是否滚动到底部,并且没有正在加载 + if (scrollInfo.metrics.pixels == scrollInfo.metrics.maxScrollExtent && + !messageController.isLoadingMore) { + messageController.isLoadingMore = true; // 滑到底部,加载下一页 - messageController.loadMore(type); + messageController.loadMore(type).then((_) { + // 加载完成后重置标志位 + messageController.isLoadingMore = false; + }).catchError((_) { + messageController.isLoadingMore = false; + }); } return true; }, @@ -360,4 +379,4 @@ class _MessageReturnPageState extends State { ), ); } -} \ No newline at end of file +} diff --git a/lib/pages/main_bottom/mine_page.dart b/lib/pages/main_bottom/mine_page.dart index 2949197..65b82f5 100644 --- a/lib/pages/main_bottom/mine_page.dart +++ b/lib/pages/main_bottom/mine_page.dart @@ -703,7 +703,7 @@ class _MinePageState extends State { mainAxisSize: MainAxisSize.max, children: [ Text( - 'V1.0.2512.10', + 'V1.0.2601.06', style: TextStyle( fontFamily: 'Inter', // color: Color(0xFFD9E3EB), diff --git a/lib/pages/mh_page/device/mht_people_info.dart b/lib/pages/mh_page/device/mht_people_info.dart index 565bc75..8e9af76 100644 --- a/lib/pages/mh_page/device/mht_people_info.dart +++ b/lib/pages/mh_page/device/mht_people_info.dart @@ -72,6 +72,21 @@ class _MHTPeopleInfoPageState extends State { }); } + PeopleInfoController personController = Get.find(); + personController.getTimeZoneByLocalTime().then((value) { + personController.timeZone.value = value; + if (value == null || value.isEmpty) { + personController.timeZone.value = "UTC+8"; + } + if (peopleList != null && peopleList.length > 0) { + ef.log("msg"); + for (int i = 0; i < peopleList.length; i++) { + peopleList[i]['UTC'] = personController.timeZone.value; + } + } + personController.updateAll(); + }); + // 初始化城市模型列表 cityModels = List.filled(peopleList.length, CityModel()); @@ -375,7 +390,7 @@ class _MHTPeopleInfoPageState extends State { final diseaseIds = diseaseIdsList[i]; // 添加城市信息到person数据 - person['UTC'] = cityModel.UTC; + // person['UTC'] = cityModel.UTC; person['city_id'] = cityModel.id; person['disease_ids'] = diseaseIds; // 添加疾病ID @@ -917,6 +932,98 @@ class _MHTPeopleInfoPageState extends State { ), ), getLine(), + Container( + height: 90.rpx, + margin: EdgeInsets.only( + left: 40.rpx, + right: 35.rpx, + ), + child: InkWell( + onTap: () { + FocusScope.of(context) + .requestFocus(FocusNode()); + Future.delayed( + const Duration(milliseconds: 250), + () { + // 获取当前时区 + String? currentTimeZone; + if (cityModels.isNotEmpty && + index < cityModels.length) { + currentTimeZone = + cityModels[index].UTC; + } + + showTimeMHTZonePickerDialog( + context, + title: "请选择时区".tr, + initialTimeZone: + currentTimeZone ?? "", + onConfirm: + (String selectedTimeZone) { + setState(() { + peopleList[index]['UTC'] = + selectedTimeZone; + }); + }, + ); + }, + ); + }, + child: Row( + mainAxisAlignment: + MainAxisAlignment.spaceBetween, + children: [ + Text( + '请选择时区'.tr, + style: TextStyle( + fontFamily: 'Readex Pro', + color: Color(0xFF9EA4B7), + fontSize: 30.rpx, + letterSpacing: 0, + ), + ), + Obx(() { + PeopleInfoController + personController = Get.find(); + var aa = + personController.timeZone.value; + return Row( + children: [ + Text( + peopleList[index]['UTC'] == + null || + peopleList[index] + ['UTC'] == + "" + ? "请选择时区".tr + : peopleList[index]['UTC'], + style: TextStyle( + color: peopleList[index] + ['UTC'] != + null + ? Colors + .white // 有时区时使用白色,保持和参考代码一致 + : themeController + .currentColor.sc4, + fontSize: 30 + .rpx, // 使用固定的30.rpx,而不是AppConstants().title_text_fontSize + ), + ), + SizedBox(width: 16.rpx), + Icon( + Icons.expand_more, + color: Colors + .white, // 保持白色,和参考代码一致 + size: 48.rpx, + ), + ], + ); + }) + ], + ), + ), + ), + getLine(), // 慢病管理部分 Container( height: 90.rpx, diff --git a/lib/pages/mh_page/new_settingPage.dart b/lib/pages/mh_page/new_settingPage.dart index 1b62830..c49f6e3 100644 --- a/lib/pages/mh_page/new_settingPage.dart +++ b/lib/pages/mh_page/new_settingPage.dart @@ -253,7 +253,7 @@ class _SettingPageState extends State { ), ].divide(SizedBox(width: 22.rpx)), ), - Text('SWES2025.12.30', + Text('SWES2026.1.5', style: TextStyle( color: Colors.white, fontSize: 26.rpx, diff --git a/lib/pages/mh_page/people_info.dart b/lib/pages/mh_page/people_info.dart index d4fb82b..6382e0c 100644 --- a/lib/pages/mh_page/people_info.dart +++ b/lib/pages/mh_page/people_info.dart @@ -142,22 +142,6 @@ class PeopleInfoPage extends GetView { isValid = false; break; } - // if (d['UTC'] == null || - // d['UTC'].toString().isEmpty) { - // TopSlideNotification.show(context, - // text: "请选择城市".tr, - // textColor: Color(0xFFFF7159)); - // isValid = false; - // break; - // } - // if (d['city_id'] ==null || - // d['city_id'].toString().isEmpty) { - // TopSlideNotification.show(context, - // text: "请选择城市".tr, - // textColor: Color(0xFFFF7159)); - // isValid = false; - // break; - // } } // 所有数据合法,开始保存 if (isValid) { @@ -890,10 +874,10 @@ class PeopleInfoPage extends GetView { // 补齐并追加 list.add(newCity); } - controller.model - .peopleList[ - index]['UTC'] = - list[index].UTC; + // controller.model + // .peopleList[ + // index]['UTC'] = + // list[index].UTC; controller.model .peopleList[ index] @@ -952,14 +936,17 @@ class PeopleInfoPage extends GetView { : "请选择城市".tr, style: TextStyle( color: getCityModel( - index) != - null + index) != + null && + getCityModel( + index)! + .id != + null ? themeController .currentColor .sc3 - : themeController - .currentColor - .sc4, + : Color( + 0xFF9EA4B7), fontSize: AppConstants() .title_text_fontSize, ), @@ -981,6 +968,160 @@ class PeopleInfoPage extends GetView { ), ), getLine(), + Container( + height: 90.rpx, + margin: EdgeInsets.only( + left: 40.rpx, + right: 35.rpx, + ), + child: InkWell( + onTap: () { + // if (widget.status == + // BindType.share.code) { + // TopSlideNotification.show( + // context, + // text: "被分享用户只能修改用户名称", + // textColor: + // themeController + // .currentColor + // .sc9); + // return; + // } + FocusScope.of(context) + .requestFocus( + FocusNode()); + Future.delayed( + Duration( + milliseconds: 250), + () { + // 获取当前时区 + String? currentTimeZone; + if (controller + .model + .peopleList + .isNotEmpty && + index < + controller + .model + .peopleList + .length && + controller.model + .peopleList[ + index]['UTC'] != + null) { + currentTimeZone = + controller.model + .peopleList[ + index]['UTC']; + } + + showTimeMHTZonePickerDialog( + context, + title: "请选择时区".tr, + initialTimeZone: + currentTimeZone ?? "", + onConfirm: (String + selectedTimeZone) { + controller.model + .peopleList[ + index]['UTC'] = + selectedTimeZone; + }, + ); + }); + }, + child: Row( + mainAxisAlignment: + MainAxisAlignment + .spaceBetween, + children: [ + Text( + '请选择时区'.tr, + style: TextStyle( + fontFamily: + 'Readex Pro', + color: + Color(0xFF9EA4B7), + fontSize: 30.rpx, + letterSpacing: 0, + ), + ), + Row( + children: [ + Text( + controller + .model + .peopleList + .isNotEmpty && + index < + controller + .model + .peopleList + .length && + controller.model + .peopleList[index] + [ + 'UTC'] != + null && + controller + .model + .peopleList[ + index] + [ + 'UTC']! + .isNotEmpty + ? controller.model + .peopleList[ + index]['UTC']! + : "请选择时区".tr, + style: TextStyle( + color: controller + .model + .peopleList + .isNotEmpty && + index < + controller + .model + .peopleList + .length && + controller.model + .peopleList[index] + [ + 'UTC'] != + null && + controller + .model + .peopleList[ + index] + [ + 'UTC']! + .isNotEmpty + ? themeController + .currentColor + .sc3 + : Color( + 0xFF9EA4B7), + fontSize: AppConstants() + .title_text_fontSize, + ), + ), + SizedBox(width: 16.rpx), + Container( + height: 30.rpx, + width: 30.rpx, + child: + SvgPicture.asset( + 'assets/img/icon/expand_more.svg', + color: Colors.white, + ), + ), + ], + ), + ], + ), + ), + ), + getLine(), Container( height: 90.rpx, margin: EdgeInsets.only( diff --git a/lib/pages/person/person_page.dart b/lib/pages/person/person_page.dart index 230b5ba..0c08ee8 100644 --- a/lib/pages/person/person_page.dart +++ b/lib/pages/person/person_page.dart @@ -60,6 +60,13 @@ class _EPageState extends State { personController.weight.value = ""; personController.height.value = ""; personController.dateTime = null; + personController.cityModel = null; + personController.timeZone.value = ""; + + personController.getTimeZoneByLocalTime().then((value) { + personController.timeZone.value = value; + personController.updateAll(); + }); cityDataFuture = cityController.loadAndSetCityData().then((success) { return cityController.cityList; @@ -172,7 +179,7 @@ class _EPageState extends State { children: [ Padding( padding: EdgeInsetsDirectional.fromSTEB( - 70.rpx, 141.rpx, 70.rpx, 0), + 70.rpx, 70.rpx, 70.rpx, 0), child: Container( width: double.infinity, height: 100.rpx, @@ -404,30 +411,77 @@ class _EPageState extends State { }, title: "选择生日".tr); }); }, - child: Center( - child: Text( - personController.dateTime != null - ? DateFormat("yyyy/MM/dd").format( - personController.dateTime!) - : '人员资料.生日输入提示'.tr, - textAlign: TextAlign.right, - style: TextStyle( - fontFamily: 'Readex Pro', - color: personController.dateTime != null - ? themeController.currentColor.sc3 - : themeController.currentColor.sc4, - fontSize: - AppConstants().normal_text_fontSize, - letterSpacing: 0, + child: Stack( + children: [ + // 文字居中 + Center( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text.rich( + TextSpan( + children: [ + TextSpan( + text: personController + .dateTime != + null + ? DateFormat( + "yyyy/MM/dd") + .format( + personController + .dateTime!) + : '人员资料.生日输入提示'.tr, + style: TextStyle( + fontFamily: 'Readex Pro', + color: personController + .dateTime != + null + ? themeController + .currentColor.sc3 + : themeController + .currentColor.sc4, + fontSize: AppConstants() + .normal_text_fontSize, + letterSpacing: 0, + ), + ), + TextSpan( + text: ' *', + style: TextStyle( + color: themeController + .currentColor.sc9, + fontSize: AppConstants() + .normal_text_fontSize, + fontWeight: + FontWeight.bold, + ), + ), + ], + ), + ), + ], + ), ), - ), + // 箭头居右 + Positioned( + right: 30.rpx, + top: 0, + bottom: 0, + child: Icon( + Icons.expand_more, + color: + themeController.currentColor.sc4, + size: 30.rpx, + ), + ), + ], ), ), ), ), Padding( padding: EdgeInsetsDirectional.fromSTEB( - 70.rpx, 50.rpx, 70.rpx, 0), + 70.rpx, 25.rpx, 70.rpx, 0), child: Container( height: 100.rpx, decoration: BoxDecoration( @@ -464,31 +518,76 @@ class _EPageState extends State { ); }); }, - child: Center( - child: Text( - personController.height.value != "" - ? personController.height.value + - "cm".tr - : '身高输入提示'.tr, - textAlign: TextAlign.right, - style: TextStyle( - fontFamily: 'Readex Pro', - color: personController.height.value != - "" - ? themeController.currentColor.sc3 - : themeController.currentColor.sc4, - fontSize: - AppConstants().normal_text_fontSize, - letterSpacing: 0, + child: Stack( + children: [ + // 文字居中 + Center( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text.rich( + TextSpan( + children: [ + TextSpan( + text: personController + .height.value != + "" + ? personController + .height.value + + "cm".tr + : '身高输入提示'.tr, + style: TextStyle( + fontFamily: 'Readex Pro', + color: personController + .height + .value != + "" + ? themeController + .currentColor.sc3 + : themeController + .currentColor.sc4, + fontSize: AppConstants() + .normal_text_fontSize, + letterSpacing: 0, + ), + ), + TextSpan( + text: ' *', + style: TextStyle( + color: themeController + .currentColor.sc9, + fontSize: AppConstants() + .normal_text_fontSize, + fontWeight: + FontWeight.bold, + ), + ), + ], + ), + ), + ], + ), ), - ), + // 箭头居右 + Positioned( + right: 30.rpx, + top: 0, + bottom: 0, + child: Icon( + Icons.expand_more, + color: + themeController.currentColor.sc4, + size: 30.rpx, + ), + ), + ], ), ), ), ), Padding( padding: EdgeInsetsDirectional.fromSTEB( - 70.rpx, 50.rpx, 70.rpx, 0), + 70.rpx, 25.rpx, 70.rpx, 0), child: Container( height: 100.rpx, decoration: BoxDecoration( @@ -512,38 +611,184 @@ class _EPageState extends State { personController.weight.value ?? "", onConfirm: (int selectedWeight) { personController.weight.value = - selectedWeight - .toString(); // ✅ 转成字符串 + selectedWeight.toString(); personController.updateAll(); }, ); }); }, - child: Center( - child: Text( - personController.weight.value != "" - ? personController.weight.value + - "kg".tr - : '人员资料.体重输入提示'.tr, - textAlign: TextAlign.right, - style: TextStyle( - fontFamily: 'Readex Pro', - color: personController.weight.value != - "" - ? themeController.currentColor.sc3 - : themeController.currentColor.sc4, - fontSize: - AppConstants().normal_text_fontSize, - letterSpacing: 0, + child: Stack( + children: [ + // 文字居中 + Center( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text.rich( + TextSpan( + children: [ + TextSpan( + text: personController + .weight.value != + "" + ? personController + .weight.value + + "kg".tr + : '人员资料.体重输入提示'.tr, + style: TextStyle( + fontFamily: 'Readex Pro', + color: personController + .weight + .value != + "" + ? themeController + .currentColor.sc3 + : themeController + .currentColor.sc4, + fontSize: AppConstants() + .normal_text_fontSize, + letterSpacing: 0, + ), + ), + TextSpan( + text: ' *', + style: TextStyle( + color: themeController + .currentColor.sc9, + fontSize: AppConstants() + .normal_text_fontSize, + fontWeight: + FontWeight.bold, + ), + ), + ], + ), + ), + ], + ), ), - ), + // 箭头居右 + Positioned( + right: 30.rpx, + top: 0, + bottom: 0, + child: Icon( + Icons.expand_more, + color: + themeController.currentColor.sc4, + size: 30.rpx, + ), + ), + ], ), ), ), ), Padding( padding: EdgeInsetsDirectional.fromSTEB( - 70.rpx, 50.rpx, 70.rpx, 0), + 70.rpx, 25.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: () { + final timeZone = + personController.timeZone.value; + final initialTimeZone = timeZone; + FocusScope.of(context) + .requestFocus(FocusNode()); + Future.delayed( + const Duration(milliseconds: 250), () { + showTimeZonePickerDialog( + context, + title: "选择时区".tr, + initialTimeZone: initialTimeZone, + onConfirm: (String selectedtimezone) { + personController.timeZone.value = + selectedtimezone.toString(); + personController.updateAll(); + print("时区: $selectedtimezone"); + }, + ); + }); + }, child: Obx(() { + return Stack( + children: [ + // 文字居中 + Center( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text.rich( + TextSpan( + children: [ + TextSpan( + text: personController + .timeZone + .value != + "" + ? personController + .timeZone.value + : '选择时区'.tr, + style: TextStyle( + fontFamily: 'Readex Pro', + color: personController + .timeZone + .value != + "" + ? themeController + .currentColor.sc3 + : themeController + .currentColor.sc4, + fontSize: AppConstants() + .normal_text_fontSize, + letterSpacing: 0, + ), + ), + TextSpan( + text: ' *', + style: TextStyle( + color: themeController + .currentColor.sc9, + fontSize: AppConstants() + .normal_text_fontSize, + fontWeight: + FontWeight.bold, + ), + ), + ], + ), + ), + ], + ), + ), + // 箭头居右 + Positioned( + right: 30.rpx, + top: 0, + bottom: 0, + child: Icon( + Icons.expand_more, + color: + themeController.currentColor.sc4, + size: 30.rpx, + ), + ), + ], + ); + })), + ), + ), + + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 70.rpx, 25.rpx, 70.rpx, 0), child: Container( height: 100.rpx, decoration: BoxDecoration( @@ -577,32 +822,77 @@ class _EPageState extends State { title: "选择城市".tr, cityDataFuture: cityDataFuture, // 传入预加载的数据 + colors: CitySelectionColors( + // pickerBackgroundColor: + // stringToColor("#003058"), + // confirmTextColor: + // stringToColor("#84F5FF"), + selectedCityColor: + themeController.currentColor.sc2, + selectedTextColor: Colors.white, + ), ); }); }, - child: Center( - child: Text( - _getDetailedCityDisplayText( - personController.cityModel), - textAlign: TextAlign.right, - style: TextStyle( - fontFamily: 'Readex Pro', - color: personController.cityModel != - null - ? themeController.currentColor.sc3 - : themeController.currentColor.sc4, - fontSize: - AppConstants().normal_text_fontSize, - letterSpacing: 0, + child: Stack( + children: [ + // 文字居中 + Center( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text( + _getDetailedCityDisplayText( + personController.cityModel), + textAlign: TextAlign.center, + style: TextStyle( + fontFamily: 'Readex Pro', + color: + personController + .cityModel != + null + ? themeController + .currentColor.sc3 + : themeController + .currentColor.sc4, + fontSize: AppConstants() + .normal_text_fontSize, + letterSpacing: 0, + ), + ), + // Text( + // ' *', + // style: TextStyle( + // color: themeController + // .currentColor.sc9, + // fontSize: AppConstants() + // .normal_text_fontSize, + // fontWeight: FontWeight.bold, + // ), + // ), + ], + ), ), - ), + // 箭头居右 + Positioned( + right: 30.rpx, + top: 0, + bottom: 0, + child: Icon( + Icons.expand_more, + color: + themeController.currentColor.sc4, + size: 30.rpx, + ), + ), + ], ), ), ), ), Padding( padding: EdgeInsetsDirectional.fromSTEB( - 0, 117.rpx, 0, 0), + 0, 60.rpx, 0, 0), child: Container( width: double.infinity, decoration: BoxDecoration(), diff --git a/lib/pages/person/select_city.dart b/lib/pages/person/select_city.dart index ec96512..a15f33c 100644 --- a/lib/pages/person/select_city.dart +++ b/lib/pages/person/select_city.dart @@ -3,9 +3,11 @@ import 'dart:convert'; import 'package:ef/ef.dart'; import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:vbvs_app/common/color/appConstants.dart'; import 'package:vbvs_app/common/pojo/city.dart'; import 'package:vbvs_app/common/util/FitTool.dart'; import 'package:vbvs_app/common/util/ListSearchWidget.dart'; +import 'package:vbvs_app/common/util/MyUtils.dart'; import 'package:vbvs_app/component/tool/ClickableContainer.dart'; import 'package:vbvs_app/controller/person/person_controller.dart'; import 'package:vbvs_app/controller/theme_controller/ThemeController.dart'; @@ -666,7 +668,7 @@ Widget _buildCityPickerContent( Get.find(); cityModelController.tmp; ef.log("${cityModelController.tmp.value}"); - return getOnePickers( + return getOnePickersCity( context, countries, countryIndex, @@ -684,7 +686,7 @@ Widget _buildCityPickerContent( Get.find(); cityModelController.tmp; ef.log("${cityModelController.tmp.value}"); - return getOnePickers( + return getOnePickersCity( context, provinces, provinceIndex, @@ -702,7 +704,7 @@ Widget _buildCityPickerContent( Get.find(); cityModelController.tmp; ef.log("${cityModelController.tmp.value}"); - return getOnePickers( + return getOnePickersCity( context, cities, cityIndex, @@ -813,3 +815,149 @@ Widget _buildErrorBottomSheet( ), ); } + +Future showTimeMHTZonePickerDialog( + BuildContext context, { + required String initialTimeZone, // 初始时区字符串,如 "UTC+8" + required Function(String selectedTimeZone) onConfirm, + String title = "选择时区", +}) async { + // 使用 AppConstants.integerTimeZones 作为数据源 + List timeZones = AppConstants.integerTimeZones; + int selectedIndex = timeZones.indexOf(initialTimeZone); + + // 如果没有找到,使用默认的UTC+8(北京时间) + if (selectedIndex == -1) { + selectedIndex = timeZones.indexOf("UTC+8"); + if (selectedIndex == -1) { + selectedIndex = 0; // 如果连UTC+8都没有,使用第一个 + } + } + + final RxInt tempIndex = RxInt(selectedIndex); + ThemeController themeController = Get.find(); + + await showDialog( + context: context, + barrierDismissible: true, + builder: (BuildContext context) { + return Stack( + children: [ + Positioned( + bottom: 0, + left: 0, + right: 0, + child: Material( + color: Colors.transparent, + child: Dialog( + backgroundColor: stringToColor("#003058"), + insetPadding: EdgeInsets.zero, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(0), + ), + child: Container( + padding: EdgeInsets.fromLTRB(0.rpx, 0.rpx, 0.rpx, 90.rpx), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + padding: + EdgeInsets.fromLTRB(30.rpx, 0.rpx, 30.rpx, 0.rpx), + color: themeController.currentColor.sc5, + height: 80.rpx, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ClickableContainer( + backgroundColor: Colors.transparent, + highlightColor: Colors.transparent, + padding: EdgeInsets.only(top: 0), + onTap: () { + Get.back(); + }, + child: Container( + alignment: Alignment.center, + width: 110.rpx, + height: 60.rpx, + child: Text( + "取消".tr, + style: TextStyle( + fontSize: 30.rpx, + color: Colors.white, + ), + ), + ), + ), + Text( + title, + style: TextStyle( + fontFamily: 'Readex Pro', + color: themeController.currentColor.sc3, + fontSize: 30.rpx, + ), + ), + ClickableContainer( + backgroundColor: Colors.transparent, + highlightColor: Colors.transparent, + padding: EdgeInsets.only(top: 0), + onTap: () { + onConfirm(timeZones[tempIndex.value]); + Get.back(); + }, + child: Container( + alignment: Alignment.center, + width: 110.rpx, + height: 60.rpx, + child: Text( + "确定".tr, + style: TextStyle( + fontSize: 30.rpx, + color: stringToColor("#84F5FF"), + ), + ), + ), + ), + ], + ), + ), + SizedBox(height: 20.rpx), + Stack( + children: [ + Positioned.fill( + child: IgnorePointer( + child: Center( + child: Container( + height: 90.rpx, + margin: + EdgeInsets.symmetric(horizontal: 95.rpx), + decoration: BoxDecoration( + color: stringToColor("#84F5FF"), + borderRadius: BorderRadius.circular(16.rpx), + ), + ), + ), + ), + ), + SizedBox( + height: 240.rpx, + child: getOnePickers( + context, + timeZones, // 传入字符串列表 + tempIndex, + unit: "", // 时区不需要单位 + selectedColor: stringToColor("#011D33"), + ), + ), + ], + ), + ], + ), + ), + ), + ), + ), + ], + ); + }, + ); +} diff --git a/lib/pages/person/select_time.dart b/lib/pages/person/select_time.dart index 71938a9..73440d9 100644 --- a/lib/pages/person/select_time.dart +++ b/lib/pages/person/select_time.dart @@ -1306,3 +1306,150 @@ Future showCountryCodePickerDialog( }, ); } + + +Future showTimeZonePickerDialog( + BuildContext context, { + required String initialTimeZone, // 初始时区字符串,如 "UTC+8" + required Function(String selectedTimeZone) onConfirm, + String title = "选择时区", +}) async { + // 使用 AppConstants.integerTimeZones 作为数据源 + List timeZones = AppConstants.integerTimeZones; + int selectedIndex = timeZones.indexOf(initialTimeZone); + + // 如果没有找到,使用默认的UTC+8(北京时间) + if (selectedIndex == -1) { + selectedIndex = timeZones.indexOf("UTC+8"); + if (selectedIndex == -1) { + selectedIndex = 0; // 如果连UTC+8都没有,使用第一个 + } + } + + final RxInt tempIndex = RxInt(selectedIndex); + ThemeController themeController = Get.find(); + + await showDialog( + context: context, + barrierDismissible: true, + builder: (BuildContext context) { + return Stack( + children: [ + Positioned( + bottom: 0, + left: 0, + right: 0, + child: Material( + color: Colors.transparent, + child: Dialog( + backgroundColor: themeController.currentColor.sc17, + insetPadding: EdgeInsets.zero, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(0), + ), + child: Container( + padding: EdgeInsets.fromLTRB(0.rpx, 0.rpx, 0.rpx, 90.rpx), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + padding: + EdgeInsets.fromLTRB(30.rpx, 0.rpx, 30.rpx, 0.rpx), + color: themeController.currentColor.sc5, + height: 80.rpx, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ClickableContainer( + backgroundColor: Colors.transparent, + highlightColor: Colors.transparent, + padding: EdgeInsets.only(top: 0), + onTap: () { + Get.back(); + }, + child: Container( + alignment: Alignment.center, + width: 110.rpx, + height: 60.rpx, + child: Text( + "取消".tr, + style: TextStyle( + fontSize: 30.rpx, + color: Colors.white, + ), + ), + ), + ), + Text( + title, + style: TextStyle( + fontFamily: 'Readex Pro', + color: themeController.currentColor.sc3, + fontSize: 30.rpx, + ), + ), + ClickableContainer( + backgroundColor: Colors.transparent, + highlightColor: Colors.transparent, + padding: EdgeInsets.only(top: 0), + onTap: () { + onConfirm(timeZones[tempIndex.value]); + Get.back(); + }, + child: Container( + alignment: Alignment.center, + width: 110.rpx, + height: 60.rpx, + child: Text( + "确定".tr, + style: TextStyle( + fontSize: 30.rpx, + color: themeController.currentColor.sc2, + ), + ), + ), + ), + ], + ), + ), + SizedBox(height: 20.rpx), + Stack( + children: [ + Positioned.fill( + child: IgnorePointer( + child: Center( + child: Container( + height: 90.rpx, + margin: + EdgeInsets.symmetric(horizontal: 95.rpx), + decoration: BoxDecoration( + color: themeController.currentColor.sc2, + borderRadius: BorderRadius.circular(16.rpx), + ), + ), + ), + ), + ), + SizedBox( + height: 240.rpx, + child: getOnePickers( + context, + timeZones, // 传入字符串列表 + tempIndex, + unit: "", // 时区不需要单位 + ), + ), + ], + ), + ], + ), + ), + ), + ), + ), + ], + ); + }, + ); +} + diff --git a/lib/pages/person/update_person_page.dart b/lib/pages/person/update_person_page.dart index cd55454..b9032b8 100644 --- a/lib/pages/person/update_person_page.dart +++ b/lib/pages/person/update_person_page.dart @@ -173,7 +173,7 @@ class _UpdatePageState extends State { children: [ Padding( padding: EdgeInsetsDirectional.fromSTEB( - 70.rpx, 141.rpx, 70.rpx, 0), + 70.rpx, 70.rpx, 70.rpx, 0), child: Container( width: double.infinity, height: 100.rpx, @@ -204,7 +204,12 @@ class _UpdatePageState extends State { letterSpacing: 0.0, color: themeController.currentColor.sc3, ), - hintText: '人员资料.名字输入提示'.tr, + hintText: (personController.name.value + as String?) + ?.isNotEmpty == + true + ? personController.name.value + : '体征检测设备'.tr, hintStyle: TextStyle( fontFamily: 'Inter', fontSize: 26.rpx, @@ -432,30 +437,77 @@ class _UpdatePageState extends State { ); }); }, - child: Center( - child: Text( - personController.dateTime != null - ? DateFormat("yyyy/MM/dd").format( - personController.dateTime!) - : '人员资料.生日输入提示'.tr, - textAlign: TextAlign.right, - style: TextStyle( - fontFamily: 'Readex Pro', - color: personController.dateTime != null - ? themeController.currentColor.sc3 - : themeController.currentColor.sc4, - fontSize: - AppConstants().normal_text_fontSize, - letterSpacing: 0, + child: Stack( + children: [ + // 文字居中 + Center( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text.rich( + TextSpan( + children: [ + TextSpan( + text: personController + .dateTime != + null + ? DateFormat( + "yyyy/MM/dd") + .format( + personController + .dateTime!) + : '人员资料.生日输入提示'.tr, + style: TextStyle( + fontFamily: 'Readex Pro', + color: personController + .dateTime != + null + ? themeController + .currentColor.sc3 + : themeController + .currentColor.sc4, + fontSize: AppConstants() + .normal_text_fontSize, + letterSpacing: 0, + ), + ), + TextSpan( + text: ' *', + style: TextStyle( + color: themeController + .currentColor.sc9, + fontSize: AppConstants() + .normal_text_fontSize, + fontWeight: + FontWeight.bold, + ), + ), + ], + ), + ), + ], + ), ), - ), + // 箭头居右 + Positioned( + right: 30.rpx, + top: 0, + bottom: 0, + child: Icon( + Icons.expand_more, + color: + themeController.currentColor.sc4, + size: 30.rpx, + ), + ), + ], ), ), ), ), Padding( padding: EdgeInsetsDirectional.fromSTEB( - 70.rpx, 50.rpx, 70.rpx, 0), + 70.rpx, 25.rpx, 70.rpx, 0), child: Container( height: 100.rpx, decoration: BoxDecoration( @@ -500,31 +552,76 @@ class _UpdatePageState extends State { ); }); }, - child: Center( - child: Text( - personController.height.value != "" - ? personController.height.value + - "cm".tr - : '身高输入提示'.tr, - textAlign: TextAlign.right, - style: TextStyle( - fontFamily: 'Readex Pro', - color: personController.height.value != - "" - ? themeController.currentColor.sc3 - : themeController.currentColor.sc4, - fontSize: - AppConstants().normal_text_fontSize, - letterSpacing: 0, + child: Stack( + children: [ + // 文字居中 + Center( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text.rich( + TextSpan( + children: [ + TextSpan( + text: personController + .height.value != + "" + ? personController + .height.value + + "cm".tr + : '身高输入提示'.tr, + style: TextStyle( + fontFamily: 'Readex Pro', + color: personController + .height + .value != + "" + ? themeController + .currentColor.sc3 + : themeController + .currentColor.sc4, + fontSize: AppConstants() + .normal_text_fontSize, + letterSpacing: 0, + ), + ), + TextSpan( + text: ' *', + style: TextStyle( + color: themeController + .currentColor.sc9, + fontSize: AppConstants() + .normal_text_fontSize, + fontWeight: + FontWeight.bold, + ), + ), + ], + ), + ), + ], + ), ), - ), + // 箭头居右 + Positioned( + right: 30.rpx, + top: 0, + bottom: 0, + child: Icon( + Icons.expand_more, + color: + themeController.currentColor.sc4, + size: 30.rpx, + ), + ), + ], ), ), ), ), Padding( padding: EdgeInsetsDirectional.fromSTEB( - 70.rpx, 50.rpx, 70.rpx, 0), + 70.rpx, 25.rpx, 70.rpx, 0), child: Container( height: 100.rpx, decoration: BoxDecoration( @@ -555,38 +652,193 @@ class _UpdatePageState extends State { personController.weight.value ?? "", onConfirm: (int selectedWeight) { personController.weight.value = - selectedWeight - .toString(); // ✅ 转成字符串 + selectedWeight.toString(); personController.updateAll(); }, ); }); }, - child: Center( - child: Text( - personController.weight.value != "" - ? personController.weight.value + - "kg".tr - : '人员资料.体重输入提示'.tr, - textAlign: TextAlign.right, - style: TextStyle( - fontFamily: 'Readex Pro', - color: personController.weight.value != - "" - ? themeController.currentColor.sc3 - : themeController.currentColor.sc4, - fontSize: - AppConstants().normal_text_fontSize, - letterSpacing: 0, + child: Stack( + children: [ + // 文字居中 + Center( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text.rich( + TextSpan( + children: [ + TextSpan( + text: personController + .weight.value != + "" + ? personController + .weight.value + + "kg".tr + : '人员资料.体重输入提示'.tr, + style: TextStyle( + fontFamily: 'Readex Pro', + color: personController + .weight + .value != + "" + ? themeController + .currentColor.sc3 + : themeController + .currentColor.sc4, + fontSize: AppConstants() + .normal_text_fontSize, + letterSpacing: 0, + ), + ), + TextSpan( + text: ' *', + style: TextStyle( + color: themeController + .currentColor.sc9, + fontSize: AppConstants() + .normal_text_fontSize, + fontWeight: + FontWeight.bold, + ), + ), + ], + ), + ), + ], + ), ), - ), + // 箭头居右 + Positioned( + right: 30.rpx, + top: 0, + bottom: 0, + child: Icon( + Icons.expand_more, + color: + themeController.currentColor.sc4, + size: 30.rpx, + ), + ), + ], ), ), ), ), Padding( padding: EdgeInsetsDirectional.fromSTEB( - 70.rpx, 50.rpx, 70.rpx, 0), + 70.rpx, 25.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: () { + if (widget.status == BindType.share.code) { + TopSlideNotification.show(context, + text: "被分享用户只能修改用户名称", + textColor: + themeController.currentColor.sc9); + return; + } + final timeZone = + personController.timeZone.value; + final initialTimeZone = timeZone; + + FocusScope.of(context) + .requestFocus(FocusNode()); + Future.delayed( + const Duration(milliseconds: 250), () { + showTimeZonePickerDialog( + context, + title: "选择时区".tr, + initialTimeZone: initialTimeZone, + onConfirm: (String selectedtimezone) { + personController.timeZone.value = + selectedtimezone.toString(); + personController.updateAll(); + print("时区: $selectedtimezone"); + }, + ); + }); + }, + child: Stack( + children: [ + // 文字居中 + Center( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Text.rich( + TextSpan( + children: [ + TextSpan( + text: personController + .timeZone + .value != + "" + ? personController + .timeZone.value + : '选择时区'.tr, + style: TextStyle( + fontFamily: 'Readex Pro', + color: personController + .timeZone + .value != + "" + ? themeController + .currentColor.sc3 + : themeController + .currentColor.sc4, + fontSize: AppConstants() + .normal_text_fontSize, + letterSpacing: 0, + ), + ), + TextSpan( + text: ' *', + style: TextStyle( + color: themeController + .currentColor.sc9, + fontSize: AppConstants() + .normal_text_fontSize, + fontWeight: + FontWeight.bold, + ), + ), + ], + ), + ), + ], + ), + ), + // 箭头居右 + Positioned( + right: 30.rpx, + top: 0, + bottom: 0, + child: Icon( + Icons.expand_more, + color: + themeController.currentColor.sc4, + size: 30.rpx, + ), + ), + ], + ), + ), + ), + ), + + Padding( + padding: EdgeInsetsDirectional.fromSTEB( + 70.rpx, 25.rpx, 70.rpx, 0), child: Container( height: 100.rpx, decoration: BoxDecoration( @@ -627,33 +879,80 @@ class _UpdatePageState extends State { title: "选择城市".tr, cityDataFuture: cityDataFuture, // 传入预加载的数据 + colors: CitySelectionColors( + // pickerBackgroundColor: + // stringToColor("#003058"), + // confirmTextColor: + // stringToColor("#84F5FF"), + selectedCityColor: + themeController.currentColor.sc2, + selectedTextColor: Colors.white, + ), ); }); }, - child: Center( - child: Text( - MyUtils.getDetailedCityDisplayText( - personController.cityModel), - textAlign: TextAlign.right, - style: TextStyle( - fontFamily: 'Readex Pro', - color: personController.cityModel != - null - ? themeController.currentColor.sc3 - : themeController.currentColor.sc4, - fontSize: - AppConstants().normal_text_fontSize, - letterSpacing: 0, + child: Stack( + children: [ + // 文字居中 + Center( + child: Row( + mainAxisAlignment: + MainAxisAlignment.center, + children: [ + Text( + MyUtils + .getDetailedCityDisplayText( + personController + .cityModel), + textAlign: TextAlign.center, + style: TextStyle( + fontFamily: 'Readex Pro', + color: + personController + .cityModel != + null + ? themeController + .currentColor.sc3 + : themeController + .currentColor.sc4, + fontSize: AppConstants() + .normal_text_fontSize, + letterSpacing: 0, + ), + ), + // Text( + // ' *', + // style: TextStyle( + // color: themeController + // .currentColor.sc9, + // fontSize: AppConstants() + // .normal_text_fontSize, + // fontWeight: FontWeight.bold, + // ), + // ), + ], + ), ), - ), + // 箭头居右 + Positioned( + right: 30.rpx, + top: 0, + bottom: 0, + child: Icon( + Icons.expand_more, + color: + themeController.currentColor.sc4, + size: 30.rpx, + ), + ), + ], ), ), ), ), - Padding( padding: EdgeInsetsDirectional.fromSTEB( - 0, 117.rpx, 0, 0), + 0, 60.rpx, 0, 0), child: Container( width: double.infinity, decoration: BoxDecoration(), @@ -858,5 +1157,4 @@ class _UpdatePageState extends State { onFailure: (res) {}, ); } - } diff --git a/lib/pages/sleep_report/chart/SnoreChart.dart b/lib/pages/sleep_report/chart/SnoreChart.dart index d51cea3..0df02d6 100644 --- a/lib/pages/sleep_report/chart/SnoreChart.dart +++ b/lib/pages/sleep_report/chart/SnoreChart.dart @@ -127,18 +127,66 @@ class BarChartPainter extends CustomPainter { final textPainter = TextPainter(textDirection: ui.TextDirection.ltr); final stepValue = maxYValue / yStepCount; + // Y轴刻度 + // for (int i = 0; i <= yStepCount; i++) { + // final value = stepValue * i; + // final y = topPadding + chartHeight - (value / maxYValue) * chartHeight; + + // final dashPaint = Paint() + // ..color = Colors.grey.withOpacity(0.4) + // ..strokeWidth = 1.rpx; + + // drawDashedLine( + // canvas, Offset(leftPadding, y), Offset(size.width, y), dashPaint); + + // textPainter.text = TextSpan( + // text: value.toStringAsFixed(0), + // style: TextStyle( + // fontSize: 18.rpx, + // color: themeController.currentColor.sc4, + // ), + // ); + // textPainter.layout(); + // textPainter.paint( + // canvas, + // Offset(leftPadding - textPainter.width - 4, y - textPainter.height / 2), + // ); + // } // Y轴刻度 for (int i = 0; i <= yStepCount; i++) { final value = stepValue * i; final y = topPadding + chartHeight - (value / maxYValue) * chartHeight; - final dashPaint = Paint() - ..color = Colors.grey.withOpacity(0.4) - ..strokeWidth = 1.rpx; + // 判断是否是基线(i == 0) + final bool isBaseline = i == 0; - drawDashedLine( - canvas, Offset(leftPadding, y), Offset(size.width, y), dashPaint); + if (isBaseline) { + // 基线画实线 + final baselinePaint = Paint() + ..color = Colors.grey.withOpacity(0.6) + ..strokeWidth = 1.rpx + ..style = PaintingStyle.stroke; + canvas.drawLine( + Offset(leftPadding, y), + Offset(size.width, y), + baselinePaint, + ); + } else { + // 其他刻度画虚线 + final dashPaint = Paint() + ..color = Colors.grey.withOpacity(0.4) + ..strokeWidth = 1.rpx; + + drawDashedLine( + canvas, + Offset(leftPadding, y), + Offset(size.width, y), + dashPaint, + ); + } + + // 绘制刻度文字 textPainter.text = TextSpan( text: value.toStringAsFixed(0), style: TextStyle( @@ -163,14 +211,14 @@ class BarChartPainter extends CustomPainter { final startHour = startDate.hour; // 绘制X轴主线(实线) - final xAxisPaint = Paint() - ..color = Colors.grey.withOpacity(0.4) - ..strokeWidth = 1.rpx; - canvas.drawLine( - Offset(leftPadding, xAxisY), - Offset(size.width, xAxisY), - xAxisPaint, - ); + // final xAxisPaint = Paint() + // ..color = Colors.grey.withOpacity(0.4) + // ..strokeWidth = 1.rpx; + // canvas.drawLine( + // Offset(leftPadding, xAxisY), + // Offset(size.width, xAxisY), + // xAxisPaint, + // ); // 绘制左右两侧时间标签(HH:mm格式) final leftLabel = DateFormat('HH:mm').format(startDate); diff --git a/lib/pages/sleep_report/chart/SnoreWaveform.dart b/lib/pages/sleep_report/chart/SnoreWaveform.dart index b491e1e..8993fdd 100644 --- a/lib/pages/sleep_report/chart/SnoreWaveform.dart +++ b/lib/pages/sleep_report/chart/SnoreWaveform.dart @@ -23,11 +23,6 @@ // @override // Widget build(BuildContext context) { -// // barData.add({ -// // "type": 6, -// // "et": 1765291778963, -// // "st": 1765289341000, -// // }); // return Column( // crossAxisAlignment: CrossAxisAlignment.start, // children: [ @@ -167,13 +162,17 @@ // Widget build(BuildContext context) { // return SizedBox( // height: 150, -// child: CustomPaint( -// size: Size(double.infinity, 150), -// painter: SnoreWaveformPainter( -// snoreValues: snoreValues, -// startTime: startTime, -// endTime: endTime, -// ), +// child: LayoutBuilder( +// builder: (context, constraints) { +// return CustomPaint( +// size: Size(constraints.maxWidth, constraints.maxHeight), +// painter: SnoreWaveformPainter( +// snoreValues: snoreValues, +// startTime: startTime, +// endTime: endTime, +// ), +// ); +// }, // ), // ); // } @@ -194,51 +193,79 @@ // void paint(Canvas canvas, Size size) { // final double width = size.width; // final double height = size.height; -// final double centerY = height / 2; + +// if (width <= 0 || height <= 0) return; + // final double totalDuration = (endTime - startTime).toDouble(); +// if (totalDuration <= 0) return; + // final double pixelPerMs = width / totalDuration; -// final Paint wavePaint = Paint() -// ..color = stringToColor("#8E7DEF").withOpacity(0.8) -// ..strokeWidth = 1.5 -// ..style = PaintingStyle.stroke; +// // 过滤在时间范围内的有效事件 +// final validEvents = snoreValues.where((e) { +// final int st = e['st']; +// final int et = e['et']; +// // 事件与时间范围有重叠 +// return !(et <= startTime || st >= endTime); +// }).toList(); -// final Path upperPath = Path(); -// final Path lowerPath = Path(); +// // 计算中心线位置 +// final double centerY = height / 2; -// double maxValue = snoreValues.fold(0, (prev, e) { -// final value = e["value"]?.toDouble() ?? 0; -// return value > prev ? value : prev; -// }); +// // 统一使用一个颜色(打鼾颜色) +// final Color snoreColor = stringToColor("#8E7DEF").withOpacity(0.8); +// final Paint barPaint = Paint() +// ..color = snoreColor +// ..style = PaintingStyle.fill; -// final double maxWaveHeight = height * 1; -// final double scaleY = maxValue > 0 ? (maxWaveHeight / maxValue) : 1; +// final Paint borderPaint = Paint() +// ..color = snoreColor.withOpacity(0.9) +// ..style = PaintingStyle.stroke +// ..strokeWidth = 0.5; -// for (int i = 0; i < snoreValues.length; i++) { -// final timestamp = snoreValues[i]["st"]; -// final value = snoreValues[i]["value"]?.toDouble() ?? 0; +// // 固定高度(上下对称) +// final double fixedBarHeight = height * 0.3; // 固定为画布高度的30% -// final x = (timestamp - startTime) * pixelPerMs; -// final y = centerY - value * scaleY; -// final yMirror = centerY + value * scaleY; +// // 绘制每个打鼾事件(上下对称的柱状图) +// for (final event in validEvents) { +// final int st = event['st']; +// final int et = event['et']; -// if (i == 0) { -// upperPath.moveTo(x, y); -// lowerPath.moveTo(x, yMirror); -// } else { -// upperPath.lineTo(x, y); -// lowerPath.lineTo(x, yMirror); -// } +// // 计算绘制位置(裁剪到可视范围内) +// final double startX = (st - startTime) * pixelPerMs; +// final double endX = (et - startTime) * pixelPerMs; + +// // 确保在画布范围内 +// if (endX < 0 || startX > width) continue; + +// final double drawStartX = startX.clamp(0, width); +// final double drawEndX = endX.clamp(0, width); +// final double drawWidth = drawEndX - drawStartX; + +// if (drawWidth <= 0) continue; + +// // 绘制上方的柱状图 +// final double topBarTop = centerY - fixedBarHeight; +// final Rect topRect = +// Rect.fromLTWH(drawStartX, topBarTop, drawWidth, fixedBarHeight); +// canvas.drawRect(topRect, barPaint); +// canvas.drawRect(topRect, borderPaint); + +// // 绘制下方的柱状图(对称) +// final double bottomBarTop = centerY; +// final Rect bottomRect = +// Rect.fromLTWH(drawStartX, bottomBarTop, drawWidth, fixedBarHeight); +// canvas.drawRect(bottomRect, barPaint); +// canvas.drawRect(bottomRect, borderPaint); // } -// canvas.drawPath(upperPath, wavePaint); -// canvas.drawPath(lowerPath, wavePaint); - +// // 绘制中心线 // final Paint axisPaint = Paint() -// ..color = Colors.grey.withOpacity(0.6) +// ..color = Colors.grey.withOpacity(0.3) // ..strokeWidth = 0.5; // canvas.drawLine(Offset(0, centerY), Offset(width, centerY), axisPaint); +// // 绘制时间轴标签 // final textPainter = TextPainter( // textAlign: TextAlign.center, // textDirection: ui.TextDirection.ltr, @@ -247,11 +274,12 @@ // final int hourMs = 60 * 60 * 1000; // final int totalHours = (endTime - startTime) ~/ hourMs; -// // 1. 始终显示开始时间 -// double x = 0; -// DateTime startDt = DateTime.fromMillisecondsSinceEpoch(startTime); -// String label = DateFormat('HH:mm').format(startDt); +// // 创建开始和结束时间的DateTime对象 +// final DateTime startDt = DateTime.fromMillisecondsSinceEpoch(startTime); +// final DateTime endDt = DateTime.fromMillisecondsSinceEpoch(endTime); +// // 1. 始终显示开始时间 +// String label = DateFormat('HH:mm').format(startDt); // textPainter.text = TextSpan( // text: label, // style: TextStyle(fontSize: 10, color: Colors.grey), @@ -259,70 +287,105 @@ // textPainter.layout(); // textPainter.paint( // canvas, -// Offset(x - textPainter.width / 2, height + 2), +// Offset(0 - textPainter.width / 2, height + 2), // ); // // 2. 决定显示策略 // if (totalHours <= 8) { -// // 小时间段:显示所有整点小时 -// for (int t = startTime + hourMs; t < endTime; t += hourMs) { -// x = (t - startTime) * pixelPerMs; -// DateTime dt = DateTime.fromMillisecondsSinceEpoch(t); +// // 小时间段:显示所有整点小时(基于实际时间) +// DateTime currentHour = DateTime( +// startDt.year, +// startDt.month, +// startDt.day, +// startDt.hour + 1, // 从下一个整点开始 +// 0, +// 0, +// 0, +// 0); -// // 判断是否接近边界(30分钟内不显示) -// if (t - startTime < 30 * 60 * 1000 || endTime - t < 30 * 60 * 1000) { -// continue; +// // 如果开始时间本身就是整点,需要调整 +// if (startDt.minute == 0 && +// startDt.second == 0 && +// startDt.millisecond == 0) { +// currentHour = startDt; +// } + +// while (currentHour.millisecondsSinceEpoch < endTime) { +// int timeMs = currentHour.millisecondsSinceEpoch; + +// // 确保时间在范围内 +// if (timeMs > startTime && timeMs < endTime) { +// double x = (timeMs - startTime) * pixelPerMs; + +// // 跳过太接近边界的时间点(30分钟内不显示) +// if (timeMs - startTime < 10 * 60 * 1000 || +// endTime - timeMs < 10 * 60 * 1000) { +// currentHour = currentHour.add(Duration(hours: 1)); +// continue; +// } + +// label = "${currentHour.hour}"; + +// textPainter.text = TextSpan( +// text: label, +// style: TextStyle(fontSize: 10, color: Colors.grey), +// ); +// textPainter.layout(); +// textPainter.paint( +// canvas, +// Offset(x - textPainter.width / 2, height + 2), +// ); // } -// label = dt.hour == 0 ? "0" : "${dt.hour}"; - -// textPainter.text = TextSpan( -// text: label, -// style: TextStyle(fontSize: 10, color: Colors.grey), -// ); -// textPainter.layout(); -// textPainter.paint( -// canvas, -// Offset(x - textPainter.width / 2, height + 2), -// ); +// currentHour = currentHour.add(Duration(hours: 1)); // } // } else { // // 长时间段:使用自适应间隔 // int labelInterval = (totalHours / 6).ceil(); -// // 计算第一个标签位置(对齐整点) -// int firstLabelMs = -// ((startTime ~/ (labelInterval * hourMs))) * labelInterval * hourMs; -// if (firstLabelMs <= startTime) { -// firstLabelMs += labelInterval * hourMs; +// // 计算第一个整点标签(对齐整点小时) +// DateTime firstLabelHour = DateTime( +// startDt.year, +// startDt.month, +// startDt.day, +// startDt.hour + (labelInterval - (startDt.hour % labelInterval)), +// 0, +// 0, +// 0, +// 0); + +// // 如果第一个标签在开始时间之前,调整到下一个间隔 +// if (firstLabelHour.millisecondsSinceEpoch <= startTime) { +// firstLabelHour = firstLabelHour.add(Duration(hours: labelInterval)); // } // // 绘制中间标签 -// for (int t = firstLabelMs; t < endTime; t += labelInterval * hourMs) { +// DateTime currentHour = firstLabelHour; +// while (currentHour.millisecondsSinceEpoch < endTime) { +// int timeMs = currentHour.millisecondsSinceEpoch; + // // 跳过太接近边界的时间点(1小时内不显示) -// if (t - startTime < hourMs || endTime - t < hourMs) continue; +// if (timeMs - startTime >= hourMs && endTime - timeMs >= hourMs) { +// double x = (timeMs - startTime) * pixelPerMs; +// label = "${currentHour.hour}"; -// x = (t - startTime) * pixelPerMs; -// DateTime dt = DateTime.fromMillisecondsSinceEpoch(t); -// label = dt.hour == 0 ? "0" : "${dt.hour}"; +// textPainter.text = TextSpan( +// text: label, +// style: TextStyle(fontSize: 10, color: Colors.grey), +// ); +// textPainter.layout(); +// textPainter.paint( +// canvas, +// Offset(x - textPainter.width / 2, height + 2), +// ); +// } -// textPainter.text = TextSpan( -// text: label, -// style: TextStyle(fontSize: 10, color: Colors.grey), -// ); -// textPainter.layout(); -// textPainter.paint( -// canvas, -// Offset(x - textPainter.width / 2, height + 2), -// ); +// currentHour = currentHour.add(Duration(hours: labelInterval)); // } // } // // 3. 始终显示结束时间 -// x = (endTime - startTime) * pixelPerMs; -// DateTime endDt = DateTime.fromMillisecondsSinceEpoch(endTime); // label = DateFormat('HH:mm').format(endDt); - // textPainter.text = TextSpan( // text: label, // style: TextStyle(fontSize: 10, color: Colors.grey), @@ -330,22 +393,28 @@ // textPainter.layout(); // textPainter.paint( // canvas, -// Offset(x - textPainter.width / 2, height + 2), +// Offset(width - textPainter.width / 2, height + 2), // ); // } // @override -// bool shouldRepaint(covariant CustomPainter oldDelegate) => true; +// bool shouldRepaint(covariant SnoreWaveformPainter oldDelegate) { +// return oldDelegate.snoreValues != snoreValues || +// oldDelegate.startTime != startTime || +// oldDelegate.endTime != endTime; +// } // } +import 'dart:math' as math; import 'dart:ui' as ui; +import 'package:ef/ef.dart'; import 'package:flutter/material.dart'; import 'package:flutterflow_ui/flutterflow_ui.dart'; import 'package:vbvs_app/common/util/FitTool.dart'; import 'package:vbvs_app/common/util/MyUtils.dart'; import 'package:intl/intl.dart'; -class SnoreChartContainer extends StatelessWidget { +class SnoreChartContainer extends StatefulWidget { final List snoreValues; final List barData; final List showLabel; @@ -361,29 +430,365 @@ class SnoreChartContainer extends StatelessWidget { super.key, }); + @override + _SnoreChartContainerState createState() => _SnoreChartContainerState(); +} + +class _SnoreChartContainerState extends State { + Offset? _hoverPosition; + dynamic _selectedData; + bool _isSelected = false; + int? _selectedStartTime; + Rect? _selectedRect; + + void _clearSelection() { + setState(() { + _hoverPosition = null; + _selectedData = null; + _isSelected = false; + _selectedStartTime = null; + _selectedRect = null; + }); + } + + void _handleClick(Offset localPosition, Size size) { + final double pixelPerMs = size.width / (widget.endTime - widget.startTime); + final int clickTime = + widget.startTime + (localPosition.dx / pixelPerMs).round(); + + // 如果点击的是已经选中的区域,则取消选择 + if (_isSelected && _selectedStartTime == clickTime) { + _clearSelection(); + return; + } + + // 查找匹配的数据 + dynamic foundData; + Rect? foundRect; + + // 首先在barData中查找(睡眠阶段) + for (var item in widget.barData) { + final int st = item['st']; + final int et = item['et']; + if (clickTime >= st && clickTime <= et) { + foundData = { + 'type': 'sleep_stage', + 'data': item, + 'label': _getSleepStageLabel(item['type']), + 'color': _getSleepStageColor(item['type']), + }; + + // 计算选中区域的位置 + final double leftX = (st - widget.startTime) * pixelPerMs; + final double rightX = (et - widget.startTime) * pixelPerMs; + foundRect = Rect.fromLTWH( + leftX, + 0, + rightX - leftX, + size.height, + ); + break; + } + } + + // 如果在barData中没找到,再在snoreValues中查找 + if (foundData == null) { + for (var item in widget.snoreValues) { + final int st = item['st']; + final int et = item['et']; + if (clickTime >= st && clickTime <= et) { + foundData = { + 'type': 'snore_event', + 'data': item, + 'label': '打鼾事件', + 'color': stringToColor("#8E7DEF"), + }; + + // 计算选中区域的位置 + final double leftX = (st - widget.startTime) * pixelPerMs; + final double rightX = (et - widget.startTime) * pixelPerMs; + foundRect = Rect.fromLTWH( + leftX, + 0, + rightX - leftX, + size.height, + ); + break; + } + } + } + + if (foundData != null) { + setState(() { + _hoverPosition = localPosition; + _selectedData = foundData; + _isSelected = true; + _selectedStartTime = clickTime; + _selectedRect = foundRect; + }); + } else { + // 点击空白区域,清除选择 + _clearSelection(); + } + } + + void _handleHover(Offset localPosition, Size size) { + if (_isSelected) return; // 如果已经有选中项,不处理悬停 + + final double pixelPerMs = size.width / (widget.endTime - widget.startTime); + final int hoverTime = + widget.startTime + (localPosition.dx / pixelPerMs).round(); + + // 查找匹配的数据 + dynamic foundData; + + // 首先在barData中查找(睡眠阶段) + for (var item in widget.barData) { + final int st = item['st']; + final int et = item['et']; + if (hoverTime >= st && hoverTime <= et) { + foundData = { + 'type': 'sleep_stage', + 'data': item, + 'label': _getSleepStageLabel(item['type']), + 'color': _getSleepStageColor(item['type']), + }; + break; + } + } + + // 如果在barData中没找到,再在snoreValues中查找 + if (foundData == null) { + for (var item in widget.snoreValues) { + final int st = item['st']; + final int et = item['et']; + if (hoverTime >= st && hoverTime <= et) { + foundData = { + 'type': 'snore_event', + 'data': item, + 'label': '打鼾事件', + 'color': stringToColor("#8E7DEF"), + }; + break; + } + } + } + + if (foundData != null) { + setState(() { + _hoverPosition = localPosition; + _selectedData = foundData; + }); + } else { + setState(() { + _hoverPosition = null; + _selectedData = null; + }); + } + } + + void _handleHoverExit() { + if (!_isSelected) { + setState(() { + _hoverPosition = null; + _selectedData = null; + }); + } + } + + String _getSleepStageLabel(int type) { + for (var label in widget.showLabel) { + if (label['type'] == type) { + return label['name']?.toString() ?? '未知'; + } + } + return '未知'; + } + + Color _getSleepStageColor(int type) { + for (var label in widget.showLabel) { + if (label['type'] == type) { + final dynamic colorStr = label['color']; + if (colorStr != null && colorStr.toString().isNotEmpty) { + return stringToColor(colorStr.toString()); + } + } + } + return Colors.grey; + } + @override Widget build(BuildContext context) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SnoreBarOverlay( - barData: barData, - showLabel: showLabel, - startTime: startTime, - endTime: endTime, - ), - Container(height: 32.rpx), - Container( - height: 23.rpx, - child: SnoreWaveform( - snoreValues: snoreValues, - startTime: startTime, - endTime: endTime, + return Container( + child: GestureDetector( + behavior: HitTestBehavior.opaque, + onTapDown: (details) { + final size = context.size ?? Size.zero; + if (size.width > 0 && size.height > 0) { + final localPosition = details.localPosition; + _handleClick(localPosition, size); + } + }, + child: MouseRegion( + onHover: (details) { + if (!_isSelected) { + final size = context.size ?? Size.zero; + if (size.width > 0 && size.height > 0) { + final localPosition = details.localPosition; + _handleHover(localPosition, size); + } + } + }, + onExit: (_) { + _handleHoverExit(); + }, + child: Stack( + clipBehavior: Clip.none, + children: [ + // 主要图表内容 + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SnoreBarOverlay( + barData: widget.barData, + showLabel: widget.showLabel, + startTime: widget.startTime, + endTime: widget.endTime, + selectedStartTime: _isSelected ? _selectedStartTime : null, + ), + Container(height: 32.rpx), + Container( + height: 23.rpx, + child: SnoreWaveform( + snoreValues: widget.snoreValues, + startTime: widget.startTime, + endTime: widget.endTime, + selectedStartTime: + _isSelected ? _selectedStartTime : null, + ), + ), + ], + ), + + // 高亮层 + if (_selectedData != null && _selectedRect != null) + Positioned.fill( + child: CustomPaint( + painter: HoverHighlightPainter( + selectedRect: _selectedRect!, + isSelected: _isSelected, + ), + ), + ), + + // 提示框 + if (_selectedData != null && _hoverPosition != null) + _buildTooltipWidget(_selectedData!, _hoverPosition!), + ], ), ), - ], + ), ); } + + Widget _buildTooltipWidget(dynamic data, Offset hoverPosition) { + return Positioned( + left: hoverPosition.dx < 50 ? hoverPosition.dx : hoverPosition.dx - 100, + top: hoverPosition.dy - 100, + child: IgnorePointer( + child: _buildTooltipContent(data), + ), + ); + } + + Widget _buildTooltipContent(dynamic data) { + final int st = data['data']['st']; + final int et = data['data']['et']; + final DateTime startTime = DateTime.fromMillisecondsSinceEpoch(st); + final DateTime endTime = DateTime.fromMillisecondsSinceEpoch(et); + final String duration = _formatDuration(et - st); + + return Container( + // width: 200.rpx, + padding: EdgeInsets.all(16.rpx), // ✅ 容器内部边距,给文字留空间 + decoration: BoxDecoration( + color: Colors.black.withOpacity(0.6), + borderRadius: BorderRadius.circular(20.rpx), + boxShadow: [ + BoxShadow( + color: Colors.black12, + blurRadius: 8, + offset: Offset(0, 4), + ), + ], + // border: Border.all(color: Colors.grey.shade200), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Container( + width: 12, + height: 12, + decoration: BoxDecoration( + color: data['color'], + shape: BoxShape.circle, + ), + ), + SizedBox(width: 8), + Text( + data['label'], + style: TextStyle( + color: data['color'], + fontSize: 20.rpx, + ), + ), + ], + ), + SizedBox(height: 8), + Text( + "开始".tr + ':${DateFormat('HH:mm:ss').format(startTime)}', + style: TextStyle( + color: Color(0XFFFFFFFF), // 多了一个 F,建议改成正确格式 + fontSize: 20.rpx, + ), + ), + Text( + "结束".tr + ':${DateFormat('HH:mm:ss').format(endTime)}', + style: TextStyle( + color: Color(0XFFFFFFFF), // 多了一个 F,建议改成正确格式 + fontSize: 20.rpx, + ), + ), + Text( + "时长".tr + '$duration', + style: TextStyle( + color: Color(0XFFFFFFFF), // 多了一个 F,建议改成正确格式 + fontSize: 20.rpx, + ), + ), + if (data['type'] == 'snore_event') + Text( + '打鼾强度: ${data['data']['value']?.toString() ?? '--'}', + style: TextStyle( + color: Color(0XFFFFFFFF), // 多了一个 F,建议改成正确格式 + fontSize: 20.rpx, + ), + ), + ], + ), + ); + } + + String _formatDuration(int milliseconds) { + final seconds = milliseconds ~/ 1000; + final minutes = seconds ~/ 60; + final remainingSeconds = seconds % 60; + return '${minutes}分${remainingSeconds}秒'; + } } class SnoreBarOverlay extends StatelessWidget { @@ -391,12 +796,14 @@ class SnoreBarOverlay extends StatelessWidget { final List showLabel; final int startTime; final int endTime; + final int? selectedStartTime; const SnoreBarOverlay({ required this.barData, required this.showLabel, required this.startTime, required this.endTime, + this.selectedStartTime, super.key, }); @@ -412,6 +819,7 @@ class SnoreBarOverlay extends StatelessWidget { showLabel: showLabel, startTime: startTime, endTime: endTime, + selectedStartTime: selectedStartTime, ), ), ); @@ -423,14 +831,28 @@ class SnoreBarPainter extends CustomPainter { final List showLabel; final int startTime; final int endTime; + final int? selectedStartTime; SnoreBarPainter({ required this.barData, required this.showLabel, required this.startTime, required this.endTime, + this.selectedStartTime, }); + Color _getSleepStageColor(int type) { + for (var label in showLabel) { + if (label['type'] == type) { + final dynamic colorStr = label['color']; + if (colorStr != null && colorStr.toString().isNotEmpty) { + return stringToColor(colorStr.toString()); + } + } + } + return Colors.transparent; + } + @override void paint(Canvas canvas, Size size) { final double width = size.width; @@ -443,17 +865,13 @@ class SnoreBarPainter extends CustomPainter { final int type = item['type']; int heightInit = 1; - final match = showLabel.firstWhere( - (e) => e['type'] == type, - orElse: () => null, - ); + Color barColor = _getSleepStageColor(type); - Color barColor = Colors.transparent; - if (match != null) { - final dynamic colorStr = match['color']; - if (colorStr != null && colorStr.toString().isNotEmpty) { - barColor = stringToColor(colorStr); - } + bool isSelected = selectedStartTime != null && + selectedStartTime! >= st && + selectedStartTime! <= et; + if (isSelected) { + barColor = barColor.withOpacity(0.9); } final Paint barPaint = Paint() @@ -462,11 +880,8 @@ class SnoreBarPainter extends CustomPainter { final double leftX = (st - startTime) * pixelPerMs; final double rightX = (et - startTime) * pixelPerMs; - final double barWidth = rightX - leftX; + final double barWidth = math.max(rightX - leftX, 1.0); - //rem 深睡 中 - //浅睡 低 - //其他 高 if (type == 1) { heightInit = 1; } else if (type == 2 || type == 6) { @@ -479,22 +894,44 @@ class SnoreBarPainter extends CustomPainter { final rect = Rect.fromLTWH(leftX, top, barWidth, barHeight); canvas.drawRect(rect, barPaint); + + if (isSelected) { + final Paint borderPaint = Paint() + ..color = Colors.white + ..style = PaintingStyle.stroke + ..strokeWidth = 2.0 + ..strokeJoin = StrokeJoin.round; + canvas.drawRect(rect, borderPaint); + + final Paint innerGlowPaint = Paint() + ..color = Colors.white.withOpacity(0.3) + ..style = PaintingStyle.stroke + ..strokeWidth = 1.0 + ..maskFilter = MaskFilter.blur(BlurStyle.normal, 3.0); + canvas.drawRect(rect, innerGlowPaint); + } } } @override - bool shouldRepaint(covariant CustomPainter oldDelegate) => true; + bool shouldRepaint(covariant CustomPainter oldDelegate) { + return oldDelegate is! SnoreBarPainter || + oldDelegate.barData != barData || + oldDelegate.selectedStartTime != selectedStartTime; + } } class SnoreWaveform extends StatelessWidget { final List snoreValues; final int startTime; final int endTime; + final int? selectedStartTime; const SnoreWaveform({ required this.snoreValues, required this.startTime, required this.endTime, + this.selectedStartTime, super.key, }); @@ -510,6 +947,7 @@ class SnoreWaveform extends StatelessWidget { snoreValues: snoreValues, startTime: startTime, endTime: endTime, + selectedStartTime: selectedStartTime, ), ); }, @@ -522,11 +960,13 @@ class SnoreWaveformPainter extends CustomPainter { final List snoreValues; final int startTime; final int endTime; + final int? selectedStartTime; SnoreWaveformPainter({ required this.snoreValues, required this.startTime, required this.endTime, + this.selectedStartTime, }); @override @@ -541,86 +981,93 @@ class SnoreWaveformPainter extends CustomPainter { final double pixelPerMs = width / totalDuration; - // 过滤在时间范围内的有效事件 final validEvents = snoreValues.where((e) { final int st = e['st']; final int et = e['et']; - // 事件与时间范围有重叠 return !(et <= startTime || st >= endTime); }).toList(); - if (validEvents.isEmpty) { - // 绘制无数据提示 - final textPainter = TextPainter( - text: TextSpan( - text: '无打鼾数据', - style: TextStyle(color: Colors.grey, fontSize: 12), - ), - textDirection: ui.TextDirection.ltr, - ); - textPainter.layout(); - textPainter.paint( - canvas, Offset(width / 2 - textPainter.width / 2, height / 2 - 10)); - return; - } - - // 计算中心线位置 final double centerY = height / 2; - // 统一使用一个颜色(打鼾颜色) - final Color snoreColor = stringToColor("#8E7DEF").withOpacity(0.8); - final Paint barPaint = Paint() - ..color = snoreColor - ..style = PaintingStyle.fill; - - final Paint borderPaint = Paint() - ..color = snoreColor.withOpacity(0.9) - ..style = PaintingStyle.stroke - ..strokeWidth = 0.5; - - // 固定高度(上下对称) - final double fixedBarHeight = height * 0.3; // 固定为画布高度的30% - - // 绘制每个打鼾事件(上下对称的柱状图) for (final event in validEvents) { final int st = event['st']; final int et = event['et']; - // 计算绘制位置(裁剪到可视范围内) + bool isSelected = selectedStartTime != null && + selectedStartTime! >= st && + selectedStartTime! <= et; + + Color snoreColor = stringToColor("#8E7DEF").withOpacity(0.8); + if (isSelected) { + snoreColor = stringToColor("#8E7DEF").withOpacity(0.95); + } + + final Paint barPaint = Paint() + ..color = snoreColor + ..style = PaintingStyle.fill; + + final Paint borderPaint = Paint() + ..color = snoreColor.withOpacity(0.9) + ..style = PaintingStyle.stroke + ..strokeWidth = isSelected ? 2.0 : 0.5; + + final double fixedBarHeight = height * 0.3; + final double startX = (st - startTime) * pixelPerMs; final double endX = (et - startTime) * pixelPerMs; - // 确保在画布范围内 if (endX < 0 || startX > width) continue; - final double drawStartX = startX.clamp(0, width); - final double drawEndX = endX.clamp(0, width); - final double drawWidth = drawEndX - drawStartX; + final double drawStartX = math.max(startX, 0); + final double drawEndX = math.min(endX, width); + final double drawWidth = math.max(drawEndX - drawStartX, 1.0); if (drawWidth <= 0) continue; - // 绘制上方的柱状图 final double topBarTop = centerY - fixedBarHeight; final Rect topRect = Rect.fromLTWH(drawStartX, topBarTop, drawWidth, fixedBarHeight); canvas.drawRect(topRect, barPaint); canvas.drawRect(topRect, borderPaint); - // 绘制下方的柱状图(对称) final double bottomBarTop = centerY; final Rect bottomRect = Rect.fromLTWH(drawStartX, bottomBarTop, drawWidth, fixedBarHeight); canvas.drawRect(bottomRect, barPaint); canvas.drawRect(bottomRect, borderPaint); + + if (isSelected) { + final Paint highlightPaint = Paint() + ..color = Colors.white.withOpacity(0.3) + ..style = PaintingStyle.fill; + + final Rect highlightRect = Rect.fromLTWH( + drawStartX - 2, + centerY - fixedBarHeight - 2, + drawWidth + 4, + fixedBarHeight * 2 + 4); + canvas.drawRect(highlightRect, highlightPaint); + + final Paint glowPaint = Paint() + ..color = stringToColor("#8E7DEF").withOpacity(0.2) + ..style = PaintingStyle.stroke + ..strokeWidth = 3.0 + ..maskFilter = MaskFilter.blur(BlurStyle.normal, 5.0); + + final Rect glowRect = Rect.fromLTWH( + drawStartX - 3, + centerY - fixedBarHeight - 3, + drawWidth + 6, + fixedBarHeight * 2 + 6); + canvas.drawRect(glowRect, glowPaint); + } } - // 绘制中心线 final Paint axisPaint = Paint() ..color = Colors.grey.withOpacity(0.3) ..strokeWidth = 0.5; canvas.drawLine(Offset(0, centerY), Offset(width, centerY), axisPaint); - // 绘制时间轴标签 final textPainter = TextPainter( textAlign: TextAlign.center, textDirection: ui.TextDirection.ltr, @@ -629,11 +1076,10 @@ class SnoreWaveformPainter extends CustomPainter { final int hourMs = 60 * 60 * 1000; final int totalHours = (endTime - startTime) ~/ hourMs; - // 1. 始终显示开始时间 - double x = 0; - DateTime startDt = DateTime.fromMillisecondsSinceEpoch(startTime); - String label = DateFormat('HH:mm').format(startDt); + final DateTime startDt = DateTime.fromMillisecondsSinceEpoch(startTime); + final DateTime endDt = DateTime.fromMillisecondsSinceEpoch(endTime); + String label = DateFormat('HH:mm').format(startDt); textPainter.text = TextSpan( text: label, style: TextStyle(fontSize: 10, color: Colors.grey), @@ -641,70 +1087,87 @@ class SnoreWaveformPainter extends CustomPainter { textPainter.layout(); textPainter.paint( canvas, - Offset(x - textPainter.width / 2, height + 2), + Offset(0 - textPainter.width / 2, height + 2), ); - // 2. 决定显示策略 if (totalHours <= 8) { - // 小时间段:显示所有整点小时 - for (int t = startTime + hourMs; t < endTime; t += hourMs) { - x = (t - startTime) * pixelPerMs; - DateTime dt = DateTime.fromMillisecondsSinceEpoch(t); + DateTime currentHour = DateTime(startDt.year, startDt.month, startDt.day, + startDt.hour + 1, 0, 0, 0, 0); - // 判断是否接近边界(30分钟内不显示) - if (t - startTime < 30 * 60 * 1000 || endTime - t < 30 * 60 * 1000) { - continue; + if (startDt.minute == 0 && + startDt.second == 0 && + startDt.millisecond == 0) { + currentHour = startDt; + } + + while (currentHour.millisecondsSinceEpoch < endTime) { + int timeMs = currentHour.millisecondsSinceEpoch; + + if (timeMs > startTime && timeMs < endTime) { + double x = (timeMs - startTime) * pixelPerMs; + + if (timeMs - startTime < 10 * 60 * 1000 || + endTime - timeMs < 10 * 60 * 1000) { + currentHour = currentHour.add(Duration(hours: 1)); + continue; + } + + label = "${currentHour.hour}"; + + textPainter.text = TextSpan( + text: label, + style: TextStyle(fontSize: 10, color: Colors.grey), + ); + textPainter.layout(); + textPainter.paint( + canvas, + Offset(x - textPainter.width / 2, height + 2), + ); } - label = dt.hour == 0 ? "0" : "${dt.hour}"; - - textPainter.text = TextSpan( - text: label, - style: TextStyle(fontSize: 10, color: Colors.grey), - ); - textPainter.layout(); - textPainter.paint( - canvas, - Offset(x - textPainter.width / 2, height + 2), - ); + currentHour = currentHour.add(Duration(hours: 1)); } } else { - // 长时间段:使用自适应间隔 int labelInterval = (totalHours / 6).ceil(); - // 计算第一个标签位置(对齐整点) - int firstLabelMs = - ((startTime ~/ (labelInterval * hourMs))) * labelInterval * hourMs; - if (firstLabelMs <= startTime) { - firstLabelMs += labelInterval * hourMs; + DateTime firstLabelHour = DateTime( + startDt.year, + startDt.month, + startDt.day, + startDt.hour + (labelInterval - (startDt.hour % labelInterval)), + 0, + 0, + 0, + 0); + + if (firstLabelHour.millisecondsSinceEpoch <= startTime) { + firstLabelHour = firstLabelHour.add(Duration(hours: labelInterval)); } - // 绘制中间标签 - for (int t = firstLabelMs; t < endTime; t += labelInterval * hourMs) { - // 跳过太接近边界的时间点(1小时内不显示) - if (t - startTime < hourMs || endTime - t < hourMs) continue; + DateTime currentHour = firstLabelHour; + while (currentHour.millisecondsSinceEpoch < endTime) { + int timeMs = currentHour.millisecondsSinceEpoch; - x = (t - startTime) * pixelPerMs; - DateTime dt = DateTime.fromMillisecondsSinceEpoch(t); - label = dt.hour == 0 ? "0" : "${dt.hour}"; + if (timeMs - startTime >= hourMs && endTime - timeMs >= hourMs) { + double x = (timeMs - startTime) * pixelPerMs; + label = "${currentHour.hour}"; - textPainter.text = TextSpan( - text: label, - style: TextStyle(fontSize: 10, color: Colors.grey), - ); - textPainter.layout(); - textPainter.paint( - canvas, - Offset(x - textPainter.width / 2, height + 2), - ); + textPainter.text = TextSpan( + text: label, + style: TextStyle(fontSize: 10, color: Colors.grey), + ); + textPainter.layout(); + textPainter.paint( + canvas, + Offset(x - textPainter.width / 2, height + 2), + ); + } + + currentHour = currentHour.add(Duration(hours: labelInterval)); } } - // 3. 始终显示结束时间 - x = (endTime - startTime) * pixelPerMs; - DateTime endDt = DateTime.fromMillisecondsSinceEpoch(endTime); label = DateFormat('HH:mm').format(endDt); - textPainter.text = TextSpan( text: label, style: TextStyle(fontSize: 10, color: Colors.grey), @@ -712,7 +1175,7 @@ class SnoreWaveformPainter extends CustomPainter { textPainter.layout(); textPainter.paint( canvas, - Offset(x - textPainter.width / 2, height + 2), + Offset(width - textPainter.width / 2, height + 2), ); } @@ -720,6 +1183,45 @@ class SnoreWaveformPainter extends CustomPainter { bool shouldRepaint(covariant SnoreWaveformPainter oldDelegate) { return oldDelegate.snoreValues != snoreValues || oldDelegate.startTime != startTime || - oldDelegate.endTime != endTime; + oldDelegate.endTime != endTime || + oldDelegate.selectedStartTime != selectedStartTime; } } + +class HoverHighlightPainter extends CustomPainter { + final Rect selectedRect; + final bool isSelected; + + HoverHighlightPainter({ + required this.selectedRect, + required this.isSelected, + }); + + @override + void paint(Canvas canvas, Size size) { + if (!isSelected) return; + + final Paint highlightPaint = Paint() + ..color = Colors.black.withOpacity(0.1) + ..style = PaintingStyle.fill; + + final Rect highlightRect = Rect.fromLTWH( + selectedRect.left.clamp(0, size.width), + 0, + selectedRect.width.clamp(0, size.width - selectedRect.left), + size.height, + ); + + canvas.drawRect(highlightRect, highlightPaint); + + final Paint borderPaint = Paint() + ..color = Colors.blue.withOpacity(0.3) + ..style = PaintingStyle.stroke + ..strokeWidth = 1.0; + + canvas.drawRect(highlightRect, borderPaint); + } + + @override + bool shouldRepaint(covariant CustomPainter oldDelegate) => true; +} diff --git a/lib/pages/sleep_report/component/BreatheCard.dart b/lib/pages/sleep_report/component/BreatheCard.dart index 11cd23a..0ce68ff 100644 --- a/lib/pages/sleep_report/component/BreatheCard.dart +++ b/lib/pages/sleep_report/component/BreatheCard.dart @@ -98,8 +98,9 @@ class _BreatheCardState extends State BorderRadius.circular(AppConstants().normal_container_radius), ), child: Padding( - padding: - EdgeInsetsDirectional.fromSTEB(26.rpx, 29.rpx, 26.rpx, 45.rpx), + // padding: + // EdgeInsetsDirectional.fromSTEB(26.rpx, 29.rpx, 26.rpx, 45.rpx), + padding: EdgeInsets.all(0), child: Wrap( spacing: 23.rpx, runSpacing: 25.rpx, @@ -110,7 +111,8 @@ class _BreatheCardState extends State _shouldAnimate && item['id'] == _highlightedId; return SizedBox( - width: (MediaQuery.of(context).size.width - 160.rpx) / 3, + // width: (MediaQuery.of(context).size.width - 160.rpx) / 3, + width: (MediaQuery.of(context).size.width - 120.rpx) / 3, child: AnimatedBuilder( animation: _animationController ?? AlwaysStoppedAnimation(0), builder: (context, child) { @@ -137,7 +139,7 @@ class _BreatheCardState extends State ), ); } catch (e) { - es.EasyDartModule.logger.error("呼吸监测绘制异常${e}"); + es.EasyDartModule.logger.error("呼吸卡片绘制异常${e}"); return Container(); } } diff --git a/lib/pages/sleep_report/component/HeartRateCard.dart b/lib/pages/sleep_report/component/HeartRateCard.dart index 34620b6..16ad523 100644 --- a/lib/pages/sleep_report/component/HeartRateCard.dart +++ b/lib/pages/sleep_report/component/HeartRateCard.dart @@ -100,15 +100,17 @@ class _HeartRateCardState extends State }).toList(); // 添加 .toList() return Container( width: double.infinity, - decoration: BoxDecoration( - color: themeController.currentColor.sc5, - borderRadius: - BorderRadius.circular(AppConstants().normal_container_radius), - ), + // 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), + // padding: + // EdgeInsetsDirectional.fromSTEB(26.rpx, 29.rpx, 26.rpx, 45.rpx), + padding: EdgeInsets.all(0), child: Wrap( + alignment: WrapAlignment.spaceBetween, spacing: 23.rpx, runSpacing: 25.rpx, children: List.generate(data.length, (index) { @@ -118,7 +120,7 @@ class _HeartRateCardState extends State _shouldAnimate && item['id'] == _highlightedId; return SizedBox( - width: (MediaQuery.of(context).size.width - 160.rpx) / 3, + width: (MediaQuery.of(context).size.width - 120.rpx) / 3, child: AnimatedBuilder( animation: _animationController ?? AlwaysStoppedAnimation(0), builder: (context, child) { @@ -145,7 +147,7 @@ class _HeartRateCardState extends State ), ); } catch (e) { - es.EasyDartModule.logger.error("心率监测绘制异常${e}"); + es.EasyDartModule.logger.error("心率卡片绘制异常${e}"); return Container(); } } diff --git a/lib/pages/sleep_report/component/HeartRateStandardWidget.dart b/lib/pages/sleep_report/component/HeartRateStandardWidget.dart index b1ab36a..a29393b 100644 --- a/lib/pages/sleep_report/component/HeartRateStandardWidget.dart +++ b/lib/pages/sleep_report/component/HeartRateStandardWidget.dart @@ -136,7 +136,8 @@ class _HeartRateStandardWidgetState extends State { 14.rpx, 10.rpx, 14.rpx, 10.rpx), // borderRadius: 0.rpx, // 圆形点击区域 onTap: () { - if (AppConstants().ent_type == APPPackageType.MHT.code) { + if (AppConstants().ent_type == + APPPackageType.MHT.code) { showTipDialog( context, Container( diff --git a/lib/pages/sleep_report/component/SleepCard.dart b/lib/pages/sleep_report/component/SleepCard.dart index 999f9cf..598c02d 100644 --- a/lib/pages/sleep_report/component/SleepCard.dart +++ b/lib/pages/sleep_report/component/SleepCard.dart @@ -104,54 +104,41 @@ class _SleepCardState extends State with TickerProviderStateMixin { .where((item) => item['show'] != false) .toList(); - return Container( - width: double.infinity, - decoration: BoxDecoration( - color: themeController.currentColor.sc5, - borderRadius: - BorderRadius.circular(AppConstants().normal_container_radius), - ), - child: Padding( - padding: - EdgeInsetsDirectional.fromSTEB(26.rpx, 29.rpx, 26.rpx, 45.rpx), - child: Wrap( - alignment: WrapAlignment.center, - spacing: 23.rpx, - runSpacing: 25.rpx, - children: List.generate(data.length, (index) { - final item = data[index]; - item['showTip'] = true; - final bool isHighlighted = - _shouldAnimate && item['id'] == _highlightedId; - return SizedBox( - width: (MediaQuery.of(context).size.width - 160.rpx) / num, - child: AnimatedBuilder( - animation: _animationController ?? AlwaysStoppedAnimation(0), - builder: (context, child) { - return Container( - key: isHighlighted ? _highlightKey : null, - decoration: isHighlighted - ? BoxDecoration( - border: Border.all( - color: themeController.currentColor.sc2 - .withOpacity( - _animationController?.value ?? 0), - width: 1.rpx, - ), - borderRadius: BorderRadius.circular(8), - ) - : null, - child: SleepDataModuleWidget( - data: item, - sleepReportData: widget.sleepReport, - ), - ); - }, - ), - ); - }), - ), - ), + return Wrap( + alignment: WrapAlignment.spaceBetween, + spacing: 23.rpx, + runSpacing: 25.rpx, + children: List.generate(data.length, (index) { + final item = data[index]; + item['showTip'] = true; + final bool isHighlighted = + _shouldAnimate && item['id'] == _highlightedId; + return SizedBox( + width: (MediaQuery.of(context).size.width - 120.rpx) / num, + child: AnimatedBuilder( + animation: _animationController ?? AlwaysStoppedAnimation(0), + builder: (context, child) { + return Container( + key: isHighlighted ? _highlightKey : null, + decoration: isHighlighted + ? BoxDecoration( + border: Border.all( + color: themeController.currentColor.sc2 + .withOpacity(_animationController?.value ?? 0), + width: 1.rpx, + ), + borderRadius: BorderRadius.circular(8), + ) + : null, + child: SleepDataModuleWidget( + data: item, + sleepReportData: widget.sleepReport, + ), + ); + }, + ), + ); + }), ); } catch (e) { es.EasyDartModule.logger.error("数据卡片渲染异常${e}"); diff --git a/lib/pages/sleep_report/component/SleepView.dart b/lib/pages/sleep_report/component/SleepView.dart index fbfad22..4e7a94f 100644 --- a/lib/pages/sleep_report/component/SleepView.dart +++ b/lib/pages/sleep_report/component/SleepView.dart @@ -70,6 +70,22 @@ class _SleepViewWidgetState extends State { // "name": "rem", // "color": "#FFC0CB", // }); + // List typeZeroStages = + // stages.where((stage) => stage['type'] == 0).toList(); + // List snoreStages = stages.where((stage) => stage['type'] == 5).toList(); + // for (int i = 0; i < typeZeroStages.length; i++) { + // var stage = typeZeroStages[i]; + // int st = stage['st']; + // int et = stage['et']; + + // // 转换为DateTime + // DateTime stDate = DateTime.fromMillisecondsSinceEpoch(st); + // DateTime etDate = DateTime.fromMillisecondsSinceEpoch(et); + + // // 一行打印所有信息 + // print( + // '离床元素 ${i + 1}: st=${formatDate(stDate)} et=${formatDate(etDate)}'); + // } return Container( width: double.infinity, @@ -306,7 +322,7 @@ class _SleepViewWidgetState extends State { ), ), SizedBox( - height: 49.rpx, + height: 70.rpx, ), Padding( padding: EdgeInsetsDirectional.fromSTEB( @@ -359,8 +375,13 @@ class _SleepViewWidgetState extends State { ), ); } catch (e) { - es.EasyDartModule.logger.error("打鼾监测绘制异常${e}"); + es.EasyDartModule.logger.error("睡眠规律性异常${e}"); return Container(); } } + + String formatDate(DateTime date) { + return '${date.year}-${date.month.toString().padLeft(2, '0')}-${date.day.toString().padLeft(2, '0')} ' + '${date.hour.toString().padLeft(2, '0')}:${date.minute.toString().padLeft(2, '0')}:${date.second.toString().padLeft(2, '0')}'; + } }