Files
tuiche/lib/pages/mh_page/device/mht_blueteeth_device_page.dart
2025-07-02 14:33:08 +08:00

733 lines
31 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import 'dart:async';
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/FitTool.dart';
import 'package:vbvs_app/common/util/MyUtils.dart';
import 'package:vbvs_app/component/tool/ClickableContainer.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/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';
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 {
Map<Permission, PermissionStatus> statuses = await [
Permission.bluetoothScan,
Permission.bluetoothConnect,
Permission.location,
].request();
bool allGranted = statuses[Permission.bluetoothScan]?.isGranted == true &&
statuses[Permission.bluetoothConnect]?.isGranted == true &&
statuses[Permission.location]?.isGranted == true;
if (allGranted) {
_startScanning();
_startPeriodicScan();
} else {
_showPermissionDeniedDialog();
}
}
void _showPermissionDeniedDialog() {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text("权限提示".tr),
content: Text("应用需要蓝牙和位置权限才能扫描设备。请授予权限。".tr),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text("确定".tr),
),
],
);
},
);
}
void _startScanning() async {
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;
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: r.device.remoteId.str.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
];
});
});
await Future.delayed(Duration(seconds: 10));
await FlutterBluePlus.stopScan();
if (mounted) {
setState(() {
isScanning = false;
});
}
}
}
void _startPeriodicScan() {
_timer = Timer.periodic(Duration(seconds: 10), (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("FCFCFC"),
stringToColor("CEECE3")
],
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("#003058"),
fontSize: 26.rpx,
letterSpacing: 0.0,
),
);
}),
),
),
),
),
Container(
width: double.infinity,
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
stringToColor("FCFCFC"),
stringToColor("CEECE3")
],
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("#003058"),
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("#003058"),
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)),
),
),
],
),
),
),
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;
});
}
}
}