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'; import 'package:vbvs_app/pages/mh_page/searchWidget.dart'; class MHTBlueteethDevicePage extends StatefulWidget { var deviceType; MHTBlueteethDevicePage({super.key, required this.deviceType}); @override State createState() => _MHTBlueteethDevicePageState(); } class _MHTBlueteethDevicePageState extends State { MHTBlueToothController mhtBlueToothController = Get.find(); GlobalController globalController = Get.find(); UserInfoController userInfoController = Get.find(); ThemeController themeController = Get.find(); late FlutterBluePlus flutterBlue; List 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>? _scanSubscription; @override void initState() { super.initState(); mhtBlueToothController.model.blueRawData = []; mhtBlueToothController.model.deviceDataStatus = []; flutterBlue = FlutterBluePlus(); _checkBluetoothPermission(); mhtBlueToothController.startStatusPolling(); mhtBlueToothController.search.value = ""; mhtBlueToothController.currentDeviceMac?.value = ""; } Future _checkBluetoothPermission() async { PermissionStatus bluetoothStatus = await Permission.bluetooth.status; PermissionStatus locationStatus = await Permission.location.status; if (bluetoothStatus.isGranted && locationStatus.isGranted) { _startScanning(); _startPeriodicScan(); } else { _requestBluetoothPermission(); } } Future _requestBluetoothPermission() async { Map 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 { 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()); 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 = []; 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 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: "检索设备", 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; }); } } }