From fa8c8261d05d831e61c0f533f859cea2e526d119 Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Thu, 3 Jul 2025 19:36:40 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E8=AE=BE=E5=A4=87=E8=A7=A3?= =?UTF-8?q?=E7=BB=91=E9=80=BB=E8=BE=91=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/core/themes/language/lang/en_us.dart | 2 + lib/core/themes/language/lang/zh_cn.dart | 2 + lib/data/models/user_Info.dart | 6 + lib/data/services/network/api.dart | 9 + .../devices/bindings/devices_binding.dart | 11 + .../controllers/devices_controller.dart | 61 ++++ lib/modules/devices/views/devices_view.dart | 290 ++++++++++++++++++ .../controllers/pairing_controller.dart | 149 +++++---- lib/modules/pairing/views/pairing_view.dart | 47 ++- .../pairing/views/pairing_view_new.dart | 20 +- lib/modules/settings/views/settings_view.dart | 8 +- lib/routes/app_pages.dart | 7 + lib/routes/app_routes.dart | 3 + .../yunqiinnovation/ble_service/BleService.kt | 2 + .../ble_service/BleServicePlugin.kt | 4 + 15 files changed, 562 insertions(+), 59 deletions(-) create mode 100644 lib/modules/devices/bindings/devices_binding.dart create mode 100644 lib/modules/devices/controllers/devices_controller.dart create mode 100644 lib/modules/devices/views/devices_view.dart diff --git a/lib/core/themes/language/lang/en_us.dart b/lib/core/themes/language/lang/en_us.dart index a767d5d7d..e4c912e19 100644 --- a/lib/core/themes/language/lang/en_us.dart +++ b/lib/core/themes/language/lang/en_us.dart @@ -90,4 +90,6 @@ const Map enUS = { "goToBind": "Unbind", "scanQrCode": "Scan the QR code", "endRecording": "Do you want to end the recording?", + "earphone": "Earphone", + "voicerecorder": "VoiceRecorder" }; diff --git a/lib/core/themes/language/lang/zh_cn.dart b/lib/core/themes/language/lang/zh_cn.dart index 07806c5cb..604f31448 100644 --- a/lib/core/themes/language/lang/zh_cn.dart +++ b/lib/core/themes/language/lang/zh_cn.dart @@ -88,4 +88,6 @@ const Map zhCN = { "goToBind": "去绑定", "scanQrCode": "扫描二维码", "endRecording": "你想结束录音吗?", + "earphone": "耳机", + "voicerecorder": "录音笔" }; diff --git a/lib/data/models/user_Info.dart b/lib/data/models/user_Info.dart index 63f6d60be..c5be98057 100644 --- a/lib/data/models/user_Info.dart +++ b/lib/data/models/user_Info.dart @@ -21,6 +21,7 @@ class User { String phonemac; String password; int viplv = 0; //添加会员等级字段,0表示普通用户 + String earphonename; String earphonemac; String earphoneauthcode; // final Int64 lastbettime; @@ -41,6 +42,7 @@ class User { required this.phonemac, required this.password, required this.viplv, // 添加默认值 + required this.earphonename, required this.earphonemac, required this.earphoneauthcode, // required this.lastbettime, @@ -70,6 +72,7 @@ class User { phonemac: (userData?['phonemac'] as String?) ?? '', password: (userData?['password'] as String?) ?? '', viplv: (userData?['viplv'] as int?) ?? 0, // 提供默认值 + earphonename: (userData?['earphonename'] as String?) ?? '', earphonemac: (userData?['earphonemac'] as String?) ?? '', earphoneauthcode: (userData?['earphoneauthcode'] as String?) ?? '', // lastbettime: _parseTimestamp(userData['lastbettime']), @@ -97,6 +100,7 @@ class User { password: (userData?['password'] as String?) ?? '', viplv: (userData?['viplv'] as int?) ?? 0, // 提供默认值 + earphonename: (userData?['earphonename'] as String?) ?? '', earphonemac: (userData?['earphonemac'] as String?) ?? '', earphoneauthcode: (userData?['earphoneauthcode'] as String?) ?? '', // lastbettime: _parseTimestamp(userData['lastbettime']), @@ -121,6 +125,8 @@ class User { 'phonemac': phonemac, 'password': password, 'viplv': viplv, + + 'earphonename': earphonename, 'earphonemac': earphonemac, 'earphoneauthcode': earphoneauthcode, diff --git a/lib/data/services/network/api.dart b/lib/data/services/network/api.dart index bed184ddc..0c7235c3b 100644 --- a/lib/data/services/network/api.dart +++ b/lib/data/services/network/api.dart @@ -136,6 +136,15 @@ class Api { ); } + //解绑定设备 + static unbindearphone([params]) { + return DioManager().request( + NWMethod.post, + '/api/home/user_unbindearphone', + params: params, + ); + } + //绑定设备 static bindauthcode([params]) { return DioManager().request( diff --git a/lib/modules/devices/bindings/devices_binding.dart b/lib/modules/devices/bindings/devices_binding.dart new file mode 100644 index 000000000..8fb603089 --- /dev/null +++ b/lib/modules/devices/bindings/devices_binding.dart @@ -0,0 +1,11 @@ +import 'package:deep_voice/modules/devices/controllers/devices_controller.dart'; +import 'package:get/get.dart'; + +class DevicesBinding extends Bindings { + @override + void dependencies() { + Get.lazyPut( + () => DevicesController(), + ); + } +} diff --git a/lib/modules/devices/controllers/devices_controller.dart b/lib/modules/devices/controllers/devices_controller.dart new file mode 100644 index 000000000..2ca2e8f47 --- /dev/null +++ b/lib/modules/devices/controllers/devices_controller.dart @@ -0,0 +1,61 @@ +import 'package:deep_voice/core/utils/logger.dart'; +import 'package:deep_voice/data/models/user_Info.dart'; +import 'package:deep_voice/data/services/ble_manager.dart'; +import 'package:deep_voice/data/services/network/api.dart'; +import 'package:deep_voice/routes/app_routes.dart'; +import 'package:get/get.dart'; +import 'package:get/get_rx/get_rx.dart'; +import 'package:get/get_state_manager/src/simple/get_controllers.dart'; + +/* +设备管理 +*/ +class DevicesController extends GetxController { + late final BleManager _bleManager; + //耳机绑定 + final isearphone = false.obs; + final earphoneName = "".obs; + final earphoneMac = "".obs; + final earphoneCode = "".obs; + + //录音笔 + final isvoicepen = false.obs; + final voicepenName = "".obs; + final voicepenMac = "".obs; + + @override + void onInit() { + super.onInit(); + _bleManager = Get.find(); + earphoneName.value = User.instance.earphonename; + earphoneMac.value = User.instance.earphonemac; + earphoneCode.value = User.instance.earphoneauthcode; + if (earphoneCode.value != "") { + isearphone.value = true; + } + } + + @override + void onClose() { + super.onClose(); + } + + void unbindearphone() async { + final response = await Api.unbindearphone({ + "code": earphoneCode.value, + "earphonname": earphoneMac.value, + }); + if (response != null) { + User.instance.earphonename = ""; + User.instance.earphonemac = ""; + User.instance.earphoneauthcode = ""; + isearphone.value = false; + _bleManager.clearPairedDevices(); + _bleManager.disconnect(); + Get.toNamed(Routes.pairing); + return; + } else { + return; + } + } +} diff --git a/lib/modules/devices/views/devices_view.dart b/lib/modules/devices/views/devices_view.dart new file mode 100644 index 000000000..d77b08253 --- /dev/null +++ b/lib/modules/devices/views/devices_view.dart @@ -0,0 +1,290 @@ +import 'dart:ui'; + +import 'package:deep_voice/modules/devices/controllers/devices_controller.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:get/get.dart'; + +class DevicesView extends GetView { + const DevicesView({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + // 直接使用 Theme.of(context) 获取当前主题 + final isDarkMode = Theme.of(context).brightness == Brightness.dark; + + return Scaffold( + backgroundColor: isDarkMode ? Colors.black : Colors.grey[50], + appBar: AppBar( + automaticallyImplyLeading: false, + centerTitle: true, + elevation: 0, + backgroundColor: isDarkMode ? Colors.black : Colors.white, + leading: IconButton( + icon: Icon( + Icons.arrow_back_ios, + size: 20.sp, + color: isDarkMode ? Colors.white : Colors.black, + ), + onPressed: () { + Get.back(); // 正常返回 + }, + ), + title: Text( + '设备管理'.tr, + style: TextStyle( + fontSize: 20.sp, + color: isDarkMode ? Colors.white : Colors.black, + ), + ), + ), + body: ListView( + padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 16.h), + children: [ + _buildEarphoneCard(isDarkMode), + SizedBox(height: 20.h), + _buildVoiceRecorderCard(isDarkMode), + SizedBox(height: 20.h), + ], + ), + ); + } + + // 耳机 + Widget _buildEarphoneCard(bool isDarkMode) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.only(left: 4.w, bottom: 8.h), + child: Text( + 'earphone'.tr, // AI设置 + style: TextStyle( + fontSize: 12.sp, + fontWeight: FontWeight.w500, + color: isDarkMode ? Colors.blue[400] : Colors.blue[600], + letterSpacing: 1, + ), + ), + ), + Container( + decoration: BoxDecoration( + color: isDarkMode ? Colors.white.withOpacity(0.05) : Colors.white, + borderRadius: BorderRadius.circular(16.r), + boxShadow: [ + BoxShadow( + color: isDarkMode + ? Colors.black.withOpacity(0.2) + : Colors.black.withOpacity(0.05), + blurRadius: 10, + offset: const Offset(0, 2), + ), + ], + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(16.r), + child: BackdropFilter( + filter: isDarkMode + ? ImageFilter.blur(sigmaX: 5, sigmaY: 5) + : ImageFilter.blur(sigmaX: 0, sigmaY: 0), + child: Column( + children: [ + Obx( + () => + // 耳机列表 + _buildSimpleNavigationSetting( + title: controller.isearphone.value + ? controller.earphoneName.value + : "未绑定", + subtitle: controller.isearphone.value + ? controller.earphoneMac.value + : "还未绑定设备", + icon: Icons.headphones, + iconBgColor: isDarkMode + ? const Color.fromARGB(255, 255, 255, 255)! + .withOpacity(0.3) + : const Color.fromARGB(255, 0, 0, 0)!, + iconColor: isDarkMode + ? const Color.fromARGB(255, 0, 0, 0)! + : const Color.fromARGB(255, 255, 255, 255)!, + titleColor: isDarkMode ? Colors.white : null, + subtitleColor: isDarkMode ? Colors.white70 : null, + isDarkMode: isDarkMode, + showbutt: controller.isearphone.value, + onPressed: () { + controller.unbindearphone(); + }, + ), + ) + ], + ), + ), + ), + ), + ], + ); + } + + // 录音笔 + Widget _buildVoiceRecorderCard(bool isDarkMode) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.only(left: 4.w, bottom: 8.h), + child: Text( + 'voicerecorder'.tr, //AI设置 + style: TextStyle( + fontSize: 12.sp, + fontWeight: FontWeight.w500, + color: isDarkMode ? Colors.blue[400] : Colors.blue[600], + letterSpacing: 1, + ), + ), + ), + Container( + decoration: BoxDecoration( + color: isDarkMode ? Colors.white.withOpacity(0.05) : Colors.white, + borderRadius: BorderRadius.circular(16.r), + boxShadow: [ + BoxShadow( + color: isDarkMode + ? Colors.black.withOpacity(0.2) + : Colors.black.withOpacity(0.05), + blurRadius: 10, + offset: const Offset(0, 2), + ), + ], + ), + child: ClipRRect( + borderRadius: BorderRadius.circular(16.r), + child: BackdropFilter( + filter: isDarkMode + ? ImageFilter.blur(sigmaX: 5, sigmaY: 5) + : ImageFilter.blur(sigmaX: 0, sigmaY: 0), + child: Column( + children: [ + // 实时对话入口 + Obx( + () => _buildSimpleNavigationSetting( + title: '未绑定', + subtitle: '还未绑定设备', + icon: Icons.edit, + iconBgColor: isDarkMode + ? const Color.fromARGB(255, 255, 255, 255)! + .withOpacity(0.3) + : const Color.fromARGB(255, 0, 0, 0)!, + iconColor: isDarkMode + ? const Color.fromARGB(255, 0, 0, 0)! + : const Color.fromARGB(255, 255, 255, 255)!, + titleColor: isDarkMode ? Colors.white : null, + subtitleColor: isDarkMode ? Colors.white70 : null, + isDarkMode: isDarkMode, + showbutt: controller.isvoicepen.value, + onPressed: () {}, + ), + ) + ], + ), + ), + ), + ), + ], + ); + } + + // 简单导航设置项 + Widget _buildSimpleNavigationSetting({ + required String title, + String? subtitle, + required IconData icon, + required Color iconBgColor, + required Color iconColor, + Color? titleColor, + Color? subtitleColor, + required bool isDarkMode, + required bool showbutt, + required VoidCallback onPressed, + }) { + return InkWell( + child: Padding( + padding: EdgeInsets.all(16.w), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Row( + children: [ + Container( + width: 32.w, + height: 32.w, + decoration: BoxDecoration( + color: iconBgColor, + borderRadius: BorderRadius.circular(16.r), + ), + child: Icon( + icon, + size: 16.w, + color: iconColor, + ), + ), + SizedBox(width: 12.w), + Flexible( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + title, + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w500, + color: titleColor ?? + (isDarkMode ? Colors.white : Colors.black87), + ), + overflow: TextOverflow.ellipsis, + ), + if (subtitle != null) SizedBox(height: 2.h), + if (subtitle != null) + Text( + subtitle, + style: TextStyle( + fontSize: 12.sp, + color: subtitleColor ?? + (isDarkMode + ? Colors.white70 + : Colors.black54), + ), + overflow: TextOverflow.ellipsis, + ), + ], + ), + ), + ], + ), + ), + if (showbutt) + Container( + width: 74.w, + height: 40.w, + child: ElevatedButton( + style: ElevatedButton.styleFrom( + backgroundColor: Colors.blue, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12), + ), + padding: EdgeInsets.symmetric(horizontal: 2, vertical: 2), + ), + onPressed: onPressed, + child: const Text( + '解绑', + style: TextStyle( + color: Colors.white, + ), + ), + )), + ], + ), + ), + ); + } +} diff --git a/lib/modules/pairing/controllers/pairing_controller.dart b/lib/modules/pairing/controllers/pairing_controller.dart index e06a8ed5a..f4f3d602e 100644 --- a/lib/modules/pairing/controllers/pairing_controller.dart +++ b/lib/modules/pairing/controllers/pairing_controller.dart @@ -2,6 +2,7 @@ import 'dart:convert'; import 'dart:typed_data'; import 'package:app_settings/app_settings.dart'; +import 'package:deep_voice/data/models/appconfig.dart'; import 'package:deep_voice/data/models/user_Info.dart'; import 'package:deep_voice/data/services/network/api.dart'; import 'package:flutter/material.dart'; @@ -21,6 +22,7 @@ enum PairingState { checking, // 检查中(蓝牙、权限等) waitingClassic, // 等待经典蓝牙连接 verifying, // 验证设备 + binding, //确认绑定设备 manualbinding, //手动绑定设备 associating, // 正在进行BLE设备关联 waitingBle, //等待BLE连接 @@ -61,6 +63,8 @@ class PairingController extends GetxController { final isLoading = false.obs; final showConnectButton = false.obs; final showRetryButton = false.obs; + final showBindButton = false.obs; + final showRetryChectButton = false.obs; final showAutoCodeInput = false.obs; final showSkipButton = false.obs; // 增加临时跳过绑定按钮状态 @@ -68,6 +72,7 @@ class PairingController extends GetxController { final connectButtonText = '连接设备'.obs; final retryButtonText = '重试'.obs; + final deviceCode = ''.obs; // ========== 监听器管理 ========== final List _workers = []; @@ -251,7 +256,8 @@ class PairingController extends GetxController { showSkipBindingButton.value = false; // 重置临时跳过绑定按钮 showSkipButton.value = false; showAutoCodeInput.value = false; - + showBindButton.value = false; + showRetryChectButton.value = false; switch (state) { case PairingState.idle: statusText.value = '准备开始配对'; @@ -266,6 +272,7 @@ class PairingController extends GetxController { statusText.value = '请确保耳机已开启并进入配对模式'; showConnectButton.value = true; showSkipButton.value = true; + connectButtonText.value = '前往蓝牙设置'; break; @@ -273,7 +280,12 @@ class PairingController extends GetxController { statusText.value = '正在验证设备...'; isLoading.value = true; break; - + case PairingState.binding: + statusText.value = '确认绑定'; + isLoading.value = true; + showBindButton.value = true; + showRetryChectButton.value = true; + break; case PairingState.associating: statusText.value = '正在关联设备...'; isLoading.value = true; @@ -418,39 +430,27 @@ class PairingController extends GetxController { Future _verifyDevice(BluetoothDevice device) async { try { Logger.i(_tag, '开始验证设备: ${device.name} (${device.address})'); - // TODO: 实现API验证逻辑 - // 暂时模拟验证通过 - // await Future.delayed(const Duration(seconds: 2));; - if (await authbindingDevice(device)) { - Logger.i(_tag, '设备验证通过'); - _verifiedDeviceAddress = device.address; - // 检查是否已有BLE关联 - final pairedMacAddress = await _bleManager.getPairedMacAddress(); - Logger.i(_tag, '已关联的MAC地址: $pairedMacAddress'); - - if (pairedMacAddress != null && - pairedMacAddress.toUpperCase() == device.address.toUpperCase()) { - // 已有关联,检查BLE连接状态 - Logger.i(_tag, '设备已关联,检查BLE连接状态'); - final currentBleState = await _bleManager.getConnectionState(); - Logger.i(_tag, '验证时BLE状态: $currentBleState'); - - if (currentBleState == 2) { - // BleConnectionState.connected - Logger.i(_tag, 'BLE已连接,直接完成配对'); - _transition(PairingState.completed); - } else { - Logger.i(_tag, 'BLE未连接,等待BLE连接'); - _transition(PairingState.waitingBle); - } + final result = await authbindingDevice(device); + if (result == 2) { + //验证失败 进入手动授权 + await connectble(device); + } else if (result == 1) { + //跳转绑定界面 + //验证失败 进入手动授权 + if (currentDevice != null) { + _transition(PairingState.binding); } else { - // 没有关联或关联的是其他设备,需要进行关联 - Logger.i(_tag, '需要进行BLE设备关联'); - _transition(PairingState.associating); + _transition(PairingState.checking); + _checkCurrentStatus(); } } else { - //验证失败 进入手动授权 - _transition(PairingState.manualbinding); + final authcode_open = AppConfig.env("APP_AUTHCODE_OPEN") ?? ""; + if (authcode_open == "true") { + //验证失败 进入手动授权 + _transition(PairingState.manualbinding); + } else { + _setError(ErrorType.deviceNotMatch, '请连接 DeepSound 设备'); + } } } catch (e) { Logger.e(_tag, '设备验证异常: $e'); @@ -458,6 +458,37 @@ class PairingController extends GetxController { } } + //连接ble + Future connectble(BluetoothDevice device) async { + Logger.i(_tag, '设备验证通过'); + _verifiedDeviceAddress = device.address; + // 检查是否已有BLE关联 + final pairedMacAddress = await _bleManager.getPairedMacAddress(); + Logger.i(_tag, '已关联的MAC地址: $pairedMacAddress'); + + if (pairedMacAddress != null && + pairedMacAddress.toUpperCase() == device.address.toUpperCase()) { + // 已有关联,检查BLE连接状态 + Logger.i(_tag, '设备已关联,检查BLE连接状态'); + final currentBleState = await _bleManager.getConnectionState(); + Logger.i(_tag, '验证时BLE状态: $currentBleState'); + + if (currentBleState == 2) { + // BleConnectionState.connected + Logger.i(_tag, 'BLE已连接,直接完成配对'); + _transition(PairingState.completed); + } else { + Logger.i(_tag, 'BLE未连接,等待BLE连接'); + _transition(PairingState.waitingBle); + } + } else { + // 没有关联或关联的是其他设备,需要进行关联 + Logger.i(_tag, '需要进行BLE设备关联'); + _transition(PairingState.associating); + } + return; + } + /// 开始设备关联 void _startDeviceAssociation() async { try { @@ -524,18 +555,19 @@ class PairingController extends GetxController { } } - //自动绑定设备 - Future authbindingDevice(BluetoothDevice device) async { + //自动绑定设备 0 未合作商 1 合作商 2,已绑定 + Future authbindingDevice(BluetoothDevice device) async { if (User.instance.earphonemac == device.address) { // 已经绑定 - return true; + return 2; } // 检查设备名称是否包含 DeepSound 或 DeapSound - final deviceName = device.name ?? ''; + final deviceName = device.name; print('获取到 deviceName: $deviceName'); if (deviceName.contains('DeepSound') || deviceName.contains('DeapSound')) { await _bleManager.startScan(); + await Future.delayed(const Duration(seconds: 1)); // 尝试最多读取 3 次设备信息,每次间隔 1 秒 Map? deviceinfo; for (int i = 0; i < 3; i++) { @@ -553,27 +585,38 @@ class PairingController extends GetxController { print('获取到 code: $code'); // 安全检查长度 if (code != "") { - final response = await Api.bindearphone({ - "code": code, - "earphonemac": device.address, - }); - Logger.i(_tag, '手动授权结果 ${response}'); - if (response != null) { - Logger.i(_tag, '设备自动绑定成功'); - User.instance.earphonemac = device.address ?? ''; - return true; - } else { - Logger.e(_tag, '手动绑定失败!'); - return false; - } + deviceCode.value = code; + return 1; } else { - return false; + return 0; } } - return false; + return 0; // 调用BLE管理器的自动绑定方法 } else { - return false; + return 0; + } + } + + //确认绑定设备 + void confirmBindDevice() async { + final response = await Api.bindearphone({ + "code": deviceCode.value, + "earphonname": currentDevice?.name, + "earphonemac": currentDevice?.address, + }); + Logger.i(_tag, '手动授权结果 ${response}'); + if (response != null) { + Logger.i(_tag, '绑定设备成功'); + User.instance.earphonemac = currentDevice?.address ?? ''; + User.instance.earphonename = currentDevice?.name ?? ''; + User.instance.earphoneauthcode = deviceCode.value; + _transition(PairingState.verifying); + _verifyDevice(_currentClassicDevice!); + return; + } else { + Logger.e(_tag, '绑定设备失败!'); + return; } } @@ -605,6 +648,8 @@ class PairingController extends GetxController { Logger.i(_tag, '手动授权结果 ${response}'); if (response != null) { User.instance.earphonemac = currentDevice?.address ?? ''; + User.instance.earphonename = currentDevice?.name ?? ''; + User.instance.earphoneauthcode = codeInputController.text.trim(); _transition(PairingState.verifying); _verifyDevice(_currentClassicDevice!); } else { diff --git a/lib/modules/pairing/views/pairing_view.dart b/lib/modules/pairing/views/pairing_view.dart index 9594f3760..98c0da59d 100644 --- a/lib/modules/pairing/views/pairing_view.dart +++ b/lib/modules/pairing/views/pairing_view.dart @@ -290,6 +290,7 @@ class PairingView extends GetView { // 当前连接设备(有设备时才显示) if (device != null && (state == PairingState.verifying || + state == PairingState.binding || state == PairingState.waitingBle || state == PairingState.completed)) ...[ SizedBox(height: 24.h), @@ -311,6 +312,8 @@ class PairingView extends GetView { return '请先通过系统蓝牙连接DeapSound设备'; case PairingState.verifying: return '正在验证设备...'; + case PairingState.binding: + return '设备绑定确认...'; case PairingState.associating: return '正在建立设备关联...'; case PairingState.waitingBle: @@ -434,6 +437,8 @@ class PairingView extends GetView { return Obx(() { final showConnectButton = controller.showConnectButton.value; final showRetryButton = controller.showRetryButton.value; + final showBindButton = controller.showBindButton.value; + final showRetryChectButton = controller.showRetryChectButton.value; final showSkipButton = controller.showSkipButton.value; final showAutoCodeInput = controller.showAutoCodeInput.value; final showSkipBindingButton = controller.showSkipBindingButton.value; @@ -470,7 +475,28 @@ class PairingView extends GetView { ], ), ), - + if (showBindButton) + ElevatedButton( + onPressed: () => controller.confirmBindDevice(), + style: ElevatedButton.styleFrom( + backgroundColor: Colors.blue, + foregroundColor: Colors.white, + padding: EdgeInsets.symmetric(vertical: 16.h), + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16.r), + ), + elevation: 0, + minimumSize: Size(double.infinity, 56.h), + ), + child: Text( + "绑定", + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w600, + ), + ), + ), + SizedBox(height: 12.h), // 显示重试按钮 if (showRetryButton) ElevatedButton( @@ -578,6 +604,25 @@ class PairingView extends GetView { ), ), ], + //重新检测 + if (showRetryChectButton) ...[ + SizedBox(height: 12.h), + TextButton( + onPressed: () => controller.onRetryTap(), + style: TextButton.styleFrom( + foregroundColor: Color(0xFF0066CC), + padding: EdgeInsets.symmetric(vertical: 12.h), + minimumSize: Size(double.infinity, 48.h), + ), + child: Text( + '重新扫描'.tr, // 稍后再说 + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w500, + ), + ), + ), + ], ], ), ); diff --git a/lib/modules/pairing/views/pairing_view_new.dart b/lib/modules/pairing/views/pairing_view_new.dart index d4b843744..59026841f 100644 --- a/lib/modules/pairing/views/pairing_view_new.dart +++ b/lib/modules/pairing/views/pairing_view_new.dart @@ -152,7 +152,10 @@ class PairingView extends GetView { iconData = Icons.verified_user; iconColor = Colors.orange; break; - + case PairingState.binding: + iconData = Icons.blind; + iconColor = Colors.blue; + break; case PairingState.associating: iconData = Icons.link; iconColor = Colors.blue; @@ -195,6 +198,7 @@ class PairingView extends GetView { /// 构建按钮 Widget _buildButtons(bool isDarkMode) { final showConnectButton = controller.showConnectButton.value; + final showBindButton = controller.showBindButton.value; final showRetryButton = controller.showRetryButton.value; final showAutoCodeInput = controller.showAutoCodeInput.value; final showSkipButton = controller.showSkipButton.value; @@ -209,6 +213,19 @@ class PairingView extends GetView { isDarkMode, ), + // 绑定按钮 + if (showBindButton) + TextButton( + onPressed: controller.confirmBindDevice, + child: Text( + '绑定', + style: TextStyle( + fontSize: 14.sp, + color: isDarkMode ? Colors.grey[400] : Colors.grey[600], + ), + ), + ), + if (showRetryButton) _buildPrimaryButton( '重试', @@ -217,6 +234,7 @@ class PairingView extends GetView { ), SizedBox(height: 12.h), + if (showAutoCodeInput) // 自动输入代码按钮 SizedBox( diff --git a/lib/modules/settings/views/settings_view.dart b/lib/modules/settings/views/settings_view.dart index 5cce91556..be59cb2bd 100644 --- a/lib/modules/settings/views/settings_view.dart +++ b/lib/modules/settings/views/settings_view.dart @@ -505,8 +505,8 @@ class SettingsView extends GetView { // 清除配对设备 _buildSimpleNavigationSetting( - title: '清除配对设备', - subtitle: '清除所有BLE配对设备记录', + title: '设备管理', + subtitle: '设备管理列表', icon: Icons.bluetooth_disabled, iconBgColor: isDarkMode ? Colors.red[900]!.withOpacity(0.3) @@ -514,9 +514,7 @@ class SettingsView extends GetView { iconColor: isDarkMode ? Colors.red[300]! : Colors.red[600]!, titleColor: isDarkMode ? Colors.white : null, subtitleColor: isDarkMode ? Colors.white70 : null, - onTap: () { - controller.clearBleDevices(); - }, + onTap: () => Get.toNamed(Routes.devices), isDarkMode: isDarkMode, ), Divider( diff --git a/lib/routes/app_pages.dart b/lib/routes/app_pages.dart index 1d544a9e7..64eb8dd37 100644 --- a/lib/routes/app_pages.dart +++ b/lib/routes/app_pages.dart @@ -1,3 +1,5 @@ +import 'package:deep_voice/modules/devices/bindings/devices_binding.dart'; +import 'package:deep_voice/modules/devices/views/devices_view.dart'; import 'package:get/get.dart'; import '../modules/feedback/bindings/feedback_bindings.dart'; import '../modules/feedback/views/feedback_view.dart'; @@ -233,5 +235,10 @@ abstract class AppPages { transition: Transition.rightToLeft, transitionDuration: Duration(milliseconds: 250), ), + GetPage( + name: Routes.devices, + page: () => const DevicesView(), + binding: DevicesBinding(), + ), ]; } diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index 550ab16cc..2cc2d5e9d 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -52,4 +52,7 @@ abstract class Routes { // 实时语音对话 static const realtime = '/realtime'; + + // 设备管理 + static const devices = '/devices'; } diff --git a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt b/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt index b2fbe0cab..fe3584da5 100644 --- a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt +++ b/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleService.kt @@ -231,6 +231,7 @@ object BleService { return true } catch (e: Exception) { Log.e(TAG, "启动扫描失败: ${e.message}", e) + isScanning = false return false } } @@ -255,6 +256,7 @@ object BleService { return true } catch (e: Exception) { Log.e(TAG, "停止扫描失败: ${e.message}", e) + isScanning = false return false } } diff --git a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleServicePlugin.kt b/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleServicePlugin.kt index 3e5062717..7cc6932ae 100644 --- a/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleServicePlugin.kt +++ b/local_plugins/ble_service/android/src/main/kotlin/com/yunqiinnovation/ble_service/BleServicePlugin.kt @@ -304,6 +304,10 @@ class BleServicePlugin : FlutterPlugin, MethodCallHandler, ActivityAware, Log.d(TAG, "getDeviceInfo $info") result.success(info) } + "disconnect" -> { + BleService.disconnect(); + result.success(null) + } else -> { result.notImplemented() }