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 data; MHTBlueteethDevicePage({super.key, required this.data}); @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 { 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.data['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.data['type']?.toInt(), bind: false, name: r.advertisementData.localName, mac: r.device.remoteId.str.replaceAll(':', '')); }).toList(); setState(() { mhtBlueToothController.model.blueRawData = filteredResults; }); }); 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) { _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("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, ); }) .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)), ), ), ), ), ), ), ); } }