From 0d8bae2d4d6e90c2a3261e628a901befa28f0f28 Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Mon, 23 Jun 2025 10:55:21 +0800 Subject: [PATCH] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E8=AE=BE=E5=A4=87=E4=BF=9D?= =?UTF-8?q?=E5=AE=9A=E6=9C=BA=E5=88=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/data/models/user_Info.dart | 28 ++-- lib/data/services/network/api.dart | 9 ++ .../controllers/pairing_controller.dart | 129 +++++++++++++----- lib/modules/pairing/views/pairing_view.dart | 51 ++++++- .../pairing/views/pairing_view_new.dart | 73 +++++++--- lib/modules/settings/views/settings_view.dart | 3 +- 6 files changed, 223 insertions(+), 70 deletions(-) diff --git a/lib/data/models/user_Info.dart b/lib/data/models/user_Info.dart index cf5853ee6..63f6d60be 100644 --- a/lib/data/models/user_Info.dart +++ b/lib/data/models/user_Info.dart @@ -14,14 +14,15 @@ class User { String name; String uid; String avatar; - String deviceid; String language; String token; String mail; String phone; String phonemac; String password; - int membershipLevel = 0; //添加会员等级字段,0表示普通用户 + int viplv = 0; //添加会员等级字段,0表示普通用户 + String earphonemac; + String earphoneauthcode; // final Int64 lastbettime; // final Int64 createtime; // final String wxopenid; @@ -33,14 +34,15 @@ class User { required this.name, required this.uid, required this.avatar, - required this.deviceid, required this.language, required this.token, required this.mail, required this.phone, required this.phonemac, required this.password, - this.membershipLevel = 0, // 添加默认值 + required this.viplv, // 添加默认值 + required this.earphonemac, + required this.earphoneauthcode, // required this.lastbettime, // required this.createtime, // required this.wxopenid, @@ -60,14 +62,16 @@ class User { name: (userData?['name'] as String?) ?? '默认名称', // 提供默认值 uid: (userData?['uid'] as String?) ?? '', avatar: (userData?['avatar'] as String?) ?? '', - deviceid: (userData?['deviceid'] as String?) ?? '', + language: (userData?['language'] as String?) ?? 'en', mail: (userData?['mail'] as String?) ?? '', phone: (userData?['phone'] as String?) ?? '', phonemac: (userData?['phonemac'] as String?) ?? '', password: (userData?['password'] as String?) ?? '', - membershipLevel: (userData?['membershipLevel'] as int?) ?? 0, // 提供默认值 + viplv: (userData?['viplv'] as int?) ?? 0, // 提供默认值 + earphonemac: (userData?['earphonemac'] as String?) ?? '', + earphoneauthcode: (userData?['earphoneauthcode'] as String?) ?? '', // lastbettime: _parseTimestamp(userData['lastbettime']), // createtime: _parseTimestamp(userData['createtime']), // wxopenid: (userData?['wxopenid'] as String?) ?? '', @@ -85,13 +89,16 @@ class User { name: (userData?['name'] as String?) ?? '默认名称', // 提供默认值 uid: (userData?['uid'] as String?) ?? '', avatar: (userData?['avatar'] as String?) ?? '', - deviceid: (userData?['deviceid'] as String?) ?? '', language: (userData?['language'] as String?) ?? 'en', mail: (userData?['mail'] as String?) ?? '', phone: (userData?['phone'] as String?) ?? '', phonemac: (userData?['phonemac'] as String?) ?? '', password: (userData?['password'] as String?) ?? '', + + viplv: (userData?['viplv'] as int?) ?? 0, // 提供默认值 + earphonemac: (userData?['earphonemac'] as String?) ?? '', + earphoneauthcode: (userData?['earphoneauthcode'] as String?) ?? '', // lastbettime: _parseTimestamp(userData['lastbettime']), // createtime: _parseTimestamp(userData['createtime']), // wxopenid: (userData?['wxopenid'] as String?) ?? '', @@ -106,14 +113,17 @@ class User { 'name': name, 'uid': uid, 'avatar': avatar, - 'deviceid': deviceid, + 'language': language, 'token': token, 'mail': mail, 'phone': phone, 'phonemac': phonemac, 'password': password, - 'membershipLevel': membershipLevel, + 'viplv': viplv, + 'earphonemac': earphonemac, + 'earphoneauthcode': earphoneauthcode, + // 'lastbettime': lastbettime, // 'createtime': createtime, // 'wxopenid': wxopenid, diff --git a/lib/data/services/network/api.dart b/lib/data/services/network/api.dart index 07e265b4e..1eb3f41a0 100644 --- a/lib/data/services/network/api.dart +++ b/lib/data/services/network/api.dart @@ -126,4 +126,13 @@ class Api { params: params, ); } + + //绑定设备 + static bindearphone([params]) { + return DioManager().request( + NWMethod.post, + '/api/home/user_bindearphone', + params: params, + ); + } } diff --git a/lib/modules/pairing/controllers/pairing_controller.dart b/lib/modules/pairing/controllers/pairing_controller.dart index ed62d4ae9..c6fd22ae9 100644 --- a/lib/modules/pairing/controllers/pairing_controller.dart +++ b/lib/modules/pairing/controllers/pairing_controller.dart @@ -1,4 +1,7 @@ import 'package:app_settings/app_settings.dart'; +import 'package:deep_voice/data/models/user_Info.dart'; +import 'package:deep_voice/data/services/network/api.dart'; +import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'dart:async'; import 'package:permission_handler/permission_handler.dart'; @@ -15,9 +18,10 @@ enum PairingState { checking, // 检查中(蓝牙、权限等) waitingClassic, // 等待经典蓝牙连接 verifying, // 验证设备 + manualbinding, //手动绑定设备 associating, // 正在进行BLE设备关联 - waitingBle, // 等待BLE连接 - completed, // 配对完成 + waitingBle, //等待BLE连接 + completed, //配对完成 error, // 错误状态 } @@ -31,6 +35,7 @@ enum ErrorType { deviceBound, // 设备已被绑定 associationFailed, // 关联失败 bleTimeout, // BLE连接超时 + authnFailed, // 授权失败 networkError, // 网络错误 } @@ -53,6 +58,7 @@ class PairingController extends GetxController { final isLoading = false.obs; final showConnectButton = false.obs; final showRetryButton = false.obs; + final showAutoCodeInput = false.obs; final showSkipButton = false.obs; final connectButtonText = '连接设备'.obs; final retryButtonText = '重试'.obs; @@ -68,6 +74,7 @@ class PairingController extends GetxController { // ========== 当前设备信息 ========== BluetoothDevice? _currentClassicDevice; String? _verifiedDeviceAddress; + TextEditingController codeInputController = TextEditingController(); // Public getter for view access BluetoothDevice? get currentDevice => _currentClassicDevice; @@ -236,6 +243,7 @@ class PairingController extends GetxController { showConnectButton.value = false; showRetryButton.value = false; showSkipButton.value = false; + showAutoCodeInput.value = false; switch (state) { case PairingState.idle: @@ -276,7 +284,12 @@ class PairingController extends GetxController { _startBleConnection(); }); break; - + case PairingState.manualbinding: + statusText.value = '需要手动版定授权码...'; + isLoading.value = false; + showAutoCodeInput.value = true; + showSkipButton.value = true; + break; case PairingState.completed: statusText.value = '配对成功!'; isLoading.value = false; @@ -306,6 +319,10 @@ class PairingController extends GetxController { showRetryButton.value = true; retryButtonText.value = '重新关联'; break; + case ErrorType.authnFailed: + showRetryButton.value = true; + retryButtonText.value = '重新授权'; + break; default: showRetryButton.value = true; retryButtonText.value = '重试'; @@ -393,44 +410,48 @@ 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)); - - // 检查设备名称是否包含 DeepSound 或 DeapSound - final deviceName = device.name ?? ''; - if (deviceName.contains('DeepSound') || - deviceName.contains('DeapSound')) { - 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); + // await Future.delayed(const Duration(seconds: 2));; + if (await authbindingDevice()) { + // 检查设备名称是否包含 DeepSound 或 DeapSound + final deviceName = device.name ?? ''; + if (deviceName.contains('DeepSound') || + deviceName.contains('DeapSound')) { + 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未连接,等待BLE连接'); - _transition(PairingState.waitingBle); + // 没有关联或关联的是其他设备,需要进行关联 + Logger.i(_tag, '需要进行BLE设备关联'); + _transition(PairingState.associating); } } else { - // 没有关联或关联的是其他设备,需要进行关联 - Logger.i(_tag, '需要进行BLE设备关联'); - _transition(PairingState.associating); + // _setError(ErrorType.deviceNotMatch, '请连接 DeepSound 设备'); + _transition(PairingState.completed); } } else { - _setError(ErrorType.deviceNotMatch, '请连接 DeepSound 设备'); + //验证失败 进入手动授权 + _transition(PairingState.manualbinding); } } catch (e) { Logger.e(_tag, '设备验证异常: $e'); @@ -504,6 +525,46 @@ class PairingController extends GetxController { } } + //自动绑定设备 + Future authbindingDevice() async { + if (User.instance.earphonemac == currentDevice?.address) { + // 已经绑定 + return true; + } + + // 调用BLE管理器的自动绑定方法 + final response = await Api.bindearphone({ + "earphonemac": currentDevice?.address, + "earphoneauthcode": "", + }); + Logger.i(_tag, '手动授权结果 ${response}'); + if (response != null) { + Logger.i(_tag, '设备自动绑定成功'); + User.instance.earphonemac = currentDevice?.address ?? ''; + return true; + } else { + Logger.e(_tag, '手动绑定失败!'); + return false; + } + } + + /// 用户点击授权码输入框 + void onAutoCodeInputTap() async { + // 调用BLE管理器的自动绑定方法 + final response = await Api.bindearphone({ + "earphonemac": currentDevice?.address, + "earphoneauthcode": codeInputController.text.trim(), + }); + Logger.i(_tag, '手动授权结果 ${response}'); + if (response != null) { + _transition(PairingState.verifying); + _verifyDevice(_currentClassicDevice!); + } else { + Logger.e(_tag, '手动绑定失败!'); + _setError(ErrorType.authnFailed, '设备授权失败,请检查授权码'); + } + } + /// 导航到主页 void _navigateToHome() async { await BleManager.to.startAgentService(); diff --git a/lib/modules/pairing/views/pairing_view.dart b/lib/modules/pairing/views/pairing_view.dart index b0cf500c3..542e22ec9 100644 --- a/lib/modules/pairing/views/pairing_view.dart +++ b/lib/modules/pairing/views/pairing_view.dart @@ -336,6 +336,8 @@ class PairingView extends GetView { return '扫描超时,请确保设备在附近'; case ErrorType.networkError: return '网络错误,请检查网络连接'; + case ErrorType.authnFailed: + return '授权失败,请检查授权码是否正确'; default: return '配对失败,请重试'; } @@ -433,7 +435,7 @@ class PairingView extends GetView { final showConnectButton = controller.showConnectButton.value; final showRetryButton = controller.showRetryButton.value; final showSkipButton = controller.showSkipButton.value; - + final showAutoCodeInput = controller.showAutoCodeInput.value; return Container( width: double.infinity, child: Column( @@ -490,6 +492,51 @@ class PairingView extends GetView { ), ), ), + if (showAutoCodeInput) + Column( + children: [ + // 输入框 + SizedBox( + width: double.infinity, + height: 48.h, + child: TextField( + controller: controller + .codeInputController, // 你可以在 Controller 中声明这个 + decoration: InputDecoration( + hintText: '请输入代码', + border: OutlineInputBorder( + borderRadius: BorderRadius.circular(12.r), + ), + contentPadding: EdgeInsets.symmetric(horizontal: 16.w), + ), + ), + ), + SizedBox(height: 12.h), + SizedBox( + width: double.infinity, + height: 48.h, + child: ElevatedButton( + onPressed: controller.onAutoCodeInputTap, + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of(Get.context!).primaryColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12.r), + ), + elevation: 0, + ), + child: Text( + '授权绑定', + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w600, + color: Colors.white, + ), + ), + ), + ), + ], + ), + // 自动输入代码按钮 // 跳过按钮 if (showSkipButton) ...[ @@ -510,11 +557,9 @@ class PairingView extends GetView { ), ), ], - ], ), ); }); } - } diff --git a/lib/modules/pairing/views/pairing_view_new.dart b/lib/modules/pairing/views/pairing_view_new.dart index eae654a1b..d4b843744 100644 --- a/lib/modules/pairing/views/pairing_view_new.dart +++ b/lib/modules/pairing/views/pairing_view_new.dart @@ -21,17 +21,17 @@ class PairingView extends GetView { children: [ // 顶部标题 _buildHeader(isDarkMode), - + // 中间内容 Expanded( child: Center( child: Obx(() => _buildContent(isDarkMode)), ), ), - + // 底部按钮 Obx(() => _buildButtons(isDarkMode)), - + SizedBox(height: 20.h), ], ), @@ -69,15 +69,15 @@ class PairingView extends GetView { Widget _buildContent(bool isDarkMode) { final state = controller.currentState; final isLoading = controller.isLoading.value; - + return Column( mainAxisSize: MainAxisSize.min, children: [ // 状态图标 _buildStateIcon(state, isLoading, isDarkMode), - + SizedBox(height: 40.h), - + // 状态文本 Text( controller.statusText.value, @@ -87,7 +87,7 @@ class PairingView extends GetView { ), textAlign: TextAlign.center, ), - + // 错误详情 if (state == PairingState.error) ...[ SizedBox(height: 16.h), @@ -132,48 +132,51 @@ class PairingView extends GetView { ), ); } - + IconData iconData; Color iconColor; - + switch (state) { case PairingState.idle: case PairingState.checking: iconData = Icons.bluetooth_searching; iconColor = Colors.blue; break; - + case PairingState.waitingClassic: iconData = Icons.bluetooth; iconColor = Colors.grey; break; - + case PairingState.verifying: iconData = Icons.verified_user; iconColor = Colors.orange; break; - + case PairingState.associating: iconData = Icons.link; iconColor = Colors.blue; break; - + case PairingState.waitingBle: iconData = Icons.sync; iconColor = Colors.blue; break; - + case PairingState.manualbinding: + iconData = Icons.blind; + iconColor = Colors.blue; + break; case PairingState.completed: iconData = Icons.check_circle; iconColor = Colors.green; break; - + case PairingState.error: iconData = Icons.error; iconColor = Colors.red; break; } - + return Container( width: 100.w, height: 100.w, @@ -193,8 +196,9 @@ class PairingView extends GetView { Widget _buildButtons(bool isDarkMode) { final showConnectButton = controller.showConnectButton.value; final showRetryButton = controller.showRetryButton.value; + final showAutoCodeInput = controller.showAutoCodeInput.value; final showSkipButton = controller.showSkipButton.value; - + return Column( children: [ // 主要操作按钮 @@ -204,16 +208,40 @@ class PairingView extends GetView { controller.onConnectTap, isDarkMode, ), - + if (showRetryButton) _buildPrimaryButton( '重试', controller.onRetryTap, isDarkMode, ), - + SizedBox(height: 12.h), - + if (showAutoCodeInput) + // 自动输入代码按钮 + SizedBox( + width: double.infinity, + height: 48.h, + child: ElevatedButton( + onPressed: controller.onAutoCodeInputTap, + style: ElevatedButton.styleFrom( + backgroundColor: Theme.of(Get.context!).primaryColor, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12.r), + ), + elevation: 0, + ), + child: Text( + '自动输入代码', + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w600, + color: Colors.white, + ), + ), + ), + ), + // 跳过按钮 if (showSkipButton) TextButton( @@ -231,7 +259,8 @@ class PairingView extends GetView { } /// 构建主要按钮 - Widget _buildPrimaryButton(String text, VoidCallback onPressed, bool isDarkMode) { + Widget _buildPrimaryButton( + String text, VoidCallback onPressed, bool isDarkMode) { return SizedBox( width: double.infinity, height: 48.h, @@ -255,4 +284,4 @@ class PairingView extends GetView { ), ); } -} \ No newline at end of file +} diff --git a/lib/modules/settings/views/settings_view.dart b/lib/modules/settings/views/settings_view.dart index 35bb6c90b..4f16e2b33 100644 --- a/lib/modules/settings/views/settings_view.dart +++ b/lib/modules/settings/views/settings_view.dart @@ -200,8 +200,7 @@ class SettingsView extends GetView { 2: 'proUser', //'专业用户' }; final membershipString = membershipMap[ - controller - .userInfo.value.membershipLevel] ?? + controller.userInfo.value.viplv] ?? 'Unknown'; return Text( membershipString,