修复眠花糖设置闹钟无效
This commit is contained in:
@@ -2,6 +2,6 @@
|
|||||||
# flutter.android.versionName=2.0.5
|
# flutter.android.versionName=2.0.5
|
||||||
# flutter.android.versionCode=11
|
# flutter.android.versionCode=11
|
||||||
|
|
||||||
# 智慧眠花糖版本信息
|
# 智慧眠花糖版本信息【最新,未提交商店】
|
||||||
flutter.android.versionName=2.0.8
|
flutter.android.versionName=2.0.9
|
||||||
flutter.android.versionCode=12
|
flutter.android.versionCode=13
|
||||||
@@ -1,3 +1,3 @@
|
|||||||
[
|
[
|
||||||
"assets/miniapp/mhtControl_1.0.87.zip"
|
"assets/miniapp/mhtControl_1.0.89.zip"
|
||||||
]
|
]
|
||||||
Binary file not shown.
@@ -2,7 +2,7 @@ class ServiceConstant {
|
|||||||
// static const String baseHost = "zhmht.swes.com.cn:27021"; //服务地址 眠花糖测试地址
|
// static const String baseHost = "zhmht.swes.com.cn:27021"; //服务地址 眠花糖测试地址
|
||||||
static const String baseHost = "zhmht.swes.com.cn:27020"; //服务地址 眠花糖正式地址
|
static const String baseHost = "zhmht.swes.com.cn:27020"; //服务地址 眠花糖正式地址
|
||||||
// static const String baseHost = "vsbs-test.he-info.cn"; //服务地址 本地测试地址
|
// static const String baseHost = "vsbs-test.he-info.cn"; //服务地址 本地测试地址
|
||||||
// static const String baseHost = "vsbst-api.he-info.cn";//服务地址
|
// static const String baseHost = "vsbst-api.he-info.com";//服务地址
|
||||||
// static const String service_address = "http://$baseHost";
|
// static const String service_address = "http://$baseHost";
|
||||||
static const String service_address = "https://$baseHost";
|
static const String service_address = "https://$baseHost";
|
||||||
|
|
||||||
|
|||||||
@@ -93,6 +93,7 @@ Future<ApiResponse> requestWithLog({
|
|||||||
EasyDartModule.logger.error("$logTitle 失败->$e");
|
EasyDartModule.logger.error("$logTitle 失败->$e");
|
||||||
DailyLogUtils.writeError("$logTitle 失败->$e");
|
DailyLogUtils.writeError("$logTitle 失败->$e");
|
||||||
onFailure?.call(apiResponse);
|
onFailure?.call(apiResponse);
|
||||||
|
apiResponse.msg = e.toString();
|
||||||
return apiResponse;
|
return apiResponse;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,8 +6,10 @@ import 'package:EasyDartModule/base/logger/Logger.dart';
|
|||||||
import 'package:EasyDartModule/base/websocket/WebSocket.dart';
|
import 'package:EasyDartModule/base/websocket/WebSocket.dart';
|
||||||
import 'package:easyweb/utils/appmanger.dart';
|
import 'package:easyweb/utils/appmanger.dart';
|
||||||
import 'package:ef/ef.dart';
|
import 'package:ef/ef.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter_inappwebview/flutter_inappwebview.dart';
|
||||||
import 'package:flutter_localizations/flutter_localizations.dart';
|
import 'package:flutter_localizations/flutter_localizations.dart';
|
||||||
import 'package:fluwx/fluwx.dart';
|
import 'package:fluwx/fluwx.dart';
|
||||||
import 'package:get_storage/get_storage.dart';
|
import 'package:get_storage/get_storage.dart';
|
||||||
@@ -98,6 +100,8 @@ Future<void> main() async {
|
|||||||
await initLanguageSetting();
|
await initLanguageSetting();
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
|
|
||||||
|
initServiceAddress();
|
||||||
|
|
||||||
ApiService.init();
|
ApiService.init();
|
||||||
await GetStorage.init();
|
await GetStorage.init();
|
||||||
// 初始化登录
|
// 初始化登录
|
||||||
@@ -111,7 +115,7 @@ Future<void> main() async {
|
|||||||
// 检查网络
|
// 检查网络
|
||||||
Checknetwork.checkNetwork();
|
Checknetwork.checkNetwork();
|
||||||
// 微信开放平台注册
|
// 微信开放平台注册
|
||||||
initWX();
|
await initWX();
|
||||||
// // 初始化 flutter_xupdate android app 更新
|
// // 初始化 flutter_xupdate android app 更新
|
||||||
// initXUpdate();
|
// initXUpdate();
|
||||||
|
|
||||||
@@ -124,6 +128,20 @@ Future<void> main() async {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//初始化服务器地址
|
||||||
|
void initServiceAddress() {
|
||||||
|
if (AppConstants().ent_type == APPPackageType.MHT.code) {
|
||||||
|
// 眠花糖
|
||||||
|
ServiceConstant.baseHost = "zhmht.swes.com.cn:27020";
|
||||||
|
} else if (AppConstants().ent_type == APPPackageType.TH.code) {
|
||||||
|
// 太和
|
||||||
|
ServiceConstant.baseHost = "vsbst-api.he-info.com";
|
||||||
|
} else {
|
||||||
|
//默认
|
||||||
|
ServiceConstant.baseHost = "vsbst-api.he-info.com";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// languageCode 系统默认语言码 en/zh
|
// languageCode 系统默认语言码 en/zh
|
||||||
loadLanguageSetting(String? languageCode) async {
|
loadLanguageSetting(String? languageCode) async {
|
||||||
int code = AppConstants().ent_type;
|
int code = AppConstants().ent_type;
|
||||||
@@ -185,17 +203,46 @@ initLanguageSetting() async {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 初始化微信开放平台注册
|
// 初始化微信开放平台注册
|
||||||
|
// Future<void> initWX() async {
|
||||||
|
// Fluwx fluwx = Fluwx();
|
||||||
|
// fluwx.registerApi(
|
||||||
|
// //请填写自己的微信appid
|
||||||
|
// // appId: "wxeb2688220799e2c5", //太和
|
||||||
|
// appId: "wx929c548fea6af9c7", //眠花糖
|
||||||
|
// doOnAndroid: true,
|
||||||
|
// doOnIOS: true,
|
||||||
|
// // universalLink: "https://app.he-info.com/theh/",
|
||||||
|
// universalLink: "https://zhmht.swes.com.cn/app/",
|
||||||
|
// );
|
||||||
|
// }
|
||||||
|
|
||||||
Future<void> initWX() async {
|
Future<void> initWX() async {
|
||||||
Fluwx fluwx = Fluwx();
|
final fluwx = Fluwx();
|
||||||
fluwx.registerApi(
|
|
||||||
//请填写自己的微信appid
|
String appId = "";
|
||||||
appId: "wxeb2688220799e2c5", //太和
|
String universalLink = "";
|
||||||
// appId: "wx929c548fea6af9c7", //眠花糖
|
|
||||||
|
if (AppConstants().ent_type == APPPackageType.MHT.code) {
|
||||||
|
// 眠花糖
|
||||||
|
appId = "wx929c548fea6af9c7";
|
||||||
|
universalLink = "https://zhmht.swes.com.cn/app/";
|
||||||
|
} else if (AppConstants().ent_type == APPPackageType.TH.code) {
|
||||||
|
// 太和
|
||||||
|
appId = "wxeb2688220799e2c5";
|
||||||
|
universalLink = "https://app.he-info.com/theh/";
|
||||||
|
} else {
|
||||||
|
ef.log("[initWX] 未匹配到有效的包类型 ent_type=${AppConstants().ent_type}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
await fluwx.registerApi(
|
||||||
|
appId: appId,
|
||||||
doOnAndroid: true,
|
doOnAndroid: true,
|
||||||
doOnIOS: true,
|
doOnIOS: true,
|
||||||
universalLink: "https://app.he-info.com/theh/",
|
universalLink: universalLink,
|
||||||
// universalLink: "https://zhmht.swes.com.cn/app/",
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
ef.log("[initWX] 微信注册完成 -> appId: $appId, link: $universalLink");
|
||||||
}
|
}
|
||||||
|
|
||||||
Timer? _messageTimer;
|
Timer? _messageTimer;
|
||||||
@@ -628,11 +675,8 @@ messageStatus() async {
|
|||||||
void initEasyDartModule() {
|
void initEasyDartModule() {
|
||||||
try {
|
try {
|
||||||
EasyDartModule.init(
|
EasyDartModule.init(
|
||||||
loggerConfig:
|
loggerConfig: LoggerConfig(
|
||||||
// LoggerConfig(host: ServiceConstant.logService, serviceName: "智慧眠花糖在线2025-10-9"),
|
host: ServiceConstant.logService, serviceName: "眠花糖在线2025-10-28"),
|
||||||
LoggerConfig(
|
|
||||||
host: ServiceConstant.logService,
|
|
||||||
serviceName: "太和e护在线2025-10-14"),
|
|
||||||
webSocketConfig:
|
webSocketConfig:
|
||||||
WebSocketConfig(ServiceConstant.webSocketService, (data) {
|
WebSocketConfig(ServiceConstant.webSocketService, (data) {
|
||||||
// 接收到服务消息
|
// 接收到服务消息
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import 'package:vbvs_app/common/util/MyUtils.dart';
|
|||||||
import 'package:vbvs_app/component/tool/TopSlideNotification.dart';
|
import 'package:vbvs_app/component/tool/TopSlideNotification.dart';
|
||||||
import 'package:vbvs_app/controller/mh_controller/mh_language_controller.dart';
|
import 'package:vbvs_app/controller/mh_controller/mh_language_controller.dart';
|
||||||
import 'package:vbvs_app/controller/user_info_controller.dart';
|
import 'package:vbvs_app/controller/user_info_controller.dart';
|
||||||
|
import 'package:vbvs_app/enum/APPPackageType.dart';
|
||||||
import 'package:vbvs_app/model/WebSocketMessage.dart';
|
import 'package:vbvs_app/model/WebSocketMessage.dart';
|
||||||
import 'package:vbvs_app/pages/common/bezier_bottom_navigation_bar.dart';
|
import 'package:vbvs_app/pages/common/bezier_bottom_navigation_bar.dart';
|
||||||
import 'package:vbvs_app/pages/common/selectDialog.dart';
|
import 'package:vbvs_app/pages/common/selectDialog.dart';
|
||||||
@@ -126,6 +127,7 @@ class _HomePageState extends State<MainPageBBottomChange>
|
|||||||
final getStorage = GetStorage();
|
final getStorage = GetStorage();
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
if (AppConstants().ent_type == APPPackageType.MHT.code) {
|
||||||
Future.delayed(const Duration(milliseconds: 0), () {
|
Future.delayed(const Duration(milliseconds: 0), () {
|
||||||
String? isShowYingShiDialog = getStorage.read("isShowYingShiDialog");
|
String? isShowYingShiDialog = getStorage.read("isShowYingShiDialog");
|
||||||
if (isShowYingShiDialog == null || isShowYingShiDialog != "true") {
|
if (isShowYingShiDialog == null || isShowYingShiDialog != "true") {
|
||||||
@@ -147,6 +149,7 @@ class _HomePageState extends State<MainPageBBottomChange>
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
return Obx(() {
|
return Obx(() {
|
||||||
final currentLanguage =
|
final currentLanguage =
|
||||||
languageController.selectLanguage.value; // 监听此变量变化
|
languageController.selectLanguage.value; // 监听此变量变化
|
||||||
|
|||||||
@@ -269,7 +269,6 @@
|
|||||||
// }
|
// }
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
|
||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
|
|
||||||
@@ -286,6 +285,7 @@ import 'package:vbvs_app/controller/main_bottom/main_page_controller.dart';
|
|||||||
import 'package:vbvs_app/controller/message/message_controller.dart';
|
import 'package:vbvs_app/controller/message/message_controller.dart';
|
||||||
import 'package:vbvs_app/controller/theme_controller/ThemeController.dart';
|
import 'package:vbvs_app/controller/theme_controller/ThemeController.dart';
|
||||||
import 'package:vbvs_app/controller/user_info_controller.dart';
|
import 'package:vbvs_app/controller/user_info_controller.dart';
|
||||||
|
import 'package:vbvs_app/enum/APPPackageType.dart';
|
||||||
import 'package:vbvs_app/enum/LoginStatus.dart';
|
import 'package:vbvs_app/enum/LoginStatus.dart';
|
||||||
import 'package:vbvs_app/pages/common/selectDialog.dart';
|
import 'package:vbvs_app/pages/common/selectDialog.dart';
|
||||||
import 'package:vbvs_app/pages/main_bottom/e_page.dart';
|
import 'package:vbvs_app/pages/main_bottom/e_page.dart';
|
||||||
@@ -315,12 +315,10 @@ class MainPageBottomChange extends GetView<MainPageController> {
|
|||||||
bottomItems = [
|
bottomItems = [
|
||||||
getBottomNavigationBarItem("assets/img/menu/home.svg",
|
getBottomNavigationBarItem("assets/img/menu/home.svg",
|
||||||
"assets/img/menu/n_home.svg", "菜单.首页".tr),
|
"assets/img/menu/n_home.svg", "菜单.首页".tr),
|
||||||
getBottomNavigationBarItem("assets/img/menu/e.svg",
|
|
||||||
"assets/img/menu/n_e.svg", "菜单.小e".tr),
|
|
||||||
getBottomNavigationBarItem(
|
getBottomNavigationBarItem(
|
||||||
"assets/img/menu/message.svg",
|
"assets/img/menu/e.svg", "assets/img/menu/n_e.svg", "菜单.小e".tr),
|
||||||
"assets/img/menu/n_message.svg",
|
getBottomNavigationBarItem("assets/img/menu/message.svg",
|
||||||
"菜单.消息".tr,
|
"assets/img/menu/n_message.svg", "菜单.消息".tr,
|
||||||
showBadge: (messageController.model.body_message_read == 1 ||
|
showBadge: (messageController.model.body_message_read == 1 ||
|
||||||
messageController.model.system_message_read == 1)),
|
messageController.model.system_message_read == 1)),
|
||||||
getBottomNavigationBarItem("assets/img/menu/mine.svg",
|
getBottomNavigationBarItem("assets/img/menu/mine.svg",
|
||||||
@@ -335,10 +333,8 @@ class MainPageBottomChange extends GetView<MainPageController> {
|
|||||||
bottomItems = [
|
bottomItems = [
|
||||||
getBottomNavigationBarItem("assets/img/menu/home.svg",
|
getBottomNavigationBarItem("assets/img/menu/home.svg",
|
||||||
"assets/img/menu/n_home.svg", "菜单.首页".tr),
|
"assets/img/menu/n_home.svg", "菜单.首页".tr),
|
||||||
getBottomNavigationBarItem(
|
getBottomNavigationBarItem("assets/img/menu/message.svg",
|
||||||
"assets/img/menu/message.svg",
|
"assets/img/menu/n_message.svg", "菜单.消息".tr,
|
||||||
"assets/img/menu/n_message.svg",
|
|
||||||
"菜单.消息".tr,
|
|
||||||
showBadge: (messageController.model.body_message_read == 1 ||
|
showBadge: (messageController.model.body_message_read == 1 ||
|
||||||
messageController.model.system_message_read == 1)),
|
messageController.model.system_message_read == 1)),
|
||||||
getBottomNavigationBarItem("assets/img/menu/mine.svg",
|
getBottomNavigationBarItem("assets/img/menu/mine.svg",
|
||||||
@@ -391,6 +387,7 @@ class MainPageBottomChange extends GetView<MainPageController> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
|
if (AppConstants().ent_type == APPPackageType.TH.code) {
|
||||||
Future.delayed(const Duration(milliseconds: 0), () {
|
Future.delayed(const Duration(milliseconds: 0), () {
|
||||||
String? isShowYingShiDialog = getStorage.read("isShowYingShiDialog");
|
String? isShowYingShiDialog = getStorage.read("isShowYingShiDialog");
|
||||||
if (isShowYingShiDialog == null || isShowYingShiDialog != "true") {
|
if (isShowYingShiDialog == null || isShowYingShiDialog != "true") {
|
||||||
@@ -409,6 +406,7 @@ class MainPageBottomChange extends GetView<MainPageController> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return PopScope(
|
return PopScope(
|
||||||
canPop: false,
|
canPop: false,
|
||||||
@@ -436,7 +434,8 @@ class MainPageBottomChange extends GetView<MainPageController> {
|
|||||||
return Scaffold(
|
return Scaffold(
|
||||||
body: IndexedStack(
|
body: IndexedStack(
|
||||||
index: currentIndex,
|
index: currentIndex,
|
||||||
children: arr.map((page) => SizedBox.expand(child: page)).toList(),
|
children:
|
||||||
|
arr.map((page) => SizedBox.expand(child: page)).toList(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
} else {
|
} else {
|
||||||
@@ -451,7 +450,8 @@ class MainPageBottomChange extends GetView<MainPageController> {
|
|||||||
backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
body: IndexedStack(
|
body: IndexedStack(
|
||||||
index: currentIndex,
|
index: currentIndex,
|
||||||
children: arr.map((page) => SizedBox.expand(child: page)).toList(),
|
children:
|
||||||
|
arr.map((page) => SizedBox.expand(child: page)).toList(),
|
||||||
),
|
),
|
||||||
bottomNavigationBar: Theme(
|
bottomNavigationBar: Theme(
|
||||||
data: Theme.of(context).copyWith(
|
data: Theme.of(context).copyWith(
|
||||||
@@ -497,7 +497,8 @@ class MainPageBottomChange extends GetView<MainPageController> {
|
|||||||
Get.currentRoute == '/messagePage') {
|
Get.currentRoute == '/messagePage') {
|
||||||
Get.back();
|
Get.back();
|
||||||
}
|
}
|
||||||
Future.delayed(const Duration(milliseconds: 100), () {
|
Future.delayed(const Duration(milliseconds: 100),
|
||||||
|
() {
|
||||||
Get.toNamed("/otherLoginPage");
|
Get.toNamed("/otherLoginPage");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@@ -505,7 +506,8 @@ class MainPageBottomChange extends GetView<MainPageController> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (controller.model.currentIndex != index) {
|
if (controller.model.currentIndex != index) {
|
||||||
globalController.model.hideBottomNavigationBar = false;
|
globalController.model.hideBottomNavigationBar =
|
||||||
|
false;
|
||||||
globalController.updateAll();
|
globalController.updateAll();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -527,7 +529,8 @@ class MainPageBottomChange extends GetView<MainPageController> {
|
|||||||
Future<bool> _handleBackPressed(BuildContext context) async {
|
Future<bool> _handleBackPressed(BuildContext context) async {
|
||||||
final currentTime = DateTime.now();
|
final currentTime = DateTime.now();
|
||||||
if (_lastBackPressedTime == null ||
|
if (_lastBackPressedTime == null ||
|
||||||
currentTime.difference(_lastBackPressedTime!) > const Duration(seconds: 2)) {
|
currentTime.difference(_lastBackPressedTime!) >
|
||||||
|
const Duration(seconds: 2)) {
|
||||||
_lastBackPressedTime = currentTime;
|
_lastBackPressedTime = currentTime;
|
||||||
TopSlideNotification.show(context, text: "滑动退出提醒".tr);
|
TopSlideNotification.show(context, text: "滑动退出提醒".tr);
|
||||||
return false;
|
return false;
|
||||||
|
|||||||
@@ -106,53 +106,53 @@ class _VitalSignsSensorState extends State<VitalSignsSensorPage> {
|
|||||||
left: 0.rpx,
|
left: 0.rpx,
|
||||||
child: returnIconButtomNew(),
|
child: returnIconButtomNew(),
|
||||||
),
|
),
|
||||||
// Positioned(
|
Positioned(
|
||||||
// right: 0.rpx,
|
right: 0.rpx,
|
||||||
// child: ClickableContainer(
|
child: ClickableContainer(
|
||||||
// backgroundColor: Colors.transparent,
|
backgroundColor: Colors.transparent,
|
||||||
// highlightColor: Colors.transparent,
|
highlightColor: Colors.transparent,
|
||||||
// padding: EdgeInsets.symmetric(
|
padding: EdgeInsets.symmetric(
|
||||||
// horizontal: 30.rpx, vertical: 30.rpx),
|
horizontal: 30.rpx, vertical: 30.rpx),
|
||||||
// onTap: () {
|
onTap: () {
|
||||||
// // TopSlideNotification.show(context);
|
// TopSlideNotification.show(context);
|
||||||
// //检测蓝牙是否开启,没有开启提示开启蓝牙
|
//检测蓝牙是否开启,没有开启提示开启蓝牙
|
||||||
// //如果已经开启,则进行x秒的蓝牙扫描
|
//如果已经开启,则进行x秒的蓝牙扫描
|
||||||
// BluetoothHelper.listenBluetoothState((isOn) {
|
BluetoothHelper.listenBluetoothState((isOn) {
|
||||||
// mhtBlueToothController.model.bluetooth = isOn;
|
mhtBlueToothController.model.bluetooth = isOn;
|
||||||
// mhtBlueToothController.updateAll();
|
mhtBlueToothController.updateAll();
|
||||||
// if (!isOn) {
|
if (!isOn) {
|
||||||
// mhtBlueToothController.model.blueRawData = [];
|
mhtBlueToothController.model.blueRawData = [];
|
||||||
// mhtBlueToothController
|
mhtBlueToothController
|
||||||
// .model.deviceDataStatus = [];
|
.model.deviceDataStatus = [];
|
||||||
// mhtBlueToothController.updateAll();
|
mhtBlueToothController.updateAll();
|
||||||
// _showBluetoothNotEnabledDialog();
|
_showBluetoothNotEnabledDialog();
|
||||||
// }
|
}
|
||||||
// });
|
});
|
||||||
// _checkBluetoothPermission();
|
_checkBluetoothPermission();
|
||||||
// },
|
},
|
||||||
// child: Obx(() {
|
child: Obx(() {
|
||||||
// return SizedBox(
|
return SizedBox(
|
||||||
// width: 34.rpx,
|
width: 34.rpx,
|
||||||
// height: mhtBlueToothController.isScanning.value
|
height: mhtBlueToothController.isScanning.value
|
||||||
// ? 34.rpx
|
? 34.rpx
|
||||||
// : 24.rpx,
|
: 24.rpx,
|
||||||
// child: mhtBlueToothController.isScanning.value
|
child: mhtBlueToothController.isScanning.value
|
||||||
// ? CircularProgressIndicator(
|
? CircularProgressIndicator(
|
||||||
// strokeWidth: 2,
|
strokeWidth: 2,
|
||||||
// valueColor:
|
valueColor:
|
||||||
// AlwaysStoppedAnimation<Color>(
|
AlwaysStoppedAnimation<Color>(
|
||||||
// themeController.currentColor.sc3,
|
themeController.currentColor.sc3,
|
||||||
// ),
|
),
|
||||||
// )
|
)
|
||||||
// : SvgPicture.asset(
|
: SvgPicture.asset(
|
||||||
// 'assets/img/icon/upgrade.svg',
|
'assets/img/icon/upgrade.svg',
|
||||||
// fit: BoxFit.cover,
|
fit: BoxFit.cover,
|
||||||
// color: Colors.white,
|
color: Colors.white,
|
||||||
// ),
|
),
|
||||||
// );
|
);
|
||||||
// }),
|
}),
|
||||||
// ),
|
),
|
||||||
// ),
|
),
|
||||||
],
|
],
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import 'package:easydevice/easydevice.dart';
|
|||||||
import 'package:ef/ef.dart';
|
import 'package:ef/ef.dart';
|
||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
import 'package:flutterflow_ui/flutterflow_ui.dart';
|
import 'package:flutterflow_ui/flutterflow_ui.dart';
|
||||||
|
import 'package:mhtctrl/mhtctrl.dart';
|
||||||
import 'package:vbvs_app/common/color/ServiceConstant.dart';
|
import 'package:vbvs_app/common/color/ServiceConstant.dart';
|
||||||
import 'package:vbvs_app/common/color/app_uri_status.dart';
|
import 'package:vbvs_app/common/color/app_uri_status.dart';
|
||||||
import 'package:vbvs_app/common/util/DailyLogUtils.dart';
|
import 'package:vbvs_app/common/util/DailyLogUtils.dart';
|
||||||
@@ -18,6 +19,8 @@ import 'package:vbvs_app/controller/theme_controller/ThemeController.dart';
|
|||||||
import 'package:vbvs_app/model/BleDeviceData.dart';
|
import 'package:vbvs_app/model/BleDeviceData.dart';
|
||||||
import 'package:vbvs_app/model/api_response.dart';
|
import 'package:vbvs_app/model/api_response.dart';
|
||||||
import 'package:vbvs_app/pages/mh_page/component/mht_bind_dialog.dart';
|
import 'package:vbvs_app/pages/mh_page/component/mht_bind_dialog.dart';
|
||||||
|
import 'package:vbvs_app/pages/mh_page/device/component/tool/BedControlService.dart';
|
||||||
|
import 'package:vbvs_app/pages/mh_page/device/component/tool/DeviceType.dart';
|
||||||
import 'package:vbvs_app/pages/mh_page/device/controller/mht_bluetooth_controller.dart';
|
import 'package:vbvs_app/pages/mh_page/device/controller/mht_bluetooth_controller.dart';
|
||||||
import 'package:vbvs_app/pages/mh_page/device/model/BlueToothDataModel.dart';
|
import 'package:vbvs_app/pages/mh_page/device/model/BlueToothDataModel.dart';
|
||||||
import 'package:vbvs_app/pages/mh_page/homepage/controller/mht_home_controller.dart';
|
import 'package:vbvs_app/pages/mh_page/homepage/controller/mht_home_controller.dart';
|
||||||
@@ -42,6 +45,8 @@ class _DeviceComponentWidgetState extends State<DeviceComponentWidget> {
|
|||||||
MHTBlueToothController blueteethBindController = Get.find();
|
MHTBlueToothController blueteethBindController = Get.find();
|
||||||
MHTHomeController homeController = Get.find();
|
MHTHomeController homeController = Get.find();
|
||||||
var lisObj;
|
var lisObj;
|
||||||
|
late MattressControlService service;
|
||||||
|
late BedControlService bedService;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -232,6 +237,8 @@ class _DeviceComponentWidgetState extends State<DeviceComponentWidget> {
|
|||||||
TopSlideNotification.show(context,
|
TopSlideNotification.show(context,
|
||||||
text: response.msg!);
|
text: response.msg!);
|
||||||
if (response.code == HttpStatusCodes.ok) {
|
if (response.code == HttpStatusCodes.ok) {
|
||||||
|
//关闭闹钟和打鼾干预
|
||||||
|
// resetLastDeviceConfig(widget.bleDevice);
|
||||||
homeController.getPersonList();
|
homeController.getPersonList();
|
||||||
//请求绑定设备列表
|
//请求绑定设备列表
|
||||||
// homeController.getSleepReport();
|
// homeController.getSleepReport();
|
||||||
@@ -761,6 +768,49 @@ class _DeviceComponentWidgetState extends State<DeviceComponentWidget> {
|
|||||||
|
|
||||||
throw Exception("获取MAC超时".tr);
|
throw Exception("获取MAC超时".tr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//重置设备闹钟数据和打鼾干预
|
||||||
|
// void resetLastDeviceConfig(BlueToothDataModel bleDevice) {
|
||||||
|
// int deviceType = bleDevice.type;
|
||||||
|
// if (deviceType == 2) {
|
||||||
|
// //床垫
|
||||||
|
// service = MattressControlService((pkg) async {
|
||||||
|
// var bytes = pkg.toBytes();
|
||||||
|
// String serviceCode = "";
|
||||||
|
// if (bleDevice != null) {
|
||||||
|
// if (deviceType == DeviceType.smartBed.value) {
|
||||||
|
// serviceCode = "ffe0/ffe1";
|
||||||
|
// }
|
||||||
|
// if (deviceType == DeviceType.smartMattress.value) {
|
||||||
|
// serviceCode = "fff0/fff2";
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
// if (bytes[7] != 0x00) {
|
||||||
|
// //打印bytes
|
||||||
|
// String hex = bytes.toString();
|
||||||
|
// print("--------->" + hex);
|
||||||
|
// String hexString = bytes
|
||||||
|
// .map((e) => e.toRadixString(16).padLeft(2, '0'))
|
||||||
|
// .join(' ')
|
||||||
|
// .toUpperCase();
|
||||||
|
// print("--------->hex->" + hexString);
|
||||||
|
// }
|
||||||
|
// int aa = await blueteethBindController.currentDevice!
|
||||||
|
// .write(bleDevice.mac, serviceCode, bytes, withresponse: true);
|
||||||
|
// ef.log(bytes.toString());
|
||||||
|
// return aa == 0 ? true : false;
|
||||||
|
// });
|
||||||
|
// } else if (deviceType == 3) {
|
||||||
|
// //床
|
||||||
|
// bedService = BedControlService(_writeToDevice); // 初始化
|
||||||
|
// }
|
||||||
|
// }
|
||||||
|
|
||||||
|
// Future<bool> _writeToDevice(Uint8List pkg) async {
|
||||||
|
// int result = await bleToolController.ble
|
||||||
|
// .write(macAddress.value, "ffe0/ffe1", pkg, withresponse: true);
|
||||||
|
// return result == 0;
|
||||||
|
// }
|
||||||
}
|
}
|
||||||
|
|
||||||
String parseMacFromBleResponse(List<int> data) {
|
String parseMacFromBleResponse(List<int> data) {
|
||||||
|
|||||||
512
lib/pages/mh_page/device/component/tool/BedControlService.dart
Normal file
512
lib/pages/mh_page/device/component/tool/BedControlService.dart
Normal file
@@ -0,0 +1,512 @@
|
|||||||
|
import 'dart:async';
|
||||||
|
import 'dart:typed_data';
|
||||||
|
import 'package:ef/ef.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:mhtctrl/mhtctrl.dart';
|
||||||
|
|
||||||
|
class BedControlService {
|
||||||
|
// 协议常量
|
||||||
|
static const List<int> frameHeader = [0xFF, 0xFF, 0xFF, 0xFF, 0x05];
|
||||||
|
static const int minFrameLength = 11;
|
||||||
|
|
||||||
|
final Future<bool> Function(Uint8List) _writeFn;
|
||||||
|
final StreamController<BedStatus> _statusController =
|
||||||
|
StreamController.broadcast();
|
||||||
|
|
||||||
|
Stream<BedStatus> get statusStream => _statusController.stream;
|
||||||
|
|
||||||
|
BedControlService(this._writeFn);
|
||||||
|
|
||||||
|
// 校验和计算(根据协议文档硬编码)
|
||||||
|
static List<int> _getChecksum(int commandCode) {
|
||||||
|
final checksumMap = {
|
||||||
|
0x00: [0xD7, 0x00], // 停止
|
||||||
|
0x01: [0x16, 0xC0], // 头部上升
|
||||||
|
0x02: [0x56, 0xC1], // 头部下降
|
||||||
|
0x03: [0x97, 0x01], // 背部上升
|
||||||
|
0x04: [0xD6, 0xC3], // 背部下降
|
||||||
|
0x05: [0x17, 0x03], // 观影模式
|
||||||
|
0x06: [0x57, 0x02], // 腿部上升
|
||||||
|
0x07: [0x96, 0xC2], // 腿部下降
|
||||||
|
0x08: [0xD6, 0xC6], // 复位
|
||||||
|
0x09: [0x17, 0x06], // 零重力
|
||||||
|
0x0A: [0x57, 0x07], // 记忆1
|
||||||
|
0x0B: [0x96, 0xC7], // 记忆2
|
||||||
|
0x0C: [0xC7, 0x04], // 童锁
|
||||||
|
0x0D: [0x16, 0xC5], // 臀部上升
|
||||||
|
0x0E: [0x56, 0xC4], // 臀部下降
|
||||||
|
0x0F: [0x97, 0x04], // 止鼾
|
||||||
|
0x10: [0xD6, 0xCC], // 背部按摩+
|
||||||
|
0x11: [0x17, 0x0C], // 背部按摩-
|
||||||
|
0x12: [0x57, 0x0D], // 腿部按摩+
|
||||||
|
0x13: [0x96, 0xCD], // 腿部按摩-
|
||||||
|
0x14: [0xD7, 0x0F], // 按摩频率+
|
||||||
|
0x15: [0x16, 0xCF], // 按摩频率-
|
||||||
|
0x16: [0x56, 0xCE], // 按摩时间10min
|
||||||
|
0x17: [0x97, 0x0E], // 按摩时间20min
|
||||||
|
0x18: [0xD7, 0x0A], // 按摩时间30min
|
||||||
|
0x19: [0x16, 0xCA], // 灯光10min
|
||||||
|
0x1A: [0x56, 0xCB], // 灯光8H
|
||||||
|
0x1B: [0x97, 0x0B], // 灯光10H
|
||||||
|
0x1C: [0xD6, 0xC9], // 按摩停止
|
||||||
|
0x2B: [0x97, 0x1F], // 背腿同上
|
||||||
|
0x2C: [0xD6, 0xDD], // 背腿同下
|
||||||
|
0x48: [0xD7, 0x36], // 阅读模式
|
||||||
|
0x49: [0x16, 0xF6], // 舒压模式
|
||||||
|
0x4E: [0x57, 0x34], // 瑜伽模式
|
||||||
|
0x6A: [0x57, 0x2F], // 助眠模式
|
||||||
|
0x50: [0xD7, 0x3C], // 背部按摩强度1
|
||||||
|
0x51: [0x16, 0xFC], // 背部按摩强度2
|
||||||
|
0x52: [0x56, 0xFD], // 背部按摩强度3
|
||||||
|
0x54: [0xD6, 0xFF], // 腿部按摩强度1
|
||||||
|
0x55: [0x17, 0x3F], // 腿部按摩强度2
|
||||||
|
0x56: [0x57, 0x3E], // 腿部按摩强度3
|
||||||
|
0x57: [0x96, 0xFE], // 按摩频率1
|
||||||
|
0x58: [0xD6, 0xFA], // 按摩频率2
|
||||||
|
0x59: [0x17, 0x3A], // 按摩频率3
|
||||||
|
0x5A: [0x57, 0x3B], // 按摩频率4
|
||||||
|
0x4F: [0x96, 0xF4], // 停止背
|
||||||
|
0x53: [0x97, 0x3D], // 停止腿
|
||||||
|
0x47: [0x87, 0x33], // 休闲模式
|
||||||
|
0xE6: [0x46, 0x8B], // 腰部放松
|
||||||
|
0xE5: [0x06, 0x8A], // 腿部放松
|
||||||
|
};
|
||||||
|
return checksumMap[commandCode] ?? [0x00, 0x00];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 基础指令构建(11字节完整格式)
|
||||||
|
Uint8List _buildCommand(int byte5, int byte6, int byte7, int byte8) {
|
||||||
|
final data = Uint8List(11);
|
||||||
|
data.setAll(0, frameHeader); // FF FF FF FF 05
|
||||||
|
data[5] = byte5;
|
||||||
|
data[6] = byte6;
|
||||||
|
data[7] = byte7;
|
||||||
|
data[8] = byte8;
|
||||||
|
final checksum = _getChecksum(byte8);
|
||||||
|
data[9] = checksum[0]; // 校验和高字节
|
||||||
|
data[10] = checksum[1]; // 校验和低字节
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 进度控制指令构建(特殊格式)
|
||||||
|
Uint8List _buildProgressCommand(int progress, int baseCmd, int commandCode) {
|
||||||
|
final data = Uint8List(11);
|
||||||
|
data.setAll(0, frameHeader);
|
||||||
|
data[5] = progress;
|
||||||
|
data[6] = baseCmd >> 8;
|
||||||
|
data[7] = baseCmd & 0xFF;
|
||||||
|
data[8] = commandCode;
|
||||||
|
final checksum = _getChecksum(commandCode);
|
||||||
|
data[9] = checksum[0];
|
||||||
|
data[10] = checksum[1];
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
//====== 指令集 ======//
|
||||||
|
|
||||||
|
// 1. 遥控器上报功能(固定10字节)
|
||||||
|
Future<bool> enableRemoteReport() => _sendCommand(
|
||||||
|
[0xFF, 0xFF, 0xFF, 0xFF, 0x05, 0x00, 0x00, 0xF2, 0x56, 0x85]);
|
||||||
|
|
||||||
|
// 2. 基础控制指令
|
||||||
|
Future<bool> stop() => _sendCommand(_buildCommand(0x00, 0x00, 0x00, 0x00));
|
||||||
|
Future<bool> moveHeadUp() =>
|
||||||
|
_sendCommand(_buildCommand(0x00, 0x00, 0x00, 0x01));
|
||||||
|
Future<bool> moveHeadDown() =>
|
||||||
|
_sendCommand(_buildCommand(0x00, 0x00, 0x00, 0x02));
|
||||||
|
Future<bool> moveBackUp() =>
|
||||||
|
_sendCommand(_buildCommand(0x00, 0x00, 0x00, 0x03));
|
||||||
|
Future<bool> moveBackDown() =>
|
||||||
|
_sendCommand(_buildCommand(0x00, 0x00, 0x00, 0x04));
|
||||||
|
Future<bool> moveLegUp() =>
|
||||||
|
_sendCommand(_buildCommand(0x00, 0x00, 0x00, 0x06));
|
||||||
|
Future<bool> moveLegDown() =>
|
||||||
|
_sendCommand(_buildCommand(0x00, 0x00, 0x00, 0x07));
|
||||||
|
Future<bool> moveHipUp() =>
|
||||||
|
_sendCommand(_buildCommand(0x00, 0x00, 0x00, 0x0D));
|
||||||
|
Future<bool> moveHipDown() =>
|
||||||
|
_sendCommand(_buildCommand(0x00, 0x00, 0x00, 0x0E));
|
||||||
|
Future<bool> resetPosition() =>
|
||||||
|
_sendCommand(_buildCommand(0x00, 0x00, 0x00, 0x08));
|
||||||
|
|
||||||
|
// 3. 预设模式指令
|
||||||
|
Future<bool> setMovieMode([int progress = 0]) => progress > 0
|
||||||
|
? _sendCommand(_buildProgressCommand(progress, 0x0500, 0x05))
|
||||||
|
: _sendCommand(_buildCommand(0x00, 0x00, 0x00, 0x05));
|
||||||
|
|
||||||
|
Future<bool> setZeroGravity([int progress = 0]) => progress > 0
|
||||||
|
? _sendCommand(_buildProgressCommand(progress, 0x0900, 0x09))
|
||||||
|
: _sendCommand(_buildCommand(0x00, 0x00, 0x00, 0x09));
|
||||||
|
|
||||||
|
Future<bool> setAntiSnore([int progress = 0]) => progress > 0
|
||||||
|
? _sendCommand(_buildProgressCommand(progress, 0x0F00, 0x0F))
|
||||||
|
: _sendCommand(_buildCommand(0x00, 0x00, 0x00, 0x0F));
|
||||||
|
|
||||||
|
//一键放松
|
||||||
|
Future<bool> setSleepAssist() =>
|
||||||
|
_sendCommand(_buildCommand(0x00, 0x00, 0x00, 0x6A));
|
||||||
|
// 休闲模式
|
||||||
|
Future<bool> setXiuxianMode() =>
|
||||||
|
_sendCommand(_buildCommand(0x00, 0x05, 0x00, 0x47));
|
||||||
|
|
||||||
|
// 腰部放松
|
||||||
|
Future<bool> setYaoRelaxMode() =>
|
||||||
|
_sendCommand(_buildCommand(0x00, 0x05, 0x00, 0xE6));
|
||||||
|
|
||||||
|
// 腿部放松
|
||||||
|
Future<bool> setLegRelaxMode() =>
|
||||||
|
_sendCommand(_buildCommand(0x00, 0x05, 0x00, 0xE5));
|
||||||
|
Future<bool> setReadingMode() =>
|
||||||
|
_sendCommand(_buildCommand(0x00, 0x00, 0x00, 0x48));
|
||||||
|
Future<bool> setRelaxMode() =>
|
||||||
|
_sendCommand(_buildCommand(0x00, 0x00, 0x00, 0x49));
|
||||||
|
Future<bool> setYogaMode() =>
|
||||||
|
_sendCommand(_buildCommand(0x00, 0x00, 0x00, 0x4E));
|
||||||
|
|
||||||
|
// 4. 记忆功能
|
||||||
|
Future<bool> saveMemory(int slot) {
|
||||||
|
assert(slot >= 1 && slot <= 2);
|
||||||
|
return _sendCommand(
|
||||||
|
_buildCommand(0x00, 0x00, 0x00, slot == 1 ? 0x0A : 0x0B));
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5. 按摩控制
|
||||||
|
Future<bool> increaseBackMassage() =>
|
||||||
|
_sendCommand(_buildCommand(0x00, 0x00, 0x00, 0x10));
|
||||||
|
Future<bool> decreaseBackMassage() =>
|
||||||
|
_sendCommand(_buildCommand(0x00, 0x00, 0x00, 0x11));
|
||||||
|
Future<bool> increaseLegMassage() =>
|
||||||
|
_sendCommand(_buildCommand(0x00, 0x00, 0x00, 0x12));
|
||||||
|
Future<bool> decreaseLegMassage() =>
|
||||||
|
_sendCommand(_buildCommand(0x00, 0x00, 0x00, 0x13));
|
||||||
|
Future<bool> increaseMassageFreq() =>
|
||||||
|
_sendCommand(_buildCommand(0x00, 0x00, 0x00, 0x14));
|
||||||
|
Future<bool> decreaseMassageFreq() =>
|
||||||
|
_sendCommand(_buildCommand(0x00, 0x00, 0x00, 0x15));
|
||||||
|
|
||||||
|
Future<bool> setMassageTime(int minutes) {
|
||||||
|
final cmd = switch (minutes) {
|
||||||
|
10 => _buildCommand(0x00, 0x00, 0x00, 0x16),
|
||||||
|
20 => _buildCommand(0x00, 0x00, 0x00, 0x17),
|
||||||
|
30 => _buildCommand(0x00, 0x00, 0x00, 0x18),
|
||||||
|
_ => _buildCommand(0x00, 0x00, 0x00, 0x16),
|
||||||
|
};
|
||||||
|
return _sendCommand(cmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> stopMassage() =>
|
||||||
|
_sendCommand(_buildCommand(0x00, 0x00, 0x00, 0x1C));
|
||||||
|
|
||||||
|
// 6. 灯光控制
|
||||||
|
Future<bool> toggleLight10Min() =>
|
||||||
|
_sendCommand(_buildCommand(0x00, 0x00, 0x00, 0x19));
|
||||||
|
Future<bool> toggleLight8H() =>
|
||||||
|
_sendCommand(_buildCommand(0x00, 0x00, 0x00, 0x1A));
|
||||||
|
Future<bool> toggleLight10H() =>
|
||||||
|
_sendCommand(_buildCommand(0x00, 0x00, 0x00, 0x1B));
|
||||||
|
|
||||||
|
// 7. 组合控制
|
||||||
|
Future<bool> moveBothUp() =>
|
||||||
|
_sendCommand(_buildCommand(0x00, 0x00, 0x00, 0x2B));
|
||||||
|
Future<bool> moveBothDown() =>
|
||||||
|
_sendCommand(_buildCommand(0x00, 0x00, 0x00, 0x2C));
|
||||||
|
|
||||||
|
// 8. 童锁功能(固定11字节)
|
||||||
|
Future<bool> toggleChildLock() => _sendCommand(
|
||||||
|
[0xFF, 0xFF, 0xFF, 0xFF, 0x05, 0x00, 0x05, 0x00, 0x0C, 0xC7, 0x04]);
|
||||||
|
|
||||||
|
// 背部按摩强度控制
|
||||||
|
Future<bool> setBackMassageLevel1() =>
|
||||||
|
_sendCommand(_buildCommand(0x00, 0x00, 0x00, 0x50));
|
||||||
|
Future<bool> setBackMassageLevel2() =>
|
||||||
|
_sendCommand(_buildCommand(0x00, 0x00, 0x00, 0x51));
|
||||||
|
Future<bool> setBackMassageLevel3() =>
|
||||||
|
_sendCommand(_buildCommand(0x00, 0x00, 0x00, 0x52));
|
||||||
|
|
||||||
|
// 腿部按摩强度控制
|
||||||
|
Future<bool> setLegMassageLevel1() =>
|
||||||
|
_sendCommand(_buildCommand(0x00, 0x00, 0x00, 0x54));
|
||||||
|
Future<bool> setLegMassageLevel2() =>
|
||||||
|
_sendCommand(_buildCommand(0x00, 0x00, 0x00, 0x55));
|
||||||
|
Future<bool> setLegMassageLevel3() =>
|
||||||
|
_sendCommand(_buildCommand(0x00, 0x00, 0x00, 0x56));
|
||||||
|
|
||||||
|
//按摩时间
|
||||||
|
Future<bool> setMassageTime10Min() =>
|
||||||
|
_sendCommand(_buildCommand(0x00, 0x00, 0x00, 0x16));
|
||||||
|
Future<bool> setMassageTime20Min() =>
|
||||||
|
_sendCommand(_buildCommand(0x00, 0x00, 0x00, 0x17));
|
||||||
|
Future<bool> setMassageTime30Min() =>
|
||||||
|
_sendCommand(_buildCommand(0x00, 0x00, 0x00, 0x18));
|
||||||
|
|
||||||
|
Future<bool> setMassageFrequency1() =>
|
||||||
|
_sendCommand(_buildCommand(0x00, 0x00, 0x00, 0x57));
|
||||||
|
Future<bool> setMassageFrequency2() =>
|
||||||
|
_sendCommand(_buildCommand(0x00, 0x00, 0x00, 0x58));
|
||||||
|
Future<bool> setMassageFrequency3() =>
|
||||||
|
_sendCommand(_buildCommand(0x00, 0x00, 0x00, 0x59));
|
||||||
|
Future<bool> setMassageFrequency4() =>
|
||||||
|
_sendCommand(_buildCommand(0x00, 0x00, 0x00, 0x5A));
|
||||||
|
|
||||||
|
Future<bool> stopLeg() => _sendCommand(_buildCommand(0x00, 0x00, 0x00, 0x53));
|
||||||
|
Future<bool> stopBack() =>
|
||||||
|
_sendCommand(_buildCommand(0x00, 0x00, 0x00, 0x4F));
|
||||||
|
//====== 核心方法 ======//
|
||||||
|
Future<bool> _sendCommand(List<int> command) async {
|
||||||
|
debugPrint(
|
||||||
|
'发送指令: ${command.map((e) => e.toRadixString(16).padLeft(2, '0'))}');
|
||||||
|
try {
|
||||||
|
return await _writeFn(Uint8List.fromList(command));
|
||||||
|
} catch (e) {
|
||||||
|
debugPrint('指令发送失败: $e');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void parseResponse(List<int> data) {
|
||||||
|
if (data.length >= minFrameLength && data[0] == 0xFF && data[1] == 0xFF) {
|
||||||
|
// 设备上报帧处理(第7字节为0x06)
|
||||||
|
if (data[7] == 0x06) {
|
||||||
|
final functionCode = data[8];
|
||||||
|
debugPrint('收到设备上报: 功能码=${functionCode.toRadixString(16)}');
|
||||||
|
}
|
||||||
|
_statusController.add(BedStatus.fromBytes(data));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void dispose() => _statusController.close();
|
||||||
|
|
||||||
|
Future<bool> setAlarm({
|
||||||
|
required bool isStart,
|
||||||
|
required List<String> wakeTime,
|
||||||
|
required List<String> weeks,
|
||||||
|
required bool isMassage,
|
||||||
|
required bool isBell,
|
||||||
|
}) async {
|
||||||
|
// 1. 参数校验
|
||||||
|
if (wakeTime.length != 2 || weeks.length != 7) {
|
||||||
|
debugPrint('参数错误:唤醒时间或星期数不符'.tr);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2. 构造指令(固定头 + 动态数据)
|
||||||
|
final arr = <int>[
|
||||||
|
0xFF, 0xFF, 0xFF, 0xFF, 0x01, 0x00, 0x02, 0x13, // 固定头
|
||||||
|
isStart ? 0x01 : 0xA1, // 开关状态
|
||||||
|
int.parse(wakeTime[0], radix: 16), // 小时(16进制)
|
||||||
|
int.parse(wakeTime[1], radix: 16), // 分钟(16进制)
|
||||||
|
0x00, // 保留位
|
||||||
|
];
|
||||||
|
|
||||||
|
// 3. 计算星期掩码
|
||||||
|
final weekValues = weeks.map((w) => int.tryParse(w) ?? 0).toList();
|
||||||
|
final weekMask = weekValues[0] * 2 + // 周一
|
||||||
|
weekValues[1] * 4 + // 周二
|
||||||
|
weekValues[2] * 8 + // 周三
|
||||||
|
weekValues[3] * 16 + // 周四
|
||||||
|
weekValues[4] * 32 + // 周五
|
||||||
|
weekValues[5] * 64 + // 周六
|
||||||
|
weekValues[6] * 128; // 周日
|
||||||
|
arr.addAll([
|
||||||
|
weekMask,
|
||||||
|
weekMask > 1 ? 1 : 0, // 是否重复
|
||||||
|
0x01, // 固定值
|
||||||
|
isMassage ? 1 : 0,
|
||||||
|
isBell ? 1 : 0,
|
||||||
|
]);
|
||||||
|
|
||||||
|
// 4. 计算校验和(小端序)
|
||||||
|
_addSumInEnd(arr);
|
||||||
|
|
||||||
|
// 5. 发送指令并等待响应
|
||||||
|
debugPrint('闹钟指令: ${arr.map((e) => e.toRadixString(16).padLeft(2, '0'))}');
|
||||||
|
return await _writeFn(Uint8List.fromList(arr));
|
||||||
|
}
|
||||||
|
|
||||||
|
List<int> _calculateChecksum(List<int> data) {
|
||||||
|
int sum = 0;
|
||||||
|
for (var byte in data) {
|
||||||
|
sum += byte;
|
||||||
|
}
|
||||||
|
return [sum & 0xFF, (sum >> 8) & 0xFF];
|
||||||
|
}
|
||||||
|
|
||||||
|
void _addSumInEnd(List<int> arr) {
|
||||||
|
int sum = arr.fold(0, (acc, byte) => acc + byte);
|
||||||
|
arr.add(sum & 0xFF); // 低字节
|
||||||
|
arr.add((sum >> 8) & 0xFF); // 高字节
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> setDeviceTime(DateTime dateTime) async {
|
||||||
|
// 1. 参数校验
|
||||||
|
// if (dateTime.year < 2000 || dateTime.year > 2099) {
|
||||||
|
// debugPrint('年份必须在2000-2099之间');
|
||||||
|
// return false;
|
||||||
|
// }
|
||||||
|
|
||||||
|
DateTime date = DateTime.now();
|
||||||
|
// 2. 构造基础指令数据(不含校验和)
|
||||||
|
final arr = <int>[
|
||||||
|
0xFF, 0xFF, 0xFF, 0xFF, // 帧头 (0-3)
|
||||||
|
0x01, 0x00, 0x01, 0x11, // 功能标识:时间校验 (4-7)
|
||||||
|
];
|
||||||
|
|
||||||
|
arr.add(int.parse("${date.hour}", radix: 16));
|
||||||
|
arr.add(int.parse("${date.minute}", radix: 16));
|
||||||
|
arr.add(int.parse("${date.second}", radix: 16));
|
||||||
|
arr.add(int.parse("${date.weekday}", radix: 16));
|
||||||
|
arr.add(int.parse("${date.year}".substring(2), radix: 16));
|
||||||
|
arr.add(int.parse("${date.month}", radix: 16));
|
||||||
|
arr.add(int.parse("${date.day}", radix: 16));
|
||||||
|
|
||||||
|
// 3. 计算并添加校验和(低字节在前)
|
||||||
|
_addSumInEnd(arr);
|
||||||
|
|
||||||
|
// 4. 发送指令
|
||||||
|
debugPrint(
|
||||||
|
'时间校验指令: ${arr.map((e) => e.toRadixString(16).padLeft(2, '0')).join(' ')}');
|
||||||
|
debugPrint('设置时间: ${dateTime.toString()}');
|
||||||
|
return _sendCommand(arr);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 将DateTime的weekday(1-7)转换为协议要求的星期值
|
||||||
|
/// DateTime.weekday: 1=Monday, 2=Tuesday, ..., 7=Sunday
|
||||||
|
/// 协议要求: 1=周一, 2=周二, ..., 7=周日
|
||||||
|
int _getWeekdayValue(int weekday) {
|
||||||
|
// 由于DateTime的weekday已经是1=周一, 7=周日,直接返回即可
|
||||||
|
return weekday;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 使用当前系统时间同步设备时间
|
||||||
|
Future<bool> syncDeviceTime() async {
|
||||||
|
final now = DateTime.now();
|
||||||
|
debugPrint('开始同步设备时间: ${now.toString()}');
|
||||||
|
return await setDeviceTime(now);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 设置自定义时间(部分参数可选)
|
||||||
|
Future<bool> setCustomTime({
|
||||||
|
int? hour,
|
||||||
|
int? minute,
|
||||||
|
int? second = 0,
|
||||||
|
int? year,
|
||||||
|
int? month,
|
||||||
|
int? day,
|
||||||
|
}) async {
|
||||||
|
final now = DateTime.now();
|
||||||
|
final targetTime = DateTime(
|
||||||
|
year ?? now.year,
|
||||||
|
month ?? now.month,
|
||||||
|
day ?? now.day,
|
||||||
|
hour ?? now.hour,
|
||||||
|
minute ?? now.minute,
|
||||||
|
second ?? 0,
|
||||||
|
);
|
||||||
|
|
||||||
|
debugPrint('设置自定义时间: ${targetTime.toString()}');
|
||||||
|
return await setDeviceTime(targetTime);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 快速设置时间(仅小时和分钟)
|
||||||
|
Future<bool> setTime(int hour, int minute) async {
|
||||||
|
return await setCustomTime(hour: hour, minute: minute, second: 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 设置日期(年、月、日)
|
||||||
|
Future<bool> setDate(int year, int month, int day) async {
|
||||||
|
return await setCustomTime(year: year, month: month, day: day);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<bool> exec({
|
||||||
|
required ProtocolDoExecFunc func,
|
||||||
|
ProtocolOnStatusFunc? onstatus,
|
||||||
|
int retry = 3,
|
||||||
|
Duration timeout = const Duration(seconds: 3),
|
||||||
|
}) async {
|
||||||
|
for (int i = 0; i < retry; i++) {
|
||||||
|
try {
|
||||||
|
if (i > 0) {
|
||||||
|
//缓一下
|
||||||
|
await Future.delayed(Duration(milliseconds: 100));
|
||||||
|
}
|
||||||
|
var re = await func();
|
||||||
|
if (onstatus == null || re == false) return re;
|
||||||
|
//等待
|
||||||
|
dynamic status = await statusStream.first.timeout(timeout);
|
||||||
|
bool ok = onstatus(status);
|
||||||
|
if (ok) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
e;
|
||||||
|
if (onstatus == null) return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BedStatus类保持不变...
|
||||||
|
|
||||||
|
// 床状态模型
|
||||||
|
class BedStatus {
|
||||||
|
final bool headUp;
|
||||||
|
final bool headDown;
|
||||||
|
final bool backUp;
|
||||||
|
final bool backDown;
|
||||||
|
final bool legUp;
|
||||||
|
final bool legDown;
|
||||||
|
final bool hipUp;
|
||||||
|
final bool hipDown;
|
||||||
|
final bool massageOn;
|
||||||
|
final int massageLevel;
|
||||||
|
final int massageTimeRemaining;
|
||||||
|
final bool isChildLocked;
|
||||||
|
|
||||||
|
const BedStatus({
|
||||||
|
required this.headUp,
|
||||||
|
required this.headDown,
|
||||||
|
required this.backUp,
|
||||||
|
required this.backDown,
|
||||||
|
required this.legUp,
|
||||||
|
required this.legDown,
|
||||||
|
required this.hipUp,
|
||||||
|
required this.hipDown,
|
||||||
|
required this.massageOn,
|
||||||
|
required this.massageLevel,
|
||||||
|
required this.massageTimeRemaining,
|
||||||
|
required this.isChildLocked,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory BedStatus.fromBytes(List<int> data) {
|
||||||
|
return BedStatus(
|
||||||
|
headUp: (data[5] & 0x01) != 0,
|
||||||
|
headDown: (data[5] & 0x02) != 0,
|
||||||
|
backUp: (data[6] & 0x01) != 0,
|
||||||
|
backDown: (data[6] & 0x02) != 0,
|
||||||
|
legUp: (data[7] & 0x01) != 0,
|
||||||
|
legDown: (data[7] & 0x02) != 0,
|
||||||
|
hipUp: (data[8] & 0x01) != 0,
|
||||||
|
hipDown: (data[8] & 0x02) != 0,
|
||||||
|
massageOn: (data[9] & 0x0F) != 0,
|
||||||
|
massageLevel: (data[9] >> 4) & 0x0F,
|
||||||
|
massageTimeRemaining: data.length > 10 ? data[10] : 0,
|
||||||
|
isChildLocked: data.length > 11 ? (data[11] & 0x80) != 0 : false,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static const defaultStatus = BedStatus(
|
||||||
|
headUp: false,
|
||||||
|
headDown: false,
|
||||||
|
backUp: false,
|
||||||
|
backDown: false,
|
||||||
|
legUp: false,
|
||||||
|
legDown: false,
|
||||||
|
hipUp: false,
|
||||||
|
hipDown: false,
|
||||||
|
massageOn: false,
|
||||||
|
massageLevel: 0,
|
||||||
|
massageTimeRemaining: 0,
|
||||||
|
isChildLocked: false,
|
||||||
|
);
|
||||||
|
}
|
||||||
40
lib/pages/mh_page/device/component/tool/DeviceType.dart
Normal file
40
lib/pages/mh_page/device/component/tool/DeviceType.dart
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
|
||||||
|
import 'package:ef/ef.dart';
|
||||||
|
|
||||||
|
enum DeviceType {
|
||||||
|
smartBed, // 2: 智能床
|
||||||
|
smartMattress // 3: 智能床垫
|
||||||
|
}
|
||||||
|
|
||||||
|
extension DeviceTypeExtension on DeviceType {
|
||||||
|
/// 获取整型值
|
||||||
|
int get value {
|
||||||
|
switch (this) {
|
||||||
|
case DeviceType.smartBed:
|
||||||
|
return 2;
|
||||||
|
case DeviceType.smartMattress:
|
||||||
|
return 3;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// 根据整型值解析为枚举
|
||||||
|
static DeviceType? fromInt(int? type) {
|
||||||
|
switch (type) {
|
||||||
|
case 2:
|
||||||
|
return DeviceType.smartBed;
|
||||||
|
case 3:
|
||||||
|
return DeviceType.smartMattress;
|
||||||
|
default:
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String get description {
|
||||||
|
switch (this) {
|
||||||
|
case DeviceType.smartBed:
|
||||||
|
return "智能电动床".tr;
|
||||||
|
case DeviceType.smartMattress:
|
||||||
|
return "律动智能床垫".tr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -74,9 +74,9 @@ class MHTBlueToothController extends GetControllerEx<MHTBlueToothModel> {
|
|||||||
|
|
||||||
RxBool isScanning = false.obs;
|
RxBool isScanning = false.obs;
|
||||||
RxMap<String, Map> localUpgradeMac = <String, Map>{}.obs; //mac 进度
|
RxMap<String, Map> localUpgradeMac = <String, Map>{}.obs; //mac 进度
|
||||||
String? currentUpgradeVersion;//最新版本号
|
String? currentUpgradeVersion; //最新版本号
|
||||||
String? currentUpgradeName;//最新固件名
|
String? currentUpgradeName; //最新固件名
|
||||||
String? currentUpgradeUrl;//最新固件名
|
String? currentUpgradeUrl; //最新固件名
|
||||||
|
|
||||||
void startStatusPolling() {
|
void startStatusPolling() {
|
||||||
updateDeviceStatus().then((res) {
|
updateDeviceStatus().then((res) {
|
||||||
@@ -264,7 +264,8 @@ class MHTBlueToothController extends GetControllerEx<MHTBlueToothModel> {
|
|||||||
return ApiResponse(code: -1, msg: "未知错误".tr);
|
return ApiResponse(code: -1, msg: "未知错误".tr);
|
||||||
}
|
}
|
||||||
|
|
||||||
saveHabitData(sleepData) async {
|
Future<bool> saveHabitData(sleepData) async {
|
||||||
|
bool resFlag = false;
|
||||||
String serviceAddress = ServiceConstant.service_address;
|
String serviceAddress = ServiceConstant.service_address;
|
||||||
String serviceName = ServiceConstant.server_service;
|
String serviceName = ServiceConstant.server_service;
|
||||||
String serviceApi = ServiceConstant.user_setting;
|
String serviceApi = ServiceConstant.user_setting;
|
||||||
@@ -281,7 +282,14 @@ class MHTBlueToothController extends GetControllerEx<MHTBlueToothModel> {
|
|||||||
method: MyHttpMethod.put,
|
method: MyHttpMethod.put,
|
||||||
queryUrl: queryUrl,
|
queryUrl: queryUrl,
|
||||||
data: data,
|
data: data,
|
||||||
|
onSuccess: (res) {
|
||||||
|
resFlag = true;
|
||||||
|
},
|
||||||
|
onFailure: (res) {
|
||||||
|
resFlag = false;
|
||||||
|
},
|
||||||
);
|
);
|
||||||
|
return resFlag;
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<Map> loadHabitDataApi(String mac, {int time = 3}) async {
|
Future<Map> loadHabitDataApi(String mac, {int time = 3}) async {
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ import 'package:vbvs_app/controller/user_info_controller.dart';
|
|||||||
import 'package:vbvs_app/model/BleDeviceData.dart';
|
import 'package:vbvs_app/model/BleDeviceData.dart';
|
||||||
import 'package:vbvs_app/pages/device_bind/blueteeth_device_page.dart';
|
import 'package:vbvs_app/pages/device_bind/blueteeth_device_page.dart';
|
||||||
import 'package:vbvs_app/pages/mh_page/component/mht_bind_dialog.dart';
|
import 'package:vbvs_app/pages/mh_page/component/mht_bind_dialog.dart';
|
||||||
|
import 'package:vbvs_app/pages/mh_page/device/controller/mht_bluetooth_controller.dart';
|
||||||
import 'package:vbvs_app/pages/mh_page/device/model/BlueToothDataModel.dart';
|
import 'package:vbvs_app/pages/mh_page/device/model/BlueToothDataModel.dart';
|
||||||
import 'package:EasyDartModule/EasyDartModule.dart' as edm;
|
import 'package:EasyDartModule/EasyDartModule.dart' as edm;
|
||||||
|
|
||||||
@@ -39,6 +40,7 @@ class _MHTWifiPageState extends State<MHTWifiPage> {
|
|||||||
GlobalController globalController = Get.find();
|
GlobalController globalController = Get.find();
|
||||||
UserInfoController userInfoController = Get.find();
|
UserInfoController userInfoController = Get.find();
|
||||||
BlueteethBindController blueteethBindController = Get.find();
|
BlueteethBindController blueteethBindController = Get.find();
|
||||||
|
MHTBlueToothController mhtBlueToothController = Get.find();
|
||||||
PersonController personController = Get.find();
|
PersonController personController = Get.find();
|
||||||
ThemeController themeController = Get.find();
|
ThemeController themeController = Get.find();
|
||||||
var lisObj;
|
var lisObj;
|
||||||
@@ -226,6 +228,7 @@ class _MHTWifiPageState extends State<MHTWifiPage> {
|
|||||||
onBack: () {
|
onBack: () {
|
||||||
// Get.offAllNamed('/mHTBlueteethDevicePage',
|
// Get.offAllNamed('/mHTBlueteethDevicePage',
|
||||||
// arguments: widget.deviceInfo.type);
|
// arguments: widget.deviceInfo.type);
|
||||||
|
blueteethBindController.currentDevice?.disconnect();
|
||||||
Get.back();
|
Get.back();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -928,6 +931,14 @@ class _MHTWifiPageState extends State<MHTWifiPage> {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
wifiStatus = false;
|
wifiStatus = false;
|
||||||
|
if (needSuccess) {
|
||||||
|
haveSuccess = true;
|
||||||
|
TopSlideNotification.show(
|
||||||
|
context,
|
||||||
|
text: "配网失败".tr,
|
||||||
|
textColor: themeController.currentColor.sc9,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
blueteethBindController.wifiStatus.value = wifiStatus ? 1 : 0;
|
blueteethBindController.wifiStatus.value = wifiStatus ? 1 : 0;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@@ -1348,6 +1359,9 @@ class _MHTWifiPageState extends State<MHTWifiPage> {
|
|||||||
await blueteethBindController.currentDevice!.disconnect();
|
await blueteethBindController.currentDevice!.disconnect();
|
||||||
// blueteethBindController.currentDevice = null;
|
// blueteethBindController.currentDevice = null;
|
||||||
}
|
}
|
||||||
|
if (mhtBlueToothController.currentDevice != null) {
|
||||||
|
await mhtBlueToothController.currentDevice!.disconnect();
|
||||||
|
}
|
||||||
DailyLogUtils.writeLog("关闭蓝牙连接成功".tr);
|
DailyLogUtils.writeLog("关闭蓝牙连接成功".tr);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
DailyLogUtils.writeError("关闭蓝牙连接失败: $e");
|
DailyLogUtils.writeError("关闭蓝牙连接失败: $e");
|
||||||
|
|||||||
@@ -891,6 +891,14 @@ class _MHTWifiAfterPageState extends State<MHTWifiAfterPage> {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
wifiStatus = false;
|
wifiStatus = false;
|
||||||
|
if (needSuccess) {
|
||||||
|
haveSuccess = true;
|
||||||
|
TopSlideNotification.show(
|
||||||
|
context,
|
||||||
|
text: "配网失败".tr,
|
||||||
|
textColor: themeController.currentColor.sc9,
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
blueteethBindController.wifiStatus.value = wifiStatus ? 1 : 0;
|
blueteethBindController.wifiStatus.value = wifiStatus ? 1 : 0;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -87,6 +87,11 @@ class _NewHomePageState extends State<NewHomePage> {
|
|||||||
if (AppConstants().ent_type != APPPackageType.MHT.code) {
|
if (AppConstants().ent_type != APPPackageType.MHT.code) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
if (userInfoController.model.login! == null ||
|
||||||
|
userInfoController.model.login! == 0) {
|
||||||
|
//未登录
|
||||||
|
return;
|
||||||
|
}
|
||||||
showTipUpgradeDialog(
|
showTipUpgradeDialog(
|
||||||
context,
|
context,
|
||||||
Column(
|
Column(
|
||||||
|
|||||||
@@ -253,7 +253,7 @@ class _SettingPageState extends State<SettingPage> {
|
|||||||
),
|
),
|
||||||
].divide(SizedBox(width: 22.rpx)),
|
].divide(SizedBox(width: 22.rpx)),
|
||||||
),
|
),
|
||||||
Text('SWES2025.10.28',
|
Text('SWES2025.10.31',
|
||||||
style: TextStyle(
|
style: TextStyle(
|
||||||
color: Colors.white,
|
color: Colors.white,
|
||||||
fontSize: 26.rpx,
|
fontSize: 26.rpx,
|
||||||
|
|||||||
@@ -58,7 +58,7 @@ class WebviewTestController extends GetControllerEx<WebviewTestModel> {
|
|||||||
},
|
},
|
||||||
onLoadResource: (controller, resource) {
|
onLoadResource: (controller, resource) {
|
||||||
// sourceTime = web.requestTimestamp;
|
// sourceTime = web.requestTimestamp;
|
||||||
// ef.log("[请求资源时间]${web.requestTimestamp}");`
|
// ef.log("[请求资源时间]${web.requestTimestamp}");
|
||||||
// _startMonitoringIfNeeded();
|
// _startMonitoringIfNeeded();
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
@@ -216,7 +216,7 @@ class WebviewTestController extends GetControllerEx<WebviewTestModel> {
|
|||||||
ef.log('更新睡眠习惯: $args[0]');
|
ef.log('更新睡眠习惯: $args[0]');
|
||||||
try {
|
try {
|
||||||
MHTBlueToothController blueToothController = Get.find();
|
MHTBlueToothController blueToothController = Get.find();
|
||||||
await blueToothController.saveHabitData(args[0]);
|
return await blueToothController.saveHabitData(args[0]);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
ef.log("[更新睡眠习惯失败]:$e");
|
ef.log("[更新睡眠习惯失败]:$e");
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user