1086 lines
45 KiB
Dart
1086 lines
45 KiB
Dart
import 'dart:async';
|
||
import 'dart:io';
|
||
import 'dart:typed_data';
|
||
|
||
import 'package:ef/ef.dart';
|
||
import 'package:flutter/material.dart';
|
||
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
|
||
import 'package:flutter_svg/svg.dart';
|
||
import 'package:flutterflow_ui/flutterflow_ui.dart';
|
||
import 'package:permission_handler/permission_handler.dart';
|
||
import 'package:vbvs_app/common/color/appConstants.dart';
|
||
import 'package:vbvs_app/common/util/CommonVariables.dart';
|
||
import 'package:vbvs_app/common/util/FitTool.dart';
|
||
import 'package:vbvs_app/common/util/MyUtils.dart';
|
||
import 'package:vbvs_app/component/tool/TopSlideNotification.dart';
|
||
import 'package:vbvs_app/controller/main_bottom/global_controller.dart';
|
||
import 'package:vbvs_app/controller/theme_controller/ThemeController.dart';
|
||
import 'package:vbvs_app/controller/user_info_controller.dart';
|
||
import 'package:vbvs_app/pages/common/selectDialog.dart';
|
||
import 'package:vbvs_app/pages/mh_page/component/mht_bind_dialog.dart';
|
||
import 'package:vbvs_app/pages/mh_page/device/component/DeviceComponentWidget.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/searchWidget.dart';
|
||
|
||
class MHTBlueteethDevicePage extends StatefulWidget {
|
||
var deviceType;
|
||
MHTBlueteethDevicePage({super.key, required this.deviceType});
|
||
|
||
@override
|
||
State<MHTBlueteethDevicePage> createState() => _MHTBlueteethDevicePageState();
|
||
}
|
||
|
||
class _MHTBlueteethDevicePageState extends State<MHTBlueteethDevicePage> {
|
||
MHTBlueToothController mhtBlueToothController = Get.find();
|
||
GlobalController globalController = Get.find();
|
||
UserInfoController userInfoController = Get.find();
|
||
ThemeController themeController = Get.find();
|
||
late FlutterBluePlus flutterBlue;
|
||
List<ScanResult> scanResults = [];
|
||
bool isScanning = false;
|
||
Timer? _timer;
|
||
bool _isDialogShowing = false;
|
||
|
||
var currentConnectedDeviceProp;
|
||
var connectDeviceCurrent = null;
|
||
List bleDevice = [];
|
||
String currentMsg = "寻找设备中...";
|
||
Timer? connectTimer;
|
||
bool isFind = false;
|
||
List bindArrBackup = [];
|
||
List bindArr = ["", "", ""];
|
||
StreamSubscription<List<ScanResult>>? _scanSubscription;
|
||
|
||
@override
|
||
void initState() {
|
||
super.initState();
|
||
mhtBlueToothController.model.blueRawData = [];
|
||
mhtBlueToothController.model.deviceDataStatus = [];
|
||
flutterBlue = FlutterBluePlus();
|
||
_checkBluetoothPermission();
|
||
mhtBlueToothController.startStatusPolling();
|
||
mhtBlueToothController.search.value = "";
|
||
mhtBlueToothController.currentDeviceMac?.value = "";
|
||
}
|
||
|
||
Future<void> _checkBluetoothPermission() async {
|
||
PermissionStatus bluetoothStatus = await Permission.bluetooth.status;
|
||
PermissionStatus locationStatus = await Permission.location.status;
|
||
|
||
if (bluetoothStatus.isGranted && locationStatus.isGranted) {
|
||
_startScanning();
|
||
_startPeriodicScan();
|
||
} else {
|
||
_requestBluetoothPermission();
|
||
}
|
||
}
|
||
|
||
// Future<void> _requestBluetoothPermission() async {
|
||
// // showPermissionInfoDialog(Get.context!, CommonVariables().permissionInfo);
|
||
// // Map<Permission, PermissionStatus> statuses = await [
|
||
// // Permission.bluetoothScan,
|
||
// // Permission.bluetoothConnect,
|
||
// // Permission.location,
|
||
// // ].request();
|
||
// Map<Permission, PermissionStatus> statuses = {};
|
||
// try {
|
||
// // 1️⃣ 先显示自定义提示弹窗
|
||
// showPermissionInfoDialog(Get.context!, CommonVariables().permissionInfo);
|
||
|
||
// // 2️⃣ 等待一小会,让 UI 有时间渲染提示弹窗
|
||
// await Future.delayed(const Duration(milliseconds: 300));
|
||
|
||
// // 3️⃣ 再去申请权限(系统权限弹窗可能会弹出)
|
||
// statuses = await [
|
||
// Permission.bluetoothScan,
|
||
// Permission.bluetoothConnect,
|
||
// Permission.location,
|
||
// ].request();
|
||
|
||
// // 这里你可以检查每个权限的状态
|
||
// ef.log("权限状态: $statuses");
|
||
// } catch (e) {
|
||
// ef.log("申请权限出错: $e");
|
||
// } finally {
|
||
// // 4️⃣ 无论成功失败,最后关闭提示弹窗
|
||
// if (Get.context != null) {
|
||
// Navigator.of(Get.context!, rootNavigator: true).pop();
|
||
// }
|
||
// }
|
||
|
||
// bool allGranted = statuses[Permission.bluetoothScan]?.isGranted == true &&
|
||
// statuses[Permission.bluetoothConnect]?.isGranted == true &&
|
||
// statuses[Permission.location]?.isGranted == true;
|
||
|
||
// if (allGranted) {
|
||
// _startScanning();
|
||
// _startPeriodicScan();
|
||
// } else {
|
||
// _showPermissionDeniedDialog();
|
||
// }
|
||
// }
|
||
|
||
Future<void> _requestBluetoothPermission() async {
|
||
Map<Permission, PermissionStatus> statuses = {};
|
||
bool dialogShown = false; // 标记是否弹过权限提示弹窗
|
||
if (Platform.isIOS) {
|
||
PermissionStatus isBleGranted = await Permission.bluetooth.request();
|
||
print('checkBlePermissions-ios, isBleGranted=$isBleGranted');
|
||
if (isBleGranted.isGranted) {
|
||
// startBluetoothScanning();
|
||
_startScanning();
|
||
_startPeriodicScan();
|
||
} else {
|
||
// showToast("蓝牙开关或蓝牙权限未开启,请开启蓝牙开关与蓝牙权限".tr, closeTime: 7);
|
||
try {
|
||
_startScanning();
|
||
_startPeriodicScan();
|
||
} catch (e) {
|
||
TopSlideNotification.show(context,
|
||
text: "蓝牙权限未开启,请在设置中开启蓝牙权限".tr,
|
||
textColor: themeController.currentColor.sc9);
|
||
}
|
||
}
|
||
} else if (Platform.isAndroid) {
|
||
try {
|
||
// 检查是否已授权
|
||
bool alreadyGranted = await Permission.bluetoothScan.isGranted &&
|
||
await Permission.bluetoothConnect.isGranted &&
|
||
await Permission.location.isGranted;
|
||
|
||
if (!alreadyGranted) {
|
||
// 弹出自定义提示
|
||
showPermissionInfoDialog(
|
||
Get.context!, CommonVariables().bluetoothpermissionInfo);
|
||
dialogShown = true;
|
||
|
||
await Future.delayed(const Duration(milliseconds: 300));
|
||
|
||
// 请求权限
|
||
statuses = await [
|
||
Permission.bluetoothScan,
|
||
Permission.bluetoothConnect,
|
||
Permission.location,
|
||
].request();
|
||
|
||
ef.log("权限状态: $statuses");
|
||
} else {
|
||
statuses = {
|
||
Permission.bluetoothScan: PermissionStatus.granted,
|
||
Permission.bluetoothConnect: PermissionStatus.granted,
|
||
Permission.location: PermissionStatus.granted,
|
||
};
|
||
}
|
||
} catch (e) {
|
||
ef.log("申请权限出错: $e");
|
||
} finally {
|
||
// 只有真的弹过提示才关闭
|
||
if (dialogShown && Get.context != null) {
|
||
Navigator.of(Get.context!, rootNavigator: true).pop();
|
||
}
|
||
}
|
||
bool allGranted = statuses[Permission.bluetoothScan]?.isGranted == true &&
|
||
statuses[Permission.bluetoothConnect]?.isGranted == true &&
|
||
statuses[Permission.location]?.isGranted == true;
|
||
|
||
if (allGranted) {
|
||
_startScanning();
|
||
_startPeriodicScan();
|
||
} else {
|
||
_showPermissionDeniedDialog(context);
|
||
}
|
||
} else {
|
||
TopSlideNotification.show(context,
|
||
text: "当前系统不支持蓝牙,无法使用此功能".tr,
|
||
textColor: themeController.currentColor.sc9);
|
||
}
|
||
}
|
||
|
||
void _showPermissionDeniedDialog(BuildContext context) {
|
||
TopSlideNotification.show(context,
|
||
text: "应用需要蓝牙和位置权限才能扫描设备。请授予权限。".tr,
|
||
textColor: themeController.currentColor.sc9);
|
||
}
|
||
|
||
void _startScanning() async {
|
||
try {
|
||
if (!mounted || isScanning || !mhtBlueToothController.shouldScan.value)
|
||
return;
|
||
|
||
_scanSubscription?.cancel();
|
||
var bluetoothState = await FlutterBluePlus.isOn;
|
||
mhtBlueToothController.model.bluetooth = bluetoothState;
|
||
mhtBlueToothController.updateAll();
|
||
|
||
if (!bluetoothState && !_isDialogShowing) {
|
||
_isDialogShowing = true;
|
||
mhtBlueToothController.model.blueRawData = [];
|
||
mhtBlueToothController.model.deviceDataStatus = [];
|
||
mhtBlueToothController.updateAll();
|
||
await _showBluetoothNotEnabledDialog();
|
||
_isDialogShowing = false;
|
||
return;
|
||
}
|
||
|
||
if (!isScanning) {
|
||
setState(() {
|
||
isScanning = true;
|
||
});
|
||
|
||
await FlutterBluePlus.startScan(timeout: Duration(seconds: 10));
|
||
|
||
// _scanSubscription = FlutterBluePlus.scanResults.listen((results) {
|
||
// if (!mounted) return;
|
||
|
||
// final signalThreshold = mhtBlueToothController.model.singal!;
|
||
// final searchKey =
|
||
// mhtBlueToothController.search.value.trim().toLowerCase();
|
||
|
||
// final filteredResults = results.where((r) {
|
||
// final localName = r.advertisementData.localName;
|
||
// final isTarget = r.rssi > signalThreshold &&
|
||
// isTargetDevice(
|
||
// localName, widget.deviceType['reg'].cast<String>());
|
||
// if (!isTarget) return false;
|
||
// final name = r.advertisementData.advName.toLowerCase();
|
||
|
||
// // String macAddress = r.device.remoteId.str;
|
||
// String macAddress = getDeviceId(r);
|
||
// final mac = macAddress.replaceAll(':', '');
|
||
// final search = searchKey.trim().replaceAll(':', '').toLowerCase();
|
||
|
||
// if (search.isNotEmpty &&
|
||
// !name.contains(search) &&
|
||
// !mac.replaceAll(':', '').toLowerCase().contains(search)) {
|
||
// return false;
|
||
// }
|
||
|
||
// return true;
|
||
// }).map((r) {
|
||
// return BlueToothDataModel.fromScanResult(
|
||
// r, widget.deviceType['type']?.toInt(),
|
||
// bind: false,
|
||
// name: r.advertisementData.localName,
|
||
// mac:getDeviceId(r).replaceAll(':', ''));
|
||
// }).toList();
|
||
|
||
// final currentDevices = mhtBlueToothController.model.blueRawData ?? [];
|
||
// final newDevices = <BlueToothDataModel>[];
|
||
|
||
// for (var newDevice in filteredResults) {
|
||
// // 检查设备是否已存在
|
||
// final existingIndex =
|
||
// currentDevices.indexWhere((d) => d.mac == newDevice.mac);
|
||
|
||
// if (existingIndex >= 0) {
|
||
// // 更新已有设备信息(如信号强度)
|
||
// currentDevices[existingIndex] = newDevice;
|
||
// } else {
|
||
// // 添加新设备
|
||
// newDevices.add(newDevice);
|
||
// }
|
||
// }
|
||
|
||
// setState(() {
|
||
// mhtBlueToothController.model.blueRawData = [
|
||
// ...currentDevices,
|
||
// ...newDevices
|
||
// ];
|
||
// });
|
||
// });
|
||
|
||
_scanSubscription = FlutterBluePlus.scanResults.listen((results) {
|
||
if (!mounted) return;
|
||
|
||
final signalThreshold = mhtBlueToothController.model.singal!;
|
||
final searchKey =
|
||
mhtBlueToothController.search.value.trim().toLowerCase();
|
||
|
||
final filteredResults = results.map((r) {
|
||
Map<String, dynamic> d = {
|
||
"updateTime": DateTime.now().millisecondsSinceEpoch,
|
||
"name": r.advertisementData.localName?.trim() ??
|
||
r.advertisementData.advName?.trim() ??
|
||
r.device.name ??
|
||
"",
|
||
"rssi": r.rssi,
|
||
"device": r.device,
|
||
"connectable": r.advertisementData.connectable
|
||
};
|
||
|
||
// 从 manufacturerData 解析设备唯一 ID
|
||
Map<int, List<int>> m_d = r.advertisementData.manufacturerData;
|
||
String? deviceId;
|
||
|
||
m_d.forEach((key, value) {
|
||
if (value == null || value.isEmpty) return;
|
||
|
||
if (key == 65517) {
|
||
List<int> a = [0, 0, ...value];
|
||
advertisDataFormatter(a, d); // 你原来的处理
|
||
if (d['adData']?['deviceId'] != null)
|
||
deviceId = d['adData']['deviceId'];
|
||
} else if (key == 11125 && value.length == 8) {
|
||
deviceId = ab2str(value.sublist(2, 8)).toUpperCase();
|
||
} else if (value.length == 8 && isQuanShiDevice(d["name"])) {
|
||
deviceId = ab2str(value.sublist(2, 8)).toUpperCase();
|
||
} else if ((value.length == 4 || value.length == 6) &&
|
||
isMHTSWES(d["name"])) {
|
||
List<int> a;
|
||
if (value.length == 4) {
|
||
ByteData bd = ByteData(2);
|
||
bd.setUint16(0, key, Endian.little);
|
||
a = [bd.getUint8(0), bd.getUint8(1), ...value];
|
||
} else {
|
||
a = [...value];
|
||
}
|
||
deviceId = ab2str(a).toUpperCase();
|
||
}
|
||
});
|
||
|
||
d['id'] = deviceId ?? r.device.remoteId.str; // fallback UUID
|
||
return BlueToothDataModel.fromScanResult(
|
||
r,
|
||
widget.deviceType['type']?.toInt(),
|
||
bind: false,
|
||
name: d['name'],
|
||
mac: d['id'],
|
||
);
|
||
}).where((d) {
|
||
// 信号强度过滤
|
||
if (d.scanResult.rssi <= signalThreshold) return false;
|
||
|
||
// 搜索关键字过滤
|
||
if (searchKey.isNotEmpty) {
|
||
String name = d.name.toLowerCase();
|
||
String mac = d.mac.toLowerCase();
|
||
if (!name.contains(searchKey) && !mac.contains(searchKey))
|
||
return false;
|
||
}
|
||
|
||
// 名称过滤规则,必须包含 deviceType['reg'] 列表中的某个字符串
|
||
List<String> regList =
|
||
widget.deviceType['reg']?.cast<String>() ?? [];
|
||
if (regList.isNotEmpty) {
|
||
String lowerName = d.name.toLowerCase();
|
||
bool match =
|
||
regList.any((reg) => lowerName.contains(reg.toLowerCase()));
|
||
if (!match) return false;
|
||
}
|
||
|
||
return true;
|
||
}).toList();
|
||
|
||
final currentDevices = mhtBlueToothController.model.blueRawData ?? [];
|
||
final newDevices = <BlueToothDataModel>[];
|
||
|
||
for (var newDevice in filteredResults) {
|
||
final existingIndex =
|
||
currentDevices.indexWhere((d) => d.mac == newDevice.mac);
|
||
if (existingIndex >= 0) {
|
||
currentDevices[existingIndex] = newDevice;
|
||
} else {
|
||
newDevices.add(newDevice);
|
||
}
|
||
}
|
||
|
||
setState(() {
|
||
mhtBlueToothController.model.blueRawData = [
|
||
...currentDevices,
|
||
...newDevices
|
||
];
|
||
});
|
||
});
|
||
|
||
await Future.delayed(Duration(seconds: 10));
|
||
await FlutterBluePlus.stopScan();
|
||
|
||
if (mounted) {
|
||
setState(() {
|
||
isScanning = false;
|
||
});
|
||
}
|
||
}
|
||
} catch (e) {
|
||
ef.log("$e");
|
||
} finally {
|
||
setState(() {
|
||
isScanning = false;
|
||
});
|
||
}
|
||
}
|
||
|
||
void _startPeriodicScan() {
|
||
_timer = Timer.periodic(Duration(seconds: 3), (timer) {
|
||
if (mhtBlueToothController.shouldScan.value && !isScanning) {
|
||
_removeOldDevices(); // 先清理老旧设备
|
||
_startScanning();
|
||
}
|
||
});
|
||
}
|
||
|
||
void _stopScanning() {
|
||
if (isScanning) {
|
||
FlutterBluePlus.stopScan();
|
||
_scanSubscription?.cancel();
|
||
if (mounted) {
|
||
setState(() {
|
||
isScanning = false;
|
||
});
|
||
}
|
||
}
|
||
}
|
||
|
||
void _stopPeriodicScan() {
|
||
_timer?.cancel();
|
||
}
|
||
|
||
@override
|
||
void dispose() {
|
||
_stopPeriodicScan();
|
||
_stopScanning();
|
||
_scanSubscription?.cancel();
|
||
connectTimer?.cancel();
|
||
mhtBlueToothController.stopStatusPolling();
|
||
mhtBlueToothController.model.blueRawData = [];
|
||
mhtBlueToothController.model.deviceDataStatus = [];
|
||
super.dispose();
|
||
}
|
||
|
||
bool isTargetDevice(String? name, List<String> keywords) {
|
||
if (name == null) return false;
|
||
return keywords.any((k) => name.contains(k));
|
||
}
|
||
|
||
_showBluetoothNotEnabledDialog() async {
|
||
await showTipDialog(
|
||
backgroundColor: Colors.white,
|
||
context,
|
||
Column(
|
||
children: [
|
||
Text(
|
||
"蓝牙未开启".tr,
|
||
style: TextStyle(
|
||
fontSize: AppConstants().title_text_fontSize,
|
||
color: stringToColor("#333333"),
|
||
),
|
||
),
|
||
SizedBox(
|
||
height: 20.rpx,
|
||
),
|
||
Text(
|
||
"请先打开蓝牙在进行设备扫描".tr,
|
||
style: TextStyle(
|
||
fontSize: AppConstants().normal_text_fontSize,
|
||
color: stringToColor("#333333"),
|
||
),
|
||
),
|
||
],
|
||
));
|
||
}
|
||
|
||
@override
|
||
Widget build(BuildContext context) {
|
||
return LayoutBuilder(
|
||
builder: (context, boxConstraints) => GestureDetector(
|
||
// onTap: () => FocusScope.of(context).unfocus(),,
|
||
child: Container(
|
||
decoration: BoxDecoration(
|
||
image: DecorationImage(
|
||
image: AssetImage('assets/images/new_background.png'),
|
||
fit: BoxFit.fill,
|
||
),
|
||
),
|
||
child: Scaffold(
|
||
resizeToAvoidBottomInset: false,
|
||
backgroundColor: Colors.transparent,
|
||
appBar: AppBar(
|
||
iconTheme: IconThemeData(color: themeController.currentColor.sc3),
|
||
backgroundColor: Colors.transparent,
|
||
automaticallyImplyLeading: false,
|
||
titleSpacing: 0,
|
||
title: Container(
|
||
width: double.infinity,
|
||
height: 180.rpx,
|
||
child: Stack(
|
||
alignment: Alignment.center,
|
||
children: [
|
||
Text(
|
||
'添加设备'.tr,
|
||
style: TextStyle(
|
||
fontFamily: 'Readex Pro',
|
||
color: themeController.currentColor.sc3,
|
||
letterSpacing: 0,
|
||
fontSize: 30.rpx,
|
||
),
|
||
),
|
||
Positioned(
|
||
left: 0,
|
||
child: returnIconButtom,
|
||
),
|
||
],
|
||
),
|
||
),
|
||
actions: [],
|
||
centerTitle: false,
|
||
),
|
||
body: SafeArea(
|
||
top: true,
|
||
child: Padding(
|
||
padding: EdgeInsetsDirectional.fromSTEB(30.rpx, 0, 30.rpx, 0),
|
||
child: Column(
|
||
mainAxisSize: MainAxisSize.max,
|
||
children: [
|
||
Expanded(
|
||
child: Column(
|
||
children: [
|
||
Padding(
|
||
padding:
|
||
EdgeInsetsDirectional.fromSTEB(0, 30.rpx, 0, 0),
|
||
child: Container(
|
||
width: double.infinity,
|
||
decoration: BoxDecoration(
|
||
gradient: LinearGradient(
|
||
colors: [
|
||
stringToColor("#003058").withOpacity(0.8),
|
||
stringToColor("#003058").withOpacity(0.8),
|
||
],
|
||
begin: Alignment.topCenter,
|
||
end: Alignment.bottomCenter,
|
||
),
|
||
borderRadius: BorderRadius.circular(20.rpx),
|
||
),
|
||
child: Align(
|
||
alignment: AlignmentDirectional(0, 0),
|
||
child: Padding(
|
||
padding: EdgeInsetsDirectional.fromSTEB(
|
||
0, 30.rpx, 0, 30.rpx),
|
||
child: Obx(() {
|
||
return Text(
|
||
(mhtBlueToothController.model.bluetooth ==
|
||
null ||
|
||
mhtBlueToothController
|
||
.model.bluetooth ==
|
||
false)
|
||
? "等待扫描".tr
|
||
: '扫描中'.tr,
|
||
style: TextStyle(
|
||
fontFamily: 'Inter',
|
||
color: stringToColor("#FFFFFF"),
|
||
fontSize: 26.rpx,
|
||
letterSpacing: 0.0,
|
||
),
|
||
);
|
||
}),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
Container(
|
||
width: double.infinity,
|
||
decoration: BoxDecoration(
|
||
gradient: LinearGradient(
|
||
colors: [
|
||
// stringToColor("FCFCFC"),
|
||
// stringToColor("CEECE3")
|
||
Colors.transparent,
|
||
Colors.transparent,
|
||
],
|
||
begin: Alignment.topCenter,
|
||
end: Alignment.bottomCenter,
|
||
),
|
||
borderRadius: BorderRadius.circular(20.rpx),
|
||
),
|
||
child: Padding(
|
||
padding: EdgeInsetsDirectional.fromSTEB(
|
||
21.rpx, 5.rpx, 21.rpx, 5.rpx),
|
||
child: Row(
|
||
mainAxisSize: MainAxisSize.max,
|
||
children: [
|
||
Text(
|
||
'最小信号强度'.tr,
|
||
style: TextStyle(
|
||
fontFamily: 'Inter',
|
||
color: stringToColor("#FFFFFF"),
|
||
fontSize: 26.rpx,
|
||
letterSpacing: 0.0,
|
||
),
|
||
),
|
||
Expanded(
|
||
child: Obx(() {
|
||
return Slider(
|
||
activeColor: Color(0xFF1FCC9B),
|
||
inactiveColor: Colors.white,
|
||
min: -100,
|
||
max: 50,
|
||
value:
|
||
mhtBlueToothController.model.singal!,
|
||
onChanged: (newValue) {
|
||
newValue = double.parse(
|
||
newValue.toStringAsFixed(0));
|
||
mhtBlueToothController.model.singal =
|
||
newValue;
|
||
_startScanning();
|
||
mhtBlueToothController.updateAll();
|
||
},
|
||
);
|
||
}),
|
||
),
|
||
Obx(() {
|
||
return Text(
|
||
'${mhtBlueToothController.model.singal!.toInt()}',
|
||
style: TextStyle(
|
||
fontFamily: 'Inter',
|
||
color: stringToColor("#FFFFFF"),
|
||
fontSize: 26.rpx,
|
||
letterSpacing: 0.0,
|
||
),
|
||
);
|
||
}),
|
||
].divide(SizedBox(width: 30.rpx)),
|
||
),
|
||
),
|
||
),
|
||
// Container(
|
||
// width: double.infinity,
|
||
// decoration: BoxDecoration(
|
||
// color: themeController.currentColor.sc3,
|
||
// borderRadius: BorderRadius.circular(20.rpx),
|
||
// ),
|
||
// // child: Padding(
|
||
// // padding: EdgeInsetsDirectional.fromSTEB(
|
||
// // 35.rpx, 0, 35.rpx, 0),
|
||
// // child: Row(
|
||
// // mainAxisSize: MainAxisSize.max,
|
||
// // mainAxisAlignment: MainAxisAlignment.spaceBetween,
|
||
// // children: [
|
||
// // Expanded(
|
||
// // child: Row(
|
||
// // mainAxisSize: MainAxisSize.max,
|
||
// // children: [
|
||
// // Padding(
|
||
// // padding: EdgeInsetsDirectional.fromSTEB(
|
||
// // 0, 0.rpx, 0, 0),
|
||
// // child: Container(
|
||
// // width: 25.rpx,
|
||
// // height: 25.rpx,
|
||
// // decoration: BoxDecoration(),
|
||
// // child: SvgPicture.asset(
|
||
// // 'assets/img/icon/query.svg',
|
||
// // fit: BoxFit.cover,
|
||
// // color: stringToColor("#333333"),
|
||
// // ),
|
||
// // ),
|
||
// // ),
|
||
// // Expanded(
|
||
// // child: Container(
|
||
// // width: 100.rpx,
|
||
// // height: 90.rpx,
|
||
// // decoration: BoxDecoration(
|
||
// // color: Colors.white,
|
||
// // ),
|
||
// // child: Align(
|
||
// // alignment:
|
||
// // AlignmentDirectional(-1, 0),
|
||
// // child: TextFormField(
|
||
// // initialValue:
|
||
// // mhtBlueToothController
|
||
// // .search.value,
|
||
// // onChanged: (Value) {
|
||
// // mhtBlueToothController
|
||
// // .search.value = Value;
|
||
// // },
|
||
// // autofocus: false,
|
||
// // obscureText: false,
|
||
// // decoration: InputDecoration(
|
||
// // isDense: true,
|
||
// // labelStyle: TextStyle(
|
||
// // fontFamily: 'Inter',
|
||
// // fontSize: 26.rpx,
|
||
// // letterSpacing: 0.0,
|
||
// // ),
|
||
// // hintText: '检索设备'.tr,
|
||
// // hintStyle: TextStyle(
|
||
// // fontFamily: 'Inter',
|
||
// // fontSize: 26.rpx,
|
||
// // letterSpacing: 0.0,
|
||
// // ),
|
||
// // enabledBorder:
|
||
// // OutlineInputBorder(
|
||
// // borderSide: BorderSide(
|
||
// // color: Color(0x00000000),
|
||
// // width: 1.rpx,
|
||
// // ),
|
||
// // borderRadius:
|
||
// // BorderRadius.circular(
|
||
// // 8.rpx),
|
||
// // ),
|
||
// // focusedBorder:
|
||
// // OutlineInputBorder(
|
||
// // borderSide: BorderSide(
|
||
// // color: Color(0x00000000),
|
||
// // width: 1.rpx,
|
||
// // ),
|
||
// // borderRadius:
|
||
// // BorderRadius.circular(
|
||
// // 8.rpx),
|
||
// // ),
|
||
// // errorBorder: OutlineInputBorder(
|
||
// // borderSide: BorderSide(
|
||
// // color: Colors.red,
|
||
// // width: 1.rpx,
|
||
// // ),
|
||
// // borderRadius:
|
||
// // BorderRadius.circular(
|
||
// // 8.rpx),
|
||
// // ),
|
||
// // focusedErrorBorder:
|
||
// // OutlineInputBorder(
|
||
// // borderSide: BorderSide(
|
||
// // color: Colors.red,
|
||
// // width: 1.rpx,
|
||
// // ),
|
||
// // borderRadius:
|
||
// // BorderRadius.circular(
|
||
// // 8.rpx),
|
||
// // ),
|
||
// // filled: false,
|
||
// // fillColor: themeController
|
||
// // .currentColor.sc22,
|
||
// // ),
|
||
// // style: TextStyle(
|
||
// // fontFamily: 'Inter',
|
||
// // fontSize: 26.rpx,
|
||
// // letterSpacing: 0.0,
|
||
// // ),
|
||
// // cursorColor:
|
||
// // stringToColor("#003058"),
|
||
// // ),
|
||
// // ),
|
||
// // ),
|
||
// // ),
|
||
// // ].divide(SizedBox(width: 6.rpx)),
|
||
// // ),
|
||
// // ),
|
||
// // Padding(
|
||
// // padding: EdgeInsetsDirectional.fromSTEB(
|
||
// // 26.rpx, 0, 0, 0),
|
||
// // child: Row(
|
||
// // mainAxisSize: MainAxisSize.max,
|
||
// // children: [
|
||
// // SizedBox(
|
||
// // height: 50.rpx,
|
||
// // child: VerticalDivider(
|
||
// // thickness: 2.rpx,
|
||
// // color: stringToColor("#333333"),
|
||
// // ),
|
||
// // ),
|
||
// // ClickableContainer(
|
||
// // backgroundColor: Colors.transparent,
|
||
// // highlightColor:
|
||
// // themeController.currentColor.sc4,
|
||
// // borderRadius: 6.rpx,
|
||
// // padding: EdgeInsets.zero,
|
||
// // onTap: () async {
|
||
// // _startScanning();
|
||
// // },
|
||
// // child: Text(
|
||
// // '搜索'.tr,
|
||
// // style: TextStyle(
|
||
// // fontFamily: 'Inter',
|
||
// // fontSize: 30.rpx,
|
||
// // letterSpacing: 0.0,
|
||
// // color: stringToColor("#333333"),
|
||
// // ),
|
||
// // ),
|
||
// // ),
|
||
// // ].divide(SizedBox(width: 26.rpx)),
|
||
// // ),
|
||
// // ),
|
||
// // ],
|
||
// // ),
|
||
// // ),
|
||
// child: Padding(
|
||
// padding:
|
||
// EdgeInsetsDirectional.fromSTEB(0, 10, 0, 23),
|
||
// child: SearchWidget(
|
||
// keyword: mhtBlueToothController.search.value,
|
||
// color: Colors.red,
|
||
// hint: "检索设备",
|
||
// onChange: (d) {
|
||
// mhtBlueToothController.search.value = d;
|
||
// },
|
||
// findCallback: () {
|
||
// // controller.getDeviceList();
|
||
// },
|
||
// ),
|
||
// ),
|
||
// ),
|
||
Padding(
|
||
padding: EdgeInsetsDirectional.fromSTEB(0, 0, 0, 0),
|
||
child: SearchWidget(
|
||
padding: EdgeInsets.all(0),
|
||
keyword: mhtBlueToothController.search.value,
|
||
color: Colors.red,
|
||
hint: "检索设备".tr,
|
||
onChange: (d) {
|
||
mhtBlueToothController.search.value = d;
|
||
},
|
||
findCallback: () {
|
||
_startScanning();
|
||
},
|
||
),
|
||
),
|
||
Padding(
|
||
padding: EdgeInsetsDirectional.fromSTEB(
|
||
0, 60.rpx, 0, 32.rpx),
|
||
child: Container(
|
||
width: double.infinity,
|
||
decoration: BoxDecoration(),
|
||
child: Padding(
|
||
padding: EdgeInsetsDirectional.fromSTEB(
|
||
19.rpx, 0, 0, 0),
|
||
child: Obx(() {
|
||
return Text(
|
||
'匹配出的外围设备'.tr +
|
||
"(${mhtBlueToothController.model.deviceDataStatus!.length})",
|
||
style: TextStyle(
|
||
fontFamily: 'Inter',
|
||
fontSize: 30.rpx,
|
||
letterSpacing: 0.0,
|
||
color: themeController.currentColor.sc3,
|
||
),
|
||
);
|
||
}),
|
||
),
|
||
),
|
||
),
|
||
Obx(() {
|
||
if (mhtBlueToothController
|
||
.model.deviceDataStatus!.isNotEmpty) {
|
||
final sortedList = mhtBlueToothController
|
||
.model.deviceDataStatus!
|
||
.toList()
|
||
..sort((a, b) => b.scanResult.rssi
|
||
.compareTo(a.scanResult.rssi));
|
||
return Expanded(
|
||
child: Container(
|
||
width: double.infinity,
|
||
child: SingleChildScrollView(
|
||
child: Column(
|
||
mainAxisSize: MainAxisSize.max,
|
||
children: [
|
||
...sortedList
|
||
.map((device) {
|
||
return DeviceComponentWidget(
|
||
bleDevice: device,
|
||
deviceType: widget.deviceType,
|
||
);
|
||
})
|
||
.toList()
|
||
.divide(SizedBox(height: 30.rpx))
|
||
.addToEnd(SizedBox(height: 30.rpx)),
|
||
],
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
return Container();
|
||
}),
|
||
].divide(SizedBox(
|
||
height: 30.rpx,
|
||
)),
|
||
)),
|
||
Padding(
|
||
padding:
|
||
EdgeInsetsDirectional.fromSTEB(0, 52.rpx, 0, 30.rpx),
|
||
child: Container(
|
||
width: double.infinity,
|
||
decoration: BoxDecoration(
|
||
borderRadius: BorderRadius.circular(20.rpx),
|
||
border: Border.all(
|
||
color: themeController.currentColor.sc4
|
||
.withOpacity(0.5),
|
||
width: AppConstants().border_width,
|
||
),
|
||
),
|
||
child: Padding(
|
||
padding: EdgeInsetsDirectional.fromSTEB(
|
||
30.rpx, 30.rpx, 30.rpx, 30.rpx),
|
||
child: Row(
|
||
mainAxisSize: MainAxisSize.max,
|
||
crossAxisAlignment: CrossAxisAlignment.start,
|
||
children: [
|
||
Padding(
|
||
padding: EdgeInsetsDirectional.fromSTEB(
|
||
0, 8.rpx, 0, 0),
|
||
child: Container(
|
||
width: 23.rpx,
|
||
height: 23.rpx,
|
||
decoration: BoxDecoration(),
|
||
child: SvgPicture.asset(
|
||
'assets/img/icon/tips.svg',
|
||
fit: BoxFit.cover,
|
||
color: themeController.currentColor.sc4,
|
||
),
|
||
),
|
||
),
|
||
Expanded(
|
||
child: Text(
|
||
'蓝牙绑定提示'.tr,
|
||
style: TextStyle(
|
||
fontFamily: 'Inter',
|
||
color: themeController.currentColor.sc4,
|
||
fontSize: 26.rpx,
|
||
letterSpacing: 0.0,
|
||
),
|
||
),
|
||
),
|
||
].divide(SizedBox(width: 23.rpx)),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
].divide(SizedBox(height: 30.rpx)),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
),
|
||
);
|
||
}
|
||
|
||
void _removeOldDevices() {
|
||
final now = DateTime.now();
|
||
final currentDevices = mhtBlueToothController.model.blueRawData ?? [];
|
||
|
||
// 移除30秒内未出现的设备
|
||
final updatedDevices = currentDevices.where((device) {
|
||
return now.difference(device.lastSeen) < Duration(seconds: 30);
|
||
}).toList();
|
||
|
||
if (updatedDevices.length != currentDevices.length) {
|
||
setState(() {
|
||
mhtBlueToothController.model.blueRawData = updatedDevices;
|
||
});
|
||
}
|
||
}
|
||
|
||
String getDeviceId(ScanResult result) {
|
||
// Android:remoteId 就是 MAC
|
||
if (Platform.isAndroid) {
|
||
return result.device.remoteId.str.replaceAll(':', '').toUpperCase();
|
||
}
|
||
|
||
// iOS:尝试从 manufacturerData 里解析
|
||
Map<int, List<int>> mData = result.advertisementData.manufacturerData;
|
||
for (var key in mData.keys) {
|
||
List<int>? bytes = mData[key];
|
||
if (bytes != null && bytes.length == 6) {
|
||
// 假设这 6 个字节就是 MAC
|
||
return bytes
|
||
.map((b) => b.toRadixString(16).padLeft(2, '0'))
|
||
.join('')
|
||
.toUpperCase();
|
||
}
|
||
}
|
||
return result.device.remoteId.str.toUpperCase();
|
||
}
|
||
}
|
||
|
||
void advertisDataFormatter(var a, item) {
|
||
Map<String, dynamic> obj = {};
|
||
try {
|
||
if (a[2] == 1) {
|
||
obj['sn'] = a[3];
|
||
obj['deviceId'] = ab2str(a.sublist(4, 10)).toUpperCase();
|
||
obj['b'] = a[10];
|
||
obj['h'] = a[11];
|
||
obj['t'] = a[12];
|
||
item['adData'] = obj;
|
||
} else if (a[2] == 2) {
|
||
obj['sn'] = a[3];
|
||
obj['deviceId'] = ab2str(a.sublist(4, 10)).toUpperCase();
|
||
obj['b'] = a[10];
|
||
obj['h'] = a[11];
|
||
obj['t'] = a[12];
|
||
obj['net'] = (a[13] & 1) == 1 ? '在线' : '离线';
|
||
obj['flag'] = (a[13] & 2) == 2 ? '异常' : '正常';
|
||
ByteData byteData = ByteData.sublistView(
|
||
Uint8List.fromList(a.sublist(14, 18).reversed.toList()));
|
||
obj['version'] = byteData.getUint32(0);
|
||
item['adData'] = obj;
|
||
} else if (a[2] == 3) {
|
||
List<String> otherstr = [];
|
||
obj['sn'] = a[3];
|
||
obj['deviceId'] = ab2str(a.sublist(4, 10)).toUpperCase();
|
||
obj['b'] = a[10];
|
||
obj['h'] = a[11];
|
||
obj['t'] = a[12];
|
||
obj['net'] = (a[13] & 1) == 1 ? '在线' : '离线';
|
||
obj['flag'] = (a[13] & 2) == 2 ? '异常' : '正常';
|
||
|
||
if ((a[13] & 4) == 4) {
|
||
otherstr.add('呼吸暂停');
|
||
}
|
||
|
||
if ((a[13] & 8) == 8 && (a[13] & 1) == 1) {
|
||
obj['isbed'] = '在床';
|
||
} else {
|
||
obj['isbed'] = '离床';
|
||
}
|
||
|
||
if ((a[13] & 16) == 16) {
|
||
otherstr.add('授权过期');
|
||
}
|
||
|
||
if ((a[13] & 64) == 64) {
|
||
otherstr.add('设备休眠');
|
||
}
|
||
|
||
obj['other'] = otherstr.join('、');
|
||
|
||
ByteData byteData = ByteData.sublistView(
|
||
Uint8List.fromList(a.sublist(14, 18).reversed.toList()));
|
||
obj['version'] = byteData.getUint32(0);
|
||
|
||
ByteData qsnData =
|
||
ByteData.sublistView(Uint8List.fromList(a.sublist(17, 19)));
|
||
obj['qsn'] = qsnData.getUint16(0) * 256 + obj['sn'];
|
||
|
||
item['adData'] = obj;
|
||
} else if (a.length > 17) {
|
||
obj['sn'] = a[3];
|
||
obj['deviceId'] = ab2str(a.sublist(4, 10)).toUpperCase();
|
||
obj['b'] = a[10];
|
||
obj['h'] = a[11];
|
||
obj['t'] = a[12];
|
||
obj['net'] = (a[13] & 1) == 1 ? '在线' : '离线';
|
||
obj['flag'] = (a[13] & 2) == 2 ? '异常' : '正常';
|
||
|
||
ByteData byteData = ByteData.sublistView(
|
||
Uint8List.fromList(a.sublist(14, 18).reversed.toList()));
|
||
obj['version'] = byteData.getUint32(0);
|
||
|
||
item['adData'] = obj;
|
||
}
|
||
} catch (e) {
|
||
print(e);
|
||
}
|
||
}
|
||
|
||
bool isQuanShiDevice(name) {
|
||
return "$name".contains("S4-ZM-M94-4") ||
|
||
"$name".contains("S4-ZM-N94-4") ||
|
||
"$name".contains("MHT-SWES-D");
|
||
}
|
||
|
||
bool isMHTSWES(name) {
|
||
return "$name".contains("MHT-") ||
|
||
"$name".contains("MHT-SWES-M") ||
|
||
"$name".contains("MHT-SWES-S");
|
||
}
|