Browse Source

009

newdev_shunjiawei
wolfplus 2 years ago
parent
commit
2934057b75
  1. 242
      android/app/src/main/kotlin/com/example/deep_voice/ClassicBluetoothHelper.kt
  2. 222
      android/app/src/main/kotlin/com/example/deep_voice/MainActivity.kt
  3. 4
      lib/core/bindings/initial_binding.dart
  4. 436
      lib/data/services/bluetooth_service.dart
  5. 452
      lib/data/services/classic_bluetooth_service.dart
  6. 3
      lib/main.dart
  7. 571
      lib/modules/home/controllers/home_controller.dart
  8. 366
      lib/modules/pairing/controllers/pairing_controller.dart
  9. 489
      lib/modules/pairing/views/pairing_view.dart
  10. 66
      lib/modules/translation/views/translation_view.dart
  11. 1
      pubspec.yaml

242
android/app/src/main/kotlin/com/example/deep_voice/ClassicBluetoothHelper.kt

@ -0,0 +1,242 @@
package com.example.deep_voice
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothProfile
import android.content.Context
import android.content.pm.PackageManager
import android.os.Build
import android.util.Log
import android.Manifest
import androidx.core.content.ContextCompat
import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit
/**
* 经典蓝牙辅助类,用于获取已连接的蓝牙设备信息
*/
class ClassicBluetoothHelper(private val context: Context) {
private val TAG = "ClassicBluetoothHelper"
// 缓存蓝牙代理对象
private var a2dpProxy: BluetoothProfile? = null
private var headsetProxy: BluetoothProfile? = null
/**
* 获取已连接的A2DP设备(音频输出设备)
* @param callback 回调接口,返回设备列表或错误信息
*/
fun getConnectedA2dpDevices(callback: BluetoothDevicesCallback) {
getConnectedBluetoothDevices(BluetoothProfile.A2DP, callback)
}
/**
* 获取已连接的蓝牙耳机设备
* @param callback 回调接口,返回设备列表或错误信息
*/
fun getConnectedHeadsetDevices(callback: BluetoothDevicesCallback) {
getConnectedBluetoothDevices(BluetoothProfile.HEADSET, callback)
}
/**
* 检查是否有蓝牙连接权限
* @return 是否有权限
*/
private fun hasBluetoothConnectPermission(): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
ContextCompat.checkSelfPermission(
context,
Manifest.permission.BLUETOOTH_CONNECT
) == PackageManager.PERMISSION_GRANTED
} else {
true
}
}
/**
* 检查蓝牙是否启用
* @return 蓝牙是否启用
*/
fun isBluetoothEnabled(): Boolean {
try {
val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
if (bluetoothAdapter == null) {
Log.w(TAG, "设备不支持蓝牙")
return false
}
return bluetoothAdapter.isEnabled
} catch (e: Exception) {
Log.e(TAG, "检查蓝牙状态失败: ${e.message}")
return false
}
}
/**
* 获取指定配置文件的已连接蓝牙设备
* @param profile 蓝牙配置文件类型,如 BluetoothProfile.A2DP 或 BluetoothProfile.HEADSET
* @param callback 回调接口,返回设备列表或错误信息
*/
private fun getConnectedBluetoothDevices(profile: Int, callback: BluetoothDevicesCallback) {
val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter()
if (bluetoothAdapter == null) {
Log.e(TAG, "没有找到蓝牙适配器")
callback.onError("NO_ADAPTER", "没有找到蓝牙适配器")
return
}
// 检查蓝牙权限
if (!hasBluetoothConnectPermission()) {
Log.e(TAG, "缺少BLUETOOTH_CONNECT权限")
callback.onError("PERMISSION_DENIED", "缺少蓝牙连接权限,请在应用设置中授予该权限")
return
}
try {
// 检查是否已经有缓存的代理对象
val cachedProxy = when (profile) {
BluetoothProfile.A2DP -> a2dpProxy
BluetoothProfile.HEADSET -> headsetProxy
else -> null
}
if (cachedProxy != null) {
// 使用已缓存的代理对象
processDeviceList(cachedProxy, profile, callback)
return
}
// 使用原子布尔值确保回调只被调用一次
val callbackCalled = AtomicBoolean(false)
// 使用CountDownLatch等待服务连接
val latch = CountDownLatch(1)
// 通过 getProfileProxy 获取指定 Profile (A2DP/HEADSET) 的连接状态
bluetoothAdapter.getProfileProxy(context,
object : BluetoothProfile.ServiceListener {
override fun onServiceConnected(profileId: Int, proxy: BluetoothProfile) {
Log.d(TAG, "蓝牙配置文件服务已连接: $profileId")
if (profileId == profile) {
// 缓存代理对象
when (profileId) {
BluetoothProfile.A2DP -> a2dpProxy = proxy
BluetoothProfile.HEADSET -> headsetProxy = proxy
}
try {
processDeviceList(proxy, profileId, callback)
latch.countDown()
} catch (e: Exception) {
Log.e(TAG, "处理设备信息时出错: ${e.message}")
// 确保回调只被调用一次
if (callbackCalled.compareAndSet(false, true)) {
callback.onError("DEVICE_ERROR", "处理设备信息时出错: ${e.message}")
}
latch.countDown()
}
}
}
override fun onServiceDisconnected(profileId: Int) {
// Profile 断开连接
Log.e(TAG, "蓝牙配置文件服务断开连接: $profileId")
// 清除对应的缓存代理
when (profileId) {
BluetoothProfile.A2DP -> a2dpProxy = null
BluetoothProfile.HEADSET -> headsetProxy = null
}
// 确保回调只被调用一次,但不在这里调用错误回调
// 避免服务断开时也返回错误导致重复回调
if (callbackCalled.compareAndSet(false, true) && !latch.await(0, TimeUnit.MILLISECONDS)) {
callback.onError("SERVICE_DISCONNECTED", "蓝牙配置文件服务断开连接")
latch.countDown()
}
}
}, profile
)
// 设置超时处理
Thread {
try {
// 最多等待3秒
if (!latch.await(3, TimeUnit.SECONDS)) {
Log.e(TAG, "获取蓝牙设备超时")
if (callbackCalled.compareAndSet(false, true)) {
callback.onError("TIMEOUT", "获取蓝牙设备超时")
}
}
} catch (e: InterruptedException) {
Log.e(TAG, "等待蓝牙服务被中断: ${e.message}")
if (callbackCalled.compareAndSet(false, true)) {
callback.onError("INTERRUPTED", "等待蓝牙服务被中断: ${e.message}")
}
}
}.start()
} catch (e: Exception) {
Log.e(TAG, "获取蓝牙设备失败: ${e.message}")
callback.onError("BLUETOOTH_ERROR", "获取蓝牙设备失败: ${e.message}")
}
}
/**
* 处理设备列表
*/
private fun processDeviceList(proxy: BluetoothProfile, profileId: Int, callback: BluetoothDevicesCallback) {
// 获取已连接设备
val connectedDevices = proxy.connectedDevices
// 转换为设备信息列表
val devicesList = connectedDevices.map { device ->
mapOf(
"name" to (device.name ?: "未知设备"),
"address" to device.address
)
}
Log.d(TAG, "获取到的设备列表:${devicesList.size}个设备")
// 返回设备列表(不关闭代理)
callback.onSuccess(devicesList)
}
/**
* 释放资源
*/
fun dispose() {
val bluetoothAdapter = BluetoothAdapter.getDefaultAdapter() ?: return
// 释放A2DP代理
a2dpProxy?.let {
try {
bluetoothAdapter.closeProfileProxy(BluetoothProfile.A2DP, it)
} catch (e: Exception) {
Log.e(TAG, "关闭A2DP代理失败: ${e.message}")
}
a2dpProxy = null
}
// 释放HEADSET代理
headsetProxy?.let {
try {
bluetoothAdapter.closeProfileProxy(BluetoothProfile.HEADSET, it)
} catch (e: Exception) {
Log.e(TAG, "关闭HEADSET代理失败: ${e.message}")
}
headsetProxy = null
}
}
/**
* 蓝牙设备回调接口
*/
interface BluetoothDevicesCallback {
fun onSuccess(devices: List<Map<String, String>>)
fun onError(code: String, message: String)
}
}

222
android/app/src/main/kotlin/com/example/deep_voice/MainActivity.kt

@ -15,6 +15,8 @@ import android.content.BroadcastReceiver
import android.content.IntentFilter
import android.os.Handler
import android.os.Looper
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
class MainActivity: FlutterActivity() {
@ -23,11 +25,15 @@ class MainActivity: FlutterActivity() {
private val AZURE_TTS_CHANNEL = "com.deep_voice.azure_tts"
private val VOICE_INTERACTION_CHANNEL = "com.deep_voice.voice_interaction"
private val VOICE_INTERACTION_EVENT_CHANNEL = "com.deep_voice.voice_interaction_events"
private val CLASSIC_BLUETOOTH_CHANNEL = "com.deep_voice.classic_bluetooth"
private val CLASSIC_BLUETOOTH_EVENT_CHANNEL = "com.deep_voice.classic_bluetooth_events"
private val TAG = "MainActivity"
private lateinit var azureAsrHelper: AzureAsrHelper
private lateinit var azureTtsHelper: AzureTtsHelper
private lateinit var classicBluetoothHelper: ClassicBluetoothHelper
private var azureAsrEventSink: EventChannel.EventSink? = null
private var voiceInteractionEventSink: EventChannel.EventSink? = null
private var bluetoothEventSink: EventChannel.EventSink? = null
// 广播接收器
private val voiceInteractionReceiver = object : BroadcastReceiver() {
@ -59,6 +65,75 @@ class MainActivity: FlutterActivity() {
}
}
// 蓝牙状态变化广播接收器
private val bluetoothStateReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
when (intent?.action) {
BluetoothDevice.ACTION_ACL_CONNECTED -> {
val device = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice::class.java)
} else {
@Suppress("DEPRECATION")
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)
}
if (device != null) {
Log.d(TAG, "蓝牙设备已连接: ${device.name ?: "未知设备"} (${device.address})")
sendBluetoothEvent(mapOf(
"type" to "deviceConnected",
"device" to mapOf(
"name" to (device.name ?: "未知设备"),
"address" to device.address
),
"timestamp" to System.currentTimeMillis()
))
}
}
BluetoothDevice.ACTION_ACL_DISCONNECTED -> {
val device = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE, BluetoothDevice::class.java)
} else {
@Suppress("DEPRECATION")
intent.getParcelableExtra(BluetoothDevice.EXTRA_DEVICE)
}
if (device != null) {
Log.d(TAG, "蓝牙设备已断开: ${device.name ?: "未知设备"} (${device.address})")
sendBluetoothEvent(mapOf(
"type" to "deviceDisconnected",
"device" to mapOf(
"name" to (device.name ?: "未知设备"),
"address" to device.address
),
"timestamp" to System.currentTimeMillis()
))
}
}
BluetoothAdapter.ACTION_STATE_CHANGED -> {
val state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE, BluetoothAdapter.ERROR)
when (state) {
BluetoothAdapter.STATE_ON -> {
Log.d(TAG, "蓝牙已开启")
sendBluetoothEvent(mapOf(
"type" to "bluetoothStateChanged",
"state" to "on",
"timestamp" to System.currentTimeMillis()
))
}
BluetoothAdapter.STATE_OFF -> {
Log.d(TAG, "蓝牙已关闭")
sendBluetoothEvent(mapOf(
"type" to "bluetoothStateChanged",
"state" to "off",
"timestamp" to System.currentTimeMillis()
))
}
}
}
}
}
}
// 使用伴生对象存储配置信息,使其在整个应用中可访问
companion object {
// Azure 语音服务配置
@ -74,9 +149,10 @@ class MainActivity: FlutterActivity() {
Log.d(TAG, "初始化本地的 Azure 语音服务实例")
azureAsrHelper = AzureAsrHelper()
azureTtsHelper = AzureTtsHelper()
classicBluetoothHelper = ClassicBluetoothHelper(applicationContext)
// 注册广播接收器
registerVoiceInteractionReceiver()
registerReceivers()
// 确保 Flutter 引擎准备好后立即设置事件通道
flutterEngine?.let { engine ->
@ -85,15 +161,21 @@ class MainActivity: FlutterActivity() {
}
}
/**
* 注册语音交互服务的广播接收器
*/
private fun registerVoiceInteractionReceiver() {
val intentFilter = IntentFilter().apply {
private fun registerReceivers() {
// 注册语音交互广播接收器
val voiceInteractionFilter = IntentFilter().apply {
addAction(VoiceInteractionService.ACTION_RECOGNITION_STARTED)
addAction(VoiceInteractionService.ACTION_CHAT_HISTORY_UPDATED)
}
registerReceiver(voiceInteractionReceiver, intentFilter)
registerReceiver(voiceInteractionReceiver, voiceInteractionFilter)
// 注册蓝牙状态变化广播接收器
val bluetoothFilter = IntentFilter().apply {
addAction(BluetoothDevice.ACTION_ACL_CONNECTED)
addAction(BluetoothDevice.ACTION_ACL_DISCONNECTED)
addAction(BluetoothAdapter.ACTION_STATE_CHANGED)
}
registerReceiver(bluetoothStateReceiver, bluetoothFilter)
}
override fun configureFlutterEngine(flutterEngine: FlutterEngine) {
@ -149,6 +231,26 @@ class MainActivity: FlutterActivity() {
}
)
// 设置蓝牙事件通道
EventChannel(flutterEngine.dartExecutor.binaryMessenger, CLASSIC_BLUETOOTH_EVENT_CHANNEL).setStreamHandler(
object : EventChannel.StreamHandler {
override fun onListen(arguments: Any?, events: EventChannel.EventSink?) {
Log.d(TAG, "蓝牙事件通道开始监听")
bluetoothEventSink = events
// 发送就绪事件
events?.success(mapOf(
"type" to "channelReady",
"timestamp" to System.currentTimeMillis()
))
}
override fun onCancel(arguments: Any?) {
Log.d(TAG, "蓝牙事件通道停止监听")
bluetoothEventSink = null
}
}
)
} catch (e: Exception) {
Log.e(TAG, "设置事件通道时发生错误: ${e.message}", e)
}
@ -158,7 +260,9 @@ class MainActivity: FlutterActivity() {
* 设置方法通道
*/
private fun setupMethodChannels(flutterEngine: FlutterEngine) {
// Azure ASR 方法通道
Log.d(TAG, "开始设置方法通道")
// 设置 Azure ASR 方法通道
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, AZURE_ASR_CHANNEL).setMethodCallHandler { call, result ->
when (call.method) {
"initialize" -> {
@ -308,7 +412,7 @@ class MainActivity: FlutterActivity() {
}
}
// Azure TTS 方法通道
// 设置 Azure TTS 方法通道
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, AZURE_TTS_CHANNEL).setMethodCallHandler { call, result ->
when (call.method) {
"initialize" -> {
@ -387,7 +491,7 @@ class MainActivity: FlutterActivity() {
}
}
// 语音交互服务通道
// 设置语音交互方法通道
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, VOICE_INTERACTION_CHANNEL).setMethodCallHandler { call, result ->
when (call.method) {
"startVoiceInteractionService" -> {
@ -409,6 +513,79 @@ class MainActivity: FlutterActivity() {
}
}
}
// 设置经典蓝牙方法通道
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CLASSIC_BLUETOOTH_CHANNEL).setMethodCallHandler { call, result ->
when (call.method) {
"getConnectedA2dpDevices" -> {
try {
classicBluetoothHelper.getConnectedA2dpDevices(object : ClassicBluetoothHelper.BluetoothDevicesCallback {
override fun onSuccess(devices: List<Map<String, String>>) {
try {
result.success(devices)
} catch (e: Exception) {
Log.e(TAG, "返回A2DP设备时发生异常: ${e.message}")
}
}
override fun onError(code: String, message: String) {
try {
result.error(code, message, null)
} catch (e: Exception) {
Log.e(TAG, "返回A2DP设备错误时发生异常: ${e.message}")
}
}
})
} catch (e: Exception) {
try {
Log.e(TAG, "获取A2DP设备时发生异常: ${e.message}")
result.error("UNEXPECTED_ERROR", "获取A2DP设备时发生异常: ${e.message}", e.toString())
} catch (e2: Exception) {
Log.e(TAG, "处理A2DP设备获取异常时发生二次异常: ${e2.message}")
}
}
}
"getConnectedHeadsetDevices" -> {
try {
classicBluetoothHelper.getConnectedHeadsetDevices(object : ClassicBluetoothHelper.BluetoothDevicesCallback {
override fun onSuccess(devices: List<Map<String, String>>) {
try {
result.success(devices)
} catch (e: Exception) {
Log.e(TAG, "返回耳机设备时发生异常: ${e.message}")
}
}
override fun onError(code: String, message: String) {
try {
result.error(code, message, null)
} catch (e: Exception) {
Log.e(TAG, "返回耳机设备错误时发生异常: ${e.message}")
}
}
})
} catch (e: Exception) {
try {
Log.e(TAG, "获取耳机设备时发生异常: ${e.message}")
result.error("UNEXPECTED_ERROR", "获取耳机设备时发生异常: ${e.message}", e.toString())
} catch (e2: Exception) {
Log.e(TAG, "处理耳机设备获取异常时发生二次异常: ${e2.message}")
}
}
}
"isBluetoothEnabled" -> {
try {
val isEnabled = classicBluetoothHelper.isBluetoothEnabled()
result.success(isEnabled)
Log.d(TAG, "检查蓝牙状态: ${if (isEnabled) "已启用" else "未启用"}")
} catch (e: Exception) {
Log.e(TAG, "检查蓝牙状态时发生异常: ${e.message}")
result.error("BLUETOOTH_ERROR", "检查蓝牙状态失败: ${e.message}", e.toString())
}
}
else -> result.notImplemented()
}
}
}
// ASR 事件发送方法
@ -517,10 +694,32 @@ class MainActivity: FlutterActivity() {
}
}
// 发送蓝牙事件到Flutter
private fun sendBluetoothEvent(event: Map<String, Any>) {
if (bluetoothEventSink == null) {
Log.w(TAG, "无法发送蓝牙事件:事件通道未准备好")
return
}
try {
runOnUiThread {
try {
bluetoothEventSink?.success(event)
Log.d(TAG, "已发送蓝牙事件: ${event["type"]}")
} catch (e: Exception) {
Log.e(TAG, "发送蓝牙事件失败: ${e.message}")
}
}
} catch (e: Exception) {
Log.e(TAG, "转到UI线程发送蓝牙事件失败: ${e.message}")
}
}
override fun onDestroy() {
// 注销广播接收器
// 注销所有广播接收器
try {
unregisterReceiver(voiceInteractionReceiver)
unregisterReceiver(bluetoothStateReceiver)
} catch (e: Exception) {
Log.e(TAG, "注销广播接收器失败: ${e.message}")
}
@ -528,6 +727,7 @@ class MainActivity: FlutterActivity() {
// 释放资源
azureAsrHelper.dispose()
azureTtsHelper.dispose()
classicBluetoothHelper.dispose()
super.onDestroy()
}

4
lib/core/bindings/initial_binding.dart

@ -6,6 +6,7 @@ import '../../data/services/volcano_translation_service.dart';
import '../../data/services/voice_interaction_service.dart';
import '../../data/services/language_manager.dart';
import '../../data/services/volcano_ai_service.dart';
import '../../data/services/classic_bluetooth_service.dart';
import '../../core/utils/logger.dart';
/// 初始绑定,用于管理全局依赖
@ -34,5 +35,8 @@ class InitialBinding extends Bindings {
// 注意:VoiceInteractionService 在 HomeController 中初始化
Get.lazyPut<VoiceInteractionService>(() => VoiceInteractionService(), fenix: true);
// 经典蓝牙服务
Get.put<ClassicBluetoothService>(ClassicBluetoothService()..initialize(), permanent: true);
}
}

436
lib/data/services/bluetooth_service.dart

@ -1,436 +0,0 @@
import 'dart:async';
import 'package:flutter_blue_plus/flutter_blue_plus.dart';
import 'package:get/get.dart';
import '../../core/utils/logger.dart';
import 'dart:io' show Platform;
import 'package:permission_handler/permission_handler.dart';
/// 设备类型枚举
enum DeviceType {
unknown, // 未知设备类型
classicAudio, // 经典蓝牙音频设备(耳机等)
ble, // BLE设备
dual // 双模设备(同时支持经典蓝牙和BLE)
}
/// 简化的蓝牙设备服务,仅用于获取已连接或配对的蓝牙耳机信息
class BluetoothService extends GetxService {
static BluetoothService get to => Get.find<BluetoothService>();
// 内部实例
BluetoothService._internal();
static final BluetoothService _instance = BluetoothService._internal();
factory BluetoothService() => _instance;
// ====== 状态管理 ======
// 蓝牙适配器状态
final _isAvailable = false.obs;
final _isOn = false.obs;
final _isInitialized = false.obs;
// 权限状态
final _permissionsGranted = false.obs;
// 设备管理
final _connectedDevices = <BluetoothDevice>[].obs;
final _systemPairedDevices = <BluetoothDevice>[].obs;
final _selectedDevice = Rx<BluetoothDevice?>(null);
final _deviceType = Rx<DeviceType>(DeviceType.unknown);
final _connectionState = Rx<BluetoothConnectionState>(BluetoothConnectionState.disconnected);
final _lastConnectedDeviceInfo = Rx<Map<String, dynamic>>({});
// 流订阅
StreamSubscription? _stateSubscription;
// 防止重复刷新的标志
bool _isRefreshingDevices = false;
// ====== Getters ======
// 基本getter
bool get isAvailable => _isAvailable.value;
bool get isOn => _isOn.value;
bool get isInitialized => _isInitialized.value;
bool get permissionsGranted => _permissionsGranted.value;
// 设备相关getter
List<BluetoothDevice> get connectedDevices => _connectedDevices;
List<BluetoothDevice> get systemPairedDevices => _systemPairedDevices;
BluetoothDevice? get selectedDevice => _selectedDevice.value;
DeviceType get deviceType => _deviceType.value;
BluetoothConnectionState get connectionState => _connectionState.value;
Map<String, dynamic> get lastConnectedDeviceInfo => _lastConnectedDeviceInfo.value;
// Observable getters
RxBool get isAvailableObs => _isAvailable;
RxBool get isOnObs => _isOn;
RxBool get isInitializedObs => _isInitialized;
RxBool get permissionsGrantedObs => _permissionsGranted;
RxList<BluetoothDevice> get connectedDevicesObs => _connectedDevices;
Rx<BluetoothDevice?> get selectedDeviceObs => _selectedDevice;
Rx<DeviceType> get deviceTypeObs => _deviceType;
Rx<BluetoothConnectionState> get connectionStateObs => _connectionState;
Rx<Map<String, dynamic>> get lastConnectedDeviceInfoObs => _lastConnectedDeviceInfo;
/// 初始化蓝牙服务
Future<BluetoothService> init() async {
if (_isInitialized.value) {
Logger.info('蓝牙服务已初始化,无需重复初始化');
return this;
}
Logger.info('开始初始化蓝牙服务');
try {
// 1. 设置蓝牙状态监听
_setupBluetoothListeners();
// 2. 初始检查蓝牙状态
_checkBluetoothState();
// 3. 请求权限
await requestPermissions();
// 4. 刷新系统设备
if (_isOn.value && _permissionsGranted.value) {
await _refreshSystemDevices();
}
// 5. 标记初始化完成
_isInitialized.value = true;
Logger.info('蓝牙服务初始化完成');
return this;
} catch (e) {
Logger.error('初始化蓝牙服务失败: $e');
return this;
}
}
/// 设置蓝牙状态监听
void _setupBluetoothListeners() {
// 取消旧订阅(如果有)
_stateSubscription?.cancel();
// 监听蓝牙适配器状态变化
_stateSubscription = FlutterBluePlus.adapterState.listen((state) {
_isOn.value = state == BluetoothAdapterState.on;
_isAvailable.value = state != BluetoothAdapterState.unavailable;
if (state == BluetoothAdapterState.on) {
Logger.info('蓝牙已开启');
if (_permissionsGranted.value) {
_refreshSystemDevices();
}
} else if (state == BluetoothAdapterState.off) {
Logger.info('蓝牙已关闭');
_resetDeviceState();
}
});
}
/// 初始检查蓝牙状态
Future<void> _checkBluetoothState() async {
try {
bool isOn = await FlutterBluePlus.isOn
.timeout(const Duration(seconds: 5), onTimeout: () => false);
_isOn.value = isOn;
_isAvailable.value = isOn;
Logger.info('当前蓝牙是否已开启: $isOn');
} catch (e) {
Logger.error('检查蓝牙状态失败: $e');
_isOn.value = false;
_isAvailable.value = false;
}
}
/// 重置设备状态
void _resetDeviceState() {
_connectedDevices.clear();
_systemPairedDevices.clear();
_selectedDevice.value = null;
_lastConnectedDeviceInfo.value = {};
_deviceType.value = DeviceType.unknown;
}
/// 检查并请求蓝牙权限
Future<bool> requestPermissions() async {
try {
// iOS无需额外权限
if (!Platform.isAndroid) {
_permissionsGranted.value = true;
return true;
}
// 请求所有必要权限
final permissions = <Permission>[
// Permission.bluetoothScan,
// Permission.bluetoothConnect,
// Permission.location, // Android需要定位权限来扫描BLE设备
Permission.microphone,
];
// 一次性请求所有权限
Map<Permission, PermissionStatus> statuses = await permissions.request();
// 检查所有权限是否已授予
bool allGranted = statuses.values.every((status) => status.isGranted);
_permissionsGranted.value = allGranted;
if (!allGranted) {
// 检查哪些权限被拒绝
List<String> deniedPermissions = [];
statuses.forEach((permission, status) {
if (!status.isGranted) {
deniedPermissions.add(permission.toString().split('.').last);
}
});
Logger.warning('权限被拒绝: ${deniedPermissions.join(", ")}');
}
return allGranted;
} catch (e) {
Logger.error('请求权限失败: $e');
_permissionsGranted.value = false;
return false;
}
}
/// 刷新系统蓝牙设备(已连接和已配对)
Future<void> _refreshSystemDevices() async {
if (!_permissionsGranted.value || !_isOn.value) {
Logger.warning('无法刷新系统设备:蓝牙未开启或缺少权限');
return;
}
// 防止重复刷新
if (_isRefreshingDevices) {
return;
}
_isRefreshingDevices = true;
try {
// 获取已连接的系统设备
List<BluetoothDevice> devices = await FlutterBluePlus.connectedSystemDevices
.timeout(const Duration(seconds: 10), onTimeout: () => <BluetoothDevice>[]);
_connectedDevices.value = devices;
Logger.info('获取到${devices.length}个系统已连接设备');
// 更新选中设备的连接状态(如果有)
if (_selectedDevice.value != null) {
bool stillConnected = _connectedDevices.any(
(d) => d.remoteId.str == _selectedDevice.value!.remoteId.str
);
_connectionState.value = stillConnected
? BluetoothConnectionState.connected
: BluetoothConnectionState.disconnected;
if (stillConnected) {
_updateLastConnectedDeviceInfo();
}
}
// 获取系统已配对设备
List<BluetoothDevice> pairedDevices = await FlutterBluePlus.bondedDevices
.timeout(const Duration(seconds: 10), onTimeout: () => <BluetoothDevice>[]);
_systemPairedDevices.value = pairedDevices;
Logger.info('获取到${pairedDevices.length}个系统已配对设备');
} catch (e) {
Logger.error('刷新系统设备失败: $e');
} finally {
_isRefreshingDevices = false;
}
}
/// 查找音频设备
BluetoothDevice? _findAudioDevice(List<BluetoothDevice> devices) {
if (devices.isEmpty) return null;
// 音频设备的可能名称关键词
final List<String> audioKeywords = [
'headphone', 'earphone', 'earbuds', 'headset',
'speaker', 'audio', '耳机', '音箱', '音响'
];
// 尝试匹配音频设备名称
for (BluetoothDevice device in devices) {
String name = device.platformName.toLowerCase();
if (audioKeywords.any((keyword) => name.contains(keyword))) {
return device;
}
}
return null;
}
/// 确定设备类型
void _determineDeviceType(BluetoothDevice device) {
final String deviceName = device.platformName.toLowerCase();
// 音频设备关键词
final List<String> audioKeywords = [
'headphone', 'earphone', 'earbuds', 'headset',
'speaker', 'audio', '耳机', '音箱', '音响'
];
bool hasAudioKeyword = audioKeywords.any((keyword) => deviceName.contains(keyword));
bool inConnectedList = _connectedDevices.any((d) => d.remoteId.str == device.remoteId.str);
if (hasAudioKeyword || inConnectedList) {
_deviceType.value = DeviceType.classicAudio;
} else {
_deviceType.value = DeviceType.ble;
}
}
/// 获取已连接设备的信息
Map<String, dynamic> getConnectedDeviceInfo() {
if (_selectedDevice.value == null) {
return {
'connected': false,
'device': null,
'deviceType': _deviceType.value.toString().split('.').last
};
}
// 判断设备是否在已连接列表中
bool isConnected = _connectedDevices.any(
(d) => d.remoteId.str == _selectedDevice.value!.remoteId.str
);
// 判断设备是否在已配对列表中
bool isPaired = _systemPairedDevices.any(
(d) => d.remoteId.str == _selectedDevice.value!.remoteId.str
);
final deviceInfo = {
'connected': isConnected,
'paired': isPaired,
'device': {
'name': _selectedDevice.value!.platformName.isEmpty ?
'未命名设备' : _selectedDevice.value!.platformName,
'id': _selectedDevice.value!.remoteId.str,
'isConnected': isConnected,
'isPaired': isPaired,
},
'deviceType': _deviceType.value.toString().split('.').last
};
return deviceInfo;
}
/// 获取已配对设备的信息
Map<String, dynamic> getPairedDeviceInfo(BluetoothDevice device) {
if (device == null) {
return {
'connected': false,
'paired': false,
'device': null,
'deviceType': DeviceType.unknown.toString().split('.').last
};
}
// 判断设备是否在已连接列表中
bool isConnected = _connectedDevices.any(
(d) => d.remoteId.str == device.remoteId.str
);
// 确定设备类型
DeviceType type = DeviceType.unknown;
final String deviceName = device.platformName.toLowerCase();
// 音频设备关键词
final List<String> audioKeywords = [
'headphone', 'earphone', 'earbuds', 'headset',
'speaker', 'audio', '耳机', '音箱', '音响'
];
bool hasAudioKeyword = audioKeywords.any((keyword) => deviceName.contains(keyword));
if (hasAudioKeyword || isConnected) {
type = DeviceType.classicAudio;
} else {
type = DeviceType.ble;
}
final deviceInfo = {
'connected': isConnected,
'paired': true,
'device': {
'name': device.platformName.isEmpty ?
'未命名设备' : device.platformName,
'id': device.remoteId.str,
'isConnected': isConnected,
'isPaired': true,
},
'deviceType': type.toString().split('.').last
};
return deviceInfo;
}
/// 设置选中的设备(不进行连接操作)
void setSelectedDevice(BluetoothDevice device) {
if (device != null) {
_selectedDevice.value = device;
_determineDeviceType(device);
_updateLastConnectedDeviceInfo();
Logger.info('已设置选中设备: ${device.platformName}');
}
}
/// 更新最后连接的设备信息
void _updateLastConnectedDeviceInfo() {
if (_selectedDevice.value != null) {
_lastConnectedDeviceInfo.value = getConnectedDeviceInfo();
}
}
/// 查找系统已连接设备
Future<BluetoothDevice?> findConnectedSystemDevice() async {
if (!_permissionsGranted.value || !_isOn.value) {
Logger.warning('蓝牙未开启或缺少权限,无法查找系统连接设备');
return null;
}
try {
// 刷新系统设备列表(避免重复刷新)
if (!_isRefreshingDevices) {
await _refreshSystemDevices();
}
// 如果没有已连接设备,直接返回null
if (_connectedDevices.isEmpty) {
Logger.info('未找到已连接的蓝牙设备');
return null;
}
// 优先找出可能的音频设备
BluetoothDevice? audioDevice = _findAudioDevice(_connectedDevices);
BluetoothDevice device = audioDevice ?? _connectedDevices.first;
// 自动选择设备
_selectedDevice.value = device;
_determineDeviceType(device);
_connectionState.value = BluetoothConnectionState.connected;
_updateLastConnectedDeviceInfo();
Logger.info('找到已连接的蓝牙设备: ${device.platformName}');
return device;
} catch (e) {
Logger.error('查找系统已连接设备失败: $e');
return null;
}
}
/// 清理资源
@override
void onClose() {
// 取消所有订阅
_stateSubscription?.cancel();
super.onClose();
}
}

452
lib/data/services/classic_bluetooth_service.dart

@ -0,0 +1,452 @@
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:app_settings/app_settings.dart';
import '../../core/utils/logger.dart';
/// 蓝牙设备信息
class BluetoothDevice {
final String name;
final String address;
final String type;
BluetoothDevice({
required this.name,
required this.address,
required this.type
});
@override
String toString() => 'BluetoothDevice(name: $name, address: $address, type: $type)';
}
/// 蓝牙事件类型
enum BluetoothEventType {
/// 设备已连接
deviceConnected,
/// 设备已断开
deviceDisconnected,
/// 蓝牙状态改变(开启/关闭)
bluetoothStateChanged
}
/// 蓝牙连接状态事件
class BluetoothConnectionEvent {
/// 事件类型
final BluetoothEventType type;
/// 相关设备(可能为空,比如蓝牙状态改变事件)
final BluetoothDevice? device;
/// 蓝牙状态(仅适用于bluetoothStateChanged事件)
final String? state;
/// 事件发生时间戳
final int timestamp;
BluetoothConnectionEvent({
required this.type,
this.device,
this.state,
required this.timestamp,
});
factory BluetoothConnectionEvent.fromMap(Map<dynamic, dynamic> map) {
BluetoothEventType eventType;
switch (map['type']) {
case 'deviceConnected':
eventType = BluetoothEventType.deviceConnected;
break;
case 'deviceDisconnected':
eventType = BluetoothEventType.deviceDisconnected;
break;
case 'bluetoothStateChanged':
eventType = BluetoothEventType.bluetoothStateChanged;
break;
default:
throw ArgumentError('未知事件类型: ${map['type']}');
}
final deviceMap = map['device'] as Map<dynamic, dynamic>?;
final BluetoothDevice? device = deviceMap != null
? BluetoothDevice(
name: deviceMap['name'] ?? '未知设备',
address: deviceMap['address'] ?? '',
type: deviceMap['type'] ?? 'unknown'
)
: null;
return BluetoothConnectionEvent(
type: eventType,
device: device,
state: map['state'] as String?,
timestamp: map['timestamp'] as int? ?? DateTime.now().millisecondsSinceEpoch,
);
}
@override
String toString() {
switch (type) {
case BluetoothEventType.deviceConnected:
return '蓝牙设备已连接: ${device?.name}';
case BluetoothEventType.deviceDisconnected:
return '蓝牙设备已断开: ${device?.name}';
case BluetoothEventType.bluetoothStateChanged:
return '蓝牙状态改变: $state';
}
}
}
/// 经典蓝牙服务
class ClassicBluetoothService extends GetxService {
static ClassicBluetoothService get to => Get.find<ClassicBluetoothService>();
// 注意: MainActivity.kt中定义的通道名称为 com.deep_voice.classic_bluetooth
final MethodChannel _channel = const MethodChannel('com.deep_voice.classic_bluetooth');
final EventChannel _eventChannel = const EventChannel('com.deep_voice.classic_bluetooth_events');
/// 蓝牙事件流控制器
final _connectionEventController = StreamController<BluetoothConnectionEvent>.broadcast();
/// 蓝牙事件流
Stream<BluetoothConnectionEvent> get connectionEvents => _connectionEventController.stream;
/// 当前连接的设备
final currentDevice = Rx<BluetoothDevice?>(null);
/// 已连接设备列表
final connectedDevices = <BluetoothDevice>[].obs;
/// 蓝牙是否可用
final _isBluetoothEnabled = true.obs;
bool get isBluetoothEnabled => _isBluetoothEnabled.value;
/// 是否正在加载
final isLoading = false.obs;
/// 错误信息
final errorMessage = ''.obs;
/// 蓝牙状态变化事件流
StreamSubscription? _bluetoothStateListener;
/// 设备连接回调
final List<Function(BluetoothDevice)> _deviceConnectedCallbacks = [];
/// 设备断开回调
final List<Function(BluetoothDevice)> _deviceDisconnectedCallbacks = [];
/// 蓝牙状态变化回调
final List<Function(String)> _bluetoothStateChangedCallbacks = [];
/// 服务是否已初始化
bool _isInitialized = false;
ClassicBluetoothService() {
// 创建服务时不立即初始化,避免还没有原生支持时就调用方法
Logger.info('创建 ClassicBluetoothService 实例');
}
/// 初始化蓝牙服务
/// 必须在使用任何方法前调用此方法确保通道已设置
Future<bool> initialize() async {
if (_isInitialized) {
Logger.info('ClassicBluetoothService 已经初始化');
return true;
}
try {
Logger.info('正在初始化 ClassicBluetoothService...');
// 检查通道是否可用
try {
// 调用一个简单方法检查通道是否已设置
await _channel.invokeMethod<bool>('isBluetoothEnabled');
Logger.info('蓝牙方法通道可用');
} catch (e) {
Logger.error('蓝牙方法通道不可用: $e');
// 这里不要立即返回false,尝试继续设置其他内容
}
// 设置事件监听器
_setupBluetoothStateListener();
_isInitialized = true;
Logger.info('ClassicBluetoothService 初始化成功');
return true;
} catch (e) {
Logger.error('初始化 ClassicBluetoothService 失败: $e');
errorMessage.value = '初始化蓝牙服务失败';
return false;
}
}
/// 检查并请求蓝牙权限
Future<bool> checkAndRequestPermissions() async {
try {
// 首先确保服务已初始化
if (!_isInitialized && !(await initialize())) {
Logger.error('无法初始化蓝牙服务');
errorMessage.value = '无法初始化蓝牙服务';
return false;
}
// 检查蓝牙相关权限
Map<Permission, PermissionStatus> statuses = await [
Permission.bluetooth,
Permission.bluetoothConnect,
// Permission.bluetoothScan,
// Permission.location,
Permission.microphone,
].request();
// 检查蓝牙连接权限
if (statuses[Permission.bluetoothConnect] != PermissionStatus.granted) {
Logger.error('蓝牙连接权限被拒绝');
errorMessage.value = '蓝牙连接权限被拒绝';
return false;
}
return true;
} catch (e) {
Logger.error('检查蓝牙权限失败: $e');
errorMessage.value = '检查蓝牙权限失败';
return false;
}
}
/// 刷新已连接的蓝牙设备
Future<bool> refreshConnectedDevices() async {
try {
// 首先确保服务已初始化
if (!_isInitialized && !(await initialize())) {
Logger.error('无法初始化蓝牙服务');
errorMessage.value = '无法初始化蓝牙服务';
return false;
}
errorMessage.value = '';
// 获取A2DP设备
final a2dpDevices = await getConnectedA2dpDevices();
Logger.info('已连接的A2DP设备: ${a2dpDevices.length}');
// 获取耳机设备
final headsetDevices = await getConnectedHeadsetDevices();
Logger.info('已连接的耳机设备: ${headsetDevices.length}');
// 更新设备列表
connectedDevices.value = [...a2dpDevices, ...headsetDevices];
// 更新当前设备
if (connectedDevices.isNotEmpty && currentDevice.value == null) {
currentDevice.value = connectedDevices.first;
}
return connectedDevices.isNotEmpty;
} catch (e) {
Logger.error('刷新已连接设备失败: $e');
errorMessage.value = '获取设备信息失败';
return false;
}
}
/// 获取已连接的A2DP设备(音频设备)
Future<List<BluetoothDevice>> getConnectedA2dpDevices() async {
try {
// 确保服务已初始化
if (!_isInitialized && !(await initialize())) {
Logger.error('无法初始化蓝牙服务');
return [];
}
final result = await _channel.invokeMethod('getConnectedA2dpDevices');
Logger.info('获取已连接的A2DP设备结果: $result');
if (result is List) {
final devices = result.map((device) => BluetoothDevice(
name: device['name'] ?? '未知设备',
address: device['address'] ?? '',
type: 'a2dp'
)).toList();
return devices;
}
return [];
} catch (e) {
Logger.error('获取已连接的A2DP设备失败: $e');
return [];
}
}
/// 获取已连接的耳机设备
Future<List<BluetoothDevice>> getConnectedHeadsetDevices() async {
try {
// 确保服务已初始化
if (!_isInitialized && !(await initialize())) {
Logger.error('无法初始化蓝牙服务');
return [];
}
final result = await _channel.invokeMethod('getConnectedHeadsetDevices');
Logger.info('获取已连接的耳机设备结果: $result');
if (result is List) {
final devices = result.map((device) => BluetoothDevice(
name: device['name'] ?? '未知设备',
address: device['address'] ?? '',
type: 'headset'
)).toList();
return devices;
}
return [];
} catch (e) {
Logger.error('获取已连接的耳机设备失败: $e');
return [];
}
}
/// 设置蓝牙状态监听
void _setupBluetoothStateListener() {
try {
_bluetoothStateListener = _eventChannel
.receiveBroadcastStream()
.listen((event) {
Logger.info('收到蓝牙事件: $event');
if (event is Map) {
final type = event['type'] as String?;
if (type == 'stateChanged') {
final state = event['state'] as String?;
if (state != null) {
_handleBluetoothStateChange(state);
}
} else if (type == 'deviceConnected') {
final deviceMap = event['device'] as Map?;
if (deviceMap != null) {
final device = BluetoothDevice(
name: deviceMap['name'] ?? '未知设备',
address: deviceMap['address'] ?? '',
type: deviceMap['type'] ?? 'unknown'
);
_handleDeviceConnected(device);
}
} else if (type == 'deviceDisconnected') {
final deviceMap = event['device'] as Map?;
if (deviceMap != null) {
final device = BluetoothDevice(
name: deviceMap['name'] ?? '未知设备',
address: deviceMap['address'] ?? '',
type: deviceMap['type'] ?? 'unknown'
);
_handleDeviceDisconnected(device);
}
}
}
}, onError: (error) {
Logger.error('蓝牙状态监听错误: $error');
});
} catch (e) {
Logger.error('设置蓝牙状态监听失败: $e');
}
}
/// 处理蓝牙状态变化
void _handleBluetoothStateChange(String state) {
Logger.info('蓝牙状态变化: $state');
// 如果蓝牙关闭,清除设备信息
if (state == 'off') {
connectedDevices.clear();
currentDevice.value = null;
}
// 通知所有回调
for (var callback in _bluetoothStateChangedCallbacks) {
callback(state);
}
}
/// 处理设备连接
void _handleDeviceConnected(BluetoothDevice device) {
Logger.info('设备已连接: ${device.name}');
// 更新设备列表
if (!connectedDevices.any((d) => d.address == device.address)) {
connectedDevices.add(device);
}
// 更新当前设备
currentDevice.value = device;
// 通知所有回调
for (var callback in _deviceConnectedCallbacks) {
callback(device);
}
}
/// 处理设备断开
void _handleDeviceDisconnected(BluetoothDevice device) {
Logger.info('设备已断开: ${device.name}');
// 更新设备列表
connectedDevices.removeWhere((d) => d.address == device.address);
// 如果是当前设备,清除当前设备
if (currentDevice.value?.address == device.address) {
currentDevice.value = connectedDevices.isEmpty ? null : connectedDevices.first;
}
// 通知所有回调
for (var callback in _deviceDisconnectedCallbacks) {
callback(device);
}
}
/// 注册设备连接回调
void onDeviceConnected(Function(BluetoothDevice) callback) {
_deviceConnectedCallbacks.add(callback);
}
/// 注册设备断开回调
void onDeviceDisconnected(Function(BluetoothDevice) callback) {
_deviceDisconnectedCallbacks.add(callback);
}
/// 注册蓝牙状态变化回调
void onBluetoothStateChanged(Function(String) callback) {
_bluetoothStateChangedCallbacks.add(callback);
}
/// 打开蓝牙设置
Future<bool> openBluetoothSettings() async {
try {
await AppSettings.openAppSettings();
return true;
} catch (e) {
Logger.error('打开蓝牙设置失败: $e');
return false;
}
}
@override
void onClose() {
// 取消蓝牙状态监听
_bluetoothStateListener?.cancel();
// 清除回调
_deviceConnectedCallbacks.clear();
_deviceDisconnectedCallbacks.clear();
_bluetoothStateChangedCallbacks.clear();
super.onClose();
}
}

3
lib/main.dart

@ -73,8 +73,7 @@ class MainApp extends StatelessWidget {
translations: AppTranslations(),
locale: const Locale('zh', 'CN'),
fallbackLocale: const Locale('en', 'US'),
// 注意:VoiceInteractionService 在 HomeController 的 onInit 方法中初始化
// 不要在这里重复初始化,以避免多次注册事件监听器
);
},
);

571
lib/modules/home/controllers/home_controller.dart

@ -1,10 +1,11 @@
import 'package:get/get.dart';
import '../../../routes/app_routes.dart';
import '../../../data/services/bluetooth_service.dart';
import '../../../data/services/classic_bluetooth_service.dart' as classic;
import '../../../core/utils/logger.dart';
import 'package:flutter_blue_plus/flutter_blue_plus.dart' hide BluetoothService;
import 'package:flutter_blue_plus/flutter_blue_plus.dart' hide BluetoothDevice;
import 'package:flutter/material.dart';
import '../../../data/services/voice_interaction_service.dart';
import 'package:permission_handler/permission_handler.dart';
// 连接状态枚举
enum ConnectionStatus {
disconnected, // 未连接
@ -16,6 +17,9 @@ enum ConnectionStatus {
class HomeController extends GetxController {
static HomeController get to => Get.find();
// 经典蓝牙服务
late classic.ClassicBluetoothService _bluetoothService;
// 设备连接状态
final _isConnected = false.obs;
bool get isConnected => _isConnected.value;
@ -24,495 +28,228 @@ class HomeController extends GetxController {
final _connectionStatus = ConnectionStatus.disconnected.obs;
ConnectionStatus get connectionStatus => _connectionStatus.value;
// 电池状态
// 电池状态(模拟数据)
final _deviceBattery = Rx<int?>(null);
int? get deviceBattery => _deviceBattery.value;
// 固件版本
// 固件版本(模拟数据)
final String firmwareVersion = 'v2.4.1';
// 蓝牙设备信息
final _deviceInfo = <String, dynamic>{}.obs;
Map<String, dynamic> get deviceInfo => _deviceInfo.value;
// 蓝牙服务
BluetoothService? _bluetoothService;
// 连接错误信息
final _connectionErrorMsg = ''.obs;
String get connectionErrorMsg => _connectionErrorMsg.value;
// 当前连接的经典蓝牙设备
final _connectedClassicDevices = <classic.BluetoothDevice>[].obs;
List<classic.BluetoothDevice> get connectedClassicDevices => _connectedClassicDevices;
@override
void onInit() {
super.onInit();
// 立即加载缓存的设备信息并显示
_loadCachedDeviceInfo();
// 先进行简单初始化,避免立即进行蓝牙操作
_initBasicServices();
// 初始化蓝牙服务和状态监控
_initBluetoothService();
// 延迟初始化蓝牙相关功能,让UI先渲染完成
Future.delayed(const Duration(milliseconds: 1200), () {
_initBluetoothService();
});
// 电池模拟更新(实际应用中可以通过蓝牙获取)
_simulateBatteryUpdate();
}
// 立即加载缓存的设备信息
void _loadCachedDeviceInfo() {
// 初始化蓝牙服务
void _initBluetoothService() {
try {
// 1. 首先检查是否有传入的设备信息(从PairingController传入)
if (Get.arguments != null && Get.arguments is Map<String, dynamic>) {
Logger.info('使用传入的设备信息');
_deviceInfo.assignAll(Get.arguments);
_updateConnectionStatus();
update(); // 立即更新UI
return;
}
// 获取经典蓝牙服务实例
_bluetoothService = Get.find<classic.ClassicBluetoothService>();
// 2. 如果没有传入信息,尝试从BluetoothService获取上次连接的设备信息
if (Get.isRegistered<BluetoothService>()) {
BluetoothService bluetoothService = Get.find<BluetoothService>();
if (bluetoothService.lastConnectedDeviceInfo.isNotEmpty) {
Logger.info('使用BluetoothService中存储的上次连接设备信息');
_deviceInfo.assignAll(bluetoothService.lastConnectedDeviceInfo);
_updateConnectionStatus();
update(); // 立即更新UI
return;
}
}
// 更新当前连接状态
_updateConnectionStatus();
// 3. 如果上述都没有,显示未连接状态
Logger.info('没有找到缓存的设备信息,显示未连接状态');
_connectionStatus.value = ConnectionStatus.disconnected;
_isConnected.value = false;
update(); // 立即更新UI
} catch (e) {
Logger.error('加载缓存设备信息失败: $e');
}
}
// 基础服务初始化,不涉及蓝牙等耗时操作
void _initBasicServices() {
try {
// 初始化语音交互服务并启动服务
Future.microtask(() async {
// 先初始化服务
await VoiceInteractionService.to.initialize();
// 启动服务
bool serviceStarted = await VoiceInteractionService.to.startService();
if (serviceStarted) {
Logger.info('语音交互服务已成功启动');
} else {
Logger.error('语音交互服务启动失败');
}
});
// 检查是否有传入的蓝牙设备信息
if (Get.arguments != null && Get.arguments is Map<String, dynamic>) {
_deviceInfo.assignAll(Get.arguments);
_updateConnectionStatus();
}
// 监听蓝牙连接状态变化
_setupBluetoothListeners();
// 模拟电池状态更新
_simulateBatteryUpdates();
} catch (e) {
Logger.error('初始化基础服务失败: $e');
}
}
// 初始化蓝牙服务
void _initBluetoothService() {
try {
if (Get.isRegistered<BluetoothService>()) {
_bluetoothService = Get.find<BluetoothService>();
// 直接获取当前设备信息
_updateDeviceInfoFromService();
// 监听连接状态变化
ever(_bluetoothService!.connectionStateObs, (state) {
Logger.info('蓝牙连接状态变化: $state');
_processConnectionStateChange(state);
});
// 监听选中设备变化
ever(_bluetoothService!.selectedDeviceObs, (device) {
Logger.info('蓝牙选中设备变化: ${device?.platformName ?? "无设备"}');
_updateDeviceInfoFromService();
});
// 监听最后连接的设备信息变化
ever(_bluetoothService!.lastConnectedDeviceInfoObs, (info) {
Logger.info('最后连接的设备信息变化');
if (info.isNotEmpty) {
_deviceInfo.assignAll(info);
_updateConnectionStatus();
update(); // 通知UI刷新
}
});
}
} catch (e) {
Logger.error('获取蓝牙服务失败: $e');
_connectionErrorMsg.value = '获取蓝牙服务失败';
update(); // 通知UI刷新
Logger.error('初始化蓝牙服务失败: $e');
_connectionStatus.value = ConnectionStatus.failed;
}
}
// 从蓝牙服务更新设备信息
void _updateDeviceInfoFromService() {
if (_bluetoothService != null) {
// 获取当前设备信息
Map<String, dynamic> info = _bluetoothService!.lastConnectedDeviceInfo;
if (info.isNotEmpty) {
_deviceInfo.assignAll(info);
_updateConnectionStatus();
update(); // 通知UI刷新
} else if (_bluetoothService!.selectedDevice != null) {
// 如果最后连接的设备信息为空,但有选中的设备,获取当前连接的设备信息
_deviceInfo.assignAll(_bluetoothService!.getConnectedDeviceInfo());
_updateConnectionStatus();
update(); // 通知UI刷新
// 电池电量模拟更新
void _simulateBatteryUpdate() {
// 每5分钟更新一次电池电量(模拟)
Future.delayed(const Duration(seconds: 2), () {
if (isConnected) {
final timestamp = DateTime.now().millisecondsSinceEpoch;
_deviceBattery.value = 50 + (timestamp % 50); // 50-99的随机值
} else {
_deviceBattery.value = null;
}
}
}
// 检查并更新当前连接状态
void _checkAndUpdateCurrentConnectionState() {
if (_bluetoothService != null) {
_updateDeviceInfoFromService();
}
}
// 处理连接状态变化
void _processConnectionStateChange(BluetoothConnectionState state) {
// 从蓝牙服务获取最新设备信息
_updateDeviceInfoFromService();
switch (state) {
case BluetoothConnectionState.connected:
_connectionStatus.value = ConnectionStatus.connected;
_connectionErrorMsg.value = '';
_isConnected.value = true;
break;
case BluetoothConnectionState.disconnected:
_connectionStatus.value = ConnectionStatus.disconnected;
_isConnected.value = false;
break;
case BluetoothConnectionState.connecting:
_connectionStatus.value = ConnectionStatus.connecting;
break;
default:
// 其他状态暂不处理
break;
}
update(); // 通知UI刷新
}
// 显示连接成功消息
void _showConnectionSuccessMessage() {
if (_deviceInfo.isNotEmpty && _deviceInfo['device'] != null) {
String deviceName = _deviceInfo['device']['name'] ?? '未命名设备';
Get.snackbar(
'连接成功',
'已成功连接到设备: $deviceName',
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.green[100],
colorText: Colors.green[800],
duration: const Duration(seconds: 2),
);
}
// 递归调用实现定时更新
_simulateBatteryUpdate();
});
}
// 更新连接状态
void _updateConnectionStatus() {
if (_deviceInfo.containsKey('connected') || _deviceInfo.containsKey('paired')) {
// 检查是否已连接
bool isConnected = _deviceInfo['connected'] ?? false;
// 检查是否已配对
bool isPaired = _deviceInfo['paired'] ?? false;
// 如果已连接,设置为已连接状态
if (isConnected) {
_isConnected.value = true;
_connectionStatus.value = ConnectionStatus.connected;
}
// 如果已配对但未连接,也视为已连接状态(简化处理)
else if (isPaired) {
_isConnected.value = true;
_connectionStatus.value = ConnectionStatus.connected;
}
// 如果既未连接也未配对,设置为未连接状态
else {
_isConnected.value = false;
_connectionStatus.value = ConnectionStatus.disconnected;
}
// 检查当前设备
final currentDevice = _bluetoothService.currentDevice.value;
if (currentDevice != null) {
_isConnected.value = true;
_connectionStatus.value = ConnectionStatus.connected;
} else {
_isConnected.value = false;
_connectionStatus.value = ConnectionStatus.disconnected;
}
}
// 模拟电池状态更新
void _simulateBatteryUpdates() {
// 不再获取设备电量
_deviceBattery.value = 0;
Logger.info('不再获取设备电量');
}
// 获取设备电量
Future<void> _fetchBatteryLevel() async {
// 不再获取设备电量
_deviceBattery.value = 0;
Logger.info('不再获取设备电量');
}
// 更新设备电量信息(提供给UI手动刷新使用)
Future<void> refreshBatteryLevel() async {
await _fetchBatteryLevel();
Get.snackbar(
'提示',
'电量信息已更新',
snackPosition: SnackPosition.TOP,
duration: const Duration(seconds: 1),
);
}
// 获取格式化后的电量显示文本
String getBatteryText() {
if (_deviceBattery.value == null) {
return '电量未知';
} else {
return '${_deviceBattery.value}%';
}
}
// 探索AI功能
void exploreAIFeatures() {
Get.toNamed(Routes.chat, arguments: {'agentId': 'personal_assistant'});
}
// 更新固件
void updateFirmware() {
Get.snackbar(
'提示',
'开始下载固件更新...',
snackPosition: SnackPosition.TOP,
);
// 模拟更新过程
// 在真实应用中,这里应该有固件更新的实际逻辑
}
// 打开降噪功能
void toggleNoiseReduction() {
Get.snackbar(
'提示',
'降噪功能已开启',
snackPosition: SnackPosition.TOP,
);
}
// 音效设置
void openSoundEffects() {
Get.snackbar(
'提示',
'音效设置功能尚未实现',
snackPosition: SnackPosition.TOP,
);
}
// 环境音功能
void toggleAmbientSound() {
Get.snackbar(
'提示',
'环境音模式已开启',
snackPosition: SnackPosition.TOP,
);
}
// 收藏的设置
void openFavorites() {
Get.snackbar(
'提示',
'收藏功能尚未实现',
snackPosition: SnackPosition.TOP,
);
}
// 打开设置页面
void openSettings() {
Get.toNamed(Routes.settings);
}
// 重新扫描设备
void rescanDevices() {
if (_bluetoothService != null) {
Get.toNamed(Routes.pairing);
} else {
Get.snackbar(
'提示',
'蓝牙服务不可用',
snackPosition: SnackPosition.TOP,
);
}
}
// 获取设备名称
String getDeviceName() {
if (_deviceInfo.isEmpty || _deviceInfo['device'] == null) {
return '未连接设备';
}
final device = _deviceInfo['device'] as Map<String, dynamic>;
return device['name'] ?? '未命名设备';
}
// 获取设备ID
String getDeviceId() {
if (_deviceInfo.isEmpty || _deviceInfo['device'] == null) {
return '';
}
final device = _deviceInfo['device'] as Map<String, dynamic>;
return device['id'] ?? '';
update(); // 通知UI刷新
}
// 获取设备信号强度
String getSignalStrength() {
if (_deviceInfo.isEmpty || _deviceInfo['device'] == null) {
return '无信号';
}
final device = _deviceInfo['device'] as Map<String, dynamic>;
final rssi = device['rssi'];
// 设置蓝牙监听器
void _setupBluetoothListeners() {
// 监听设备连接
_bluetoothService.onDeviceConnected((device) {
Logger.info('监听到蓝牙设备连接: ${device.name}');
_isConnected.value = true;
_connectionStatus.value = ConnectionStatus.connected;
update();
});
if (rssi == null) {
return '未知';
}
// 监听设备断开
_bluetoothService.onDeviceDisconnected((device) {
Logger.info('监听到蓝牙设备断开: ${device.name}');
_isConnected.value = false;
_connectionStatus.value = ConnectionStatus.disconnected;
_deviceBattery.value = null;
update();
});
// RSSI通常在-100到0之间,值越大信号越强
if (rssi > -60) {
return '强';
} else if (rssi > -80) {
return '中';
} else {
return '弱';
}
// 监听蓝牙状态变化
_bluetoothService.onBluetoothStateChanged((state) {
Logger.info('监听到蓝牙状态变化: $state');
if (state == 'off') {
_isConnected.value = false;
_connectionStatus.value = ConnectionStatus.disconnected;
_deviceBattery.value = null;
update();
}
});
}
// 刷新设备信息
Future<void> refreshDeviceInfo() async {
try {
// 检查蓝牙是否可用
if (_bluetoothService == null) {
Get.snackbar(
'提示',
'蓝牙服务不可用',
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red[100],
colorText: Colors.red[800],
);
return;
}
// 更新连接状态为正在连接,但保持当前设备信息显示
// 设置状态为连接中
_connectionStatus.value = ConnectionStatus.connecting;
update(); // 立即更新UI状态
update();
// 从BluetoothService获取最新设备信息
_updateDeviceInfoFromService();
// 刷新设备列表
final hasDevices = await _bluetoothService.refreshConnectedDevices();
// 刷新电量信息
await _fetchBatteryLevel();
// 更新状态
_updateConnectionStatus();
// 根据当前状态给出提示
if (_isConnected.value) {
Get.snackbar(
'提示',
'设备信息已更新',
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.green[100],
colorText: Colors.green[800],
);
// 根据结果更新状态
if (!hasDevices) {
Logger.info('刷新设备:未找到设备');
// 这里可以设置状态变量而不是使用snackbar
_connectionErrorMsg.value = _bluetoothService.errorMessage.value.isEmpty
? '未找到已连接的设备'
: _bluetoothService.errorMessage.value;
} else {
Get.snackbar(
'提示',
'无法获取设备信息,请确保设备已连接',
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.amber[100],
colorText: Colors.amber[800],
);
_connectionErrorMsg.value = '';
Logger.info('刷新设备:成功');
}
} catch (e) {
Logger.error('刷新设备信息失败: $e');
_connectionErrorMsg.value = '刷新设备信息失败: ${e.toString()}';
update(); // 确保状态更新
Get.snackbar(
'提示',
'刷新设备信息失败',
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.red[100],
colorText: Colors.red[800],
);
_connectionStatus.value = ConnectionStatus.failed;
_connectionErrorMsg.value = '无法获取设备信息';
update();
}
}
// 获取设备状态文本
String getConnectionStatusText() {
// 检查是否已配对但未连接
if (_deviceInfo.containsKey('paired') && _deviceInfo['paired'] == true &&
(_deviceInfo['connected'] == false || _deviceInfo['connected'] == null)) {
return "已配对";
}
// 重新扫描设备
void rescanDevices() {
Get.toNamed(Routes.pairing);
}
// 打开翻译功能
void openTranslation() {
Get.toNamed(Routes.translation);
}
// 打开AI伴侣功能
void openAIGirlfriend() {
Get.toNamed(Routes.chat);
}
// 展示AI功能
void exploreAIFeatures() {
// 简单实现,直接跳转到AI聊天页面
openAIGirlfriend();
}
// 获取连接状态对应的颜色
Color getConnectionStatusColor() {
switch (_connectionStatus.value) {
case ConnectionStatus.connected:
return "已连接";
return Colors.green;
case ConnectionStatus.connecting:
return "连接中...";
case ConnectionStatus.disconnected:
return "未连接";
return Colors.amber;
case ConnectionStatus.failed:
return "连接失败";
return Colors.red;
case ConnectionStatus.disconnected:
default:
return Colors.grey;
}
}
// 获取连接状态颜色
Color getConnectionStatusColor() {
// 检查是否已配对但未连接
if (_deviceInfo.containsKey('paired') && _deviceInfo['paired'] == true &&
(_deviceInfo['connected'] == false || _deviceInfo['connected'] == null)) {
return Colors.orange; // 已配对但未连接使用橙色
}
// 获取连接状态对应的文本
String getConnectionStatusText() {
switch (_connectionStatus.value) {
case ConnectionStatus.connected:
return Colors.green;
return '已连接';
case ConnectionStatus.connecting:
return Colors.blue;
case ConnectionStatus.disconnected:
return Colors.red;
return '连接中...';
case ConnectionStatus.failed:
return Colors.red;
return '连接失败';
case ConnectionStatus.disconnected:
default:
return '未连接';
}
}
// 快捷入口方法
// 打开同声翻译页面
void openTranslation() {
Get.toNamed(Routes.translation);
// 获取设备名称
String getDeviceName() {
final device = _bluetoothService.currentDevice.value;
return device?.name ?? '未知设备';
}
// 打开AI女友页面
void openAIGirlfriend() {
Get.toNamed(Routes.chat, arguments: {'agentId': 'cyber_girlfriend'});
// 获取信号强度(模拟数据)
String getSignalStrength() {
if (!_isConnected.value) {
return '无信号';
}
// 简单模拟信号强度
final random = DateTime.now().millisecondsSinceEpoch % 3;
switch (random) {
case 0:
return '良好';
case 1:
return '很好';
default:
return '极佳';
}
}
// 打开AI助理(已有方法 exploreAIFeatures)
// 获取设备ID
String getDeviceId() {
final device = _bluetoothService.currentDevice.value;
return device?.address ?? '未知';
}
}

366
lib/modules/pairing/controllers/pairing_controller.dart

@ -1,303 +1,185 @@
import 'package:get/get.dart';
import 'dart:async';
import '../../../routes/app_routes.dart';
import '../../../data/services/bluetooth_service.dart';
import 'package:flutter_blue_plus/flutter_blue_plus.dart' hide BluetoothService;
import '../../../data/services/classic_bluetooth_service.dart' as classic;
import '../../../core/utils/logger.dart';
import 'package:permission_handler/permission_handler.dart';
import 'package:flutter/material.dart';
import 'package:permission_handler/permission_handler.dart';
/// 简化的配对控制器,仅用于显示已连接或配对的蓝牙耳机信息
/// 简化的配对控制器,仅用于显示已连接经典蓝牙设备和处理基本配对流程
class PairingController extends GetxController {
// 蓝牙服务
late BluetoothService _bluetoothService;
static PairingController get to => Get.find();
// 设备信息
final _deviceInfo = Rx<Map<String, dynamic>>({});
Map<String, dynamic> get deviceInfo => _deviceInfo.value;
// 经典蓝牙服务
late classic.ClassicBluetoothService _bluetoothService;
// 蓝牙状态
final _isBluetoothAvailable = false.obs;
final _isBluetoothOn = false.obs;
bool get isBluetoothAvailable => _isBluetoothAvailable.value;
bool get isBluetoothOn => _isBluetoothOn.value;
// 是否开始搜索
final _isSearching = false.obs;
bool get isSearching => _isSearching.value;
// 错误消息
final _errorMessage = ''.obs;
String get errorMessage => _errorMessage.value;
// 设备连接状态
final _isConnecting = false.obs;
bool get isConnecting => _isConnecting.value;
// 已连接的设备列表
final _connectedDevices = <BluetoothDevice>[].obs;
List<BluetoothDevice> get connectedDevices => _connectedDevices;
// 蓝牙服务是否可用
final _isBluetoothServiceAvailable = true.obs;
bool get isBluetoothServiceAvailable => _isBluetoothServiceAvailable.value;
// 已配对的设备列表
final _pairedDevices = <BluetoothDevice>[].obs;
List<BluetoothDevice> get pairedDevices => _pairedDevices;
// 当前连接设备
Rx<classic.BluetoothDevice?> get currentDevice => _bluetoothService.currentDevice;
// 状态描述(用于UI显示)
final _statusDescription = ''.obs;
String get statusDescription => _statusDescription.value;
// 连接设备列表
RxList<classic.BluetoothDevice> get connectedDevices => _bluetoothService.connectedDevices;
// 错误信息
RxString get errorMessage => _bluetoothService.errorMessage;
// 防止重复刷新的标志
bool _isRefreshing = false;
// 连接设备列表(兼容旧代码)
List<classic.BluetoothDevice> get connectedClassicDevices =>
_bluetoothService.currentDevice.value != null
? [_bluetoothService.currentDevice.value!]
: [];
// 上次刷新时间
DateTime? _lastRefreshTime;
// 状态描述(用于UI显示)
final statusDescription = '正在初始化...'.obs;
// 加载状态
final isLoading = false.obs;
@override
void onInit() {
super.onInit();
_initializeProcess();
_initialize();
}
// 初始化流程
Future<void> _initializeProcess() async {
_updateStatus('正在初始化...');
try {
// 获取或初始化蓝牙服务
await _getBluetoothService();
// 检查权限并获取设备信息
await _checkAndRequestPermissions();
} catch (e) {
Logger.error('初始化过程失败: $e');
_updateStatus('初始化失败');
_setError('初始化蓝牙服务失败,请重试');
}
@override
void onReady() {
super.onReady();
// 设置连接监听器,确保在页面完全加载后设置,避免过早监听可能导致的问题
_setupDeviceListeners();
}
// 获取蓝牙服务
Future<void> _getBluetoothService() async {
/// 设置设备监听器
void _setupDeviceListeners() {
try {
// 检查是否已注册
if (Get.isRegistered<BluetoothService>()) {
_bluetoothService = Get.find<BluetoothService>();
} else {
// 初始化并注册服务
_bluetoothService = await Get.put(BluetoothService()).init();
}
// 设置蓝牙状态监听
_setupBluetoothListeners();
if (!_isBluetoothServiceAvailable.value) return;
// 获取设备信息
_refreshDeviceInfo();
// 监听设备连接事件
_bluetoothService.onDeviceConnected((device) {
Logger.info('检测到蓝牙设备连接: ${device.name}');
// 更新状态并跳转到主页
statusDescription.value = '已连接到设备: ${device.name}';
// 立即跳转到主页
_navigateToHome();
});
} catch (e) {
Logger.error('获取蓝牙服务失败: $e');
throw Exception('获取蓝牙服务失败: $e');
Logger.error('设置设备监听器失败: $e');
}
}
// 设置蓝牙状态监听
void _setupBluetoothListeners() {
// 监听蓝牙可用性
_isBluetoothAvailable.value = _bluetoothService.isAvailable;
ever(_bluetoothService.isAvailableObs, (bool available) {
_isBluetoothAvailable.value = available;
if (!available) {
_updateStatus('蓝牙不可用');
_setError('蓝牙不可用,请检查设备是否支持蓝牙功能');
}
});
// 监听蓝牙开关状态
_isBluetoothOn.value = _bluetoothService.isOn;
ever(_bluetoothService.isOnObs, (bool on) {
_isBluetoothOn.value = on;
if (on) {
// 蓝牙开启时,延迟一下再刷新设备信息,避免频繁刷新
Future.delayed(const Duration(milliseconds: 500), () {
_refreshDeviceInfo();
});
} else {
_updateStatus('蓝牙已关闭');
_setError('蓝牙已关闭,请开启蓝牙后再试');
}
});
// 监听已连接设备变化
_connectedDevices.value = _bluetoothService.connectedDevices;
ever(_bluetoothService.connectedDevicesObs, (List<BluetoothDevice> devices) {
_connectedDevices.value = devices;
// 只有当设备列表不为空时才刷新设备信息
if (devices.isNotEmpty) {
// 延迟一下再刷新,避免频繁刷新
Future.delayed(const Duration(milliseconds: 500), () {
_refreshDeviceInfo();
});
}
});
// 监听已配对设备变化
_pairedDevices.value = _bluetoothService.systemPairedDevices;
/// 立即跳转到主页
void _navigateToHome() {
Get.offAllNamed(Routes.home);
}
// 检查并请求权限
Future<void> _checkAndRequestPermissions() async {
_updateStatus('正在请求权限...');
/// 完整的初始化流程
Future<void> _initialize() async {
try {
bool hasPermissions = await _bluetoothService.requestPermissions();
isLoading.value = true;
statusDescription.value = '正在初始化...';
// 1. 获取蓝牙服务
_bluetoothService = Get.find<classic.ClassicBluetoothService>();
if (!hasPermissions) {
// 权限被拒绝
_handlePermissionDenied();
// 确保蓝牙服务已初始化
if (!await _bluetoothService.initialize()) {
Logger.error('蓝牙服务初始化失败');
statusDescription.value = '蓝牙服务初始化失败';
_isBluetoothServiceAvailable.value = false;
isLoading.value = false;
return;
}
// 权限已授予,检查蓝牙状态
if (!_bluetoothService.isOn) {
_updateStatus('蓝牙已关闭');
_setError('蓝牙已关闭,请开启蓝牙后再试');
// 2. 检查权限
statusDescription.value = '正在检查权限...';
final hasPermission = await _bluetoothService.checkAndRequestPermissions();
if (!hasPermission) {
statusDescription.value = '缺少蓝牙权限';
isLoading.value = false;
return;
}
// 刷新设备信息
_refreshDeviceInfo();
} catch (e) {
Logger.error('权限请求失败: $e');
_updateStatus('权限请求失败');
_setError('权限请求失败,请手动授予蓝牙和位置权限');
}
}
// 处理权限被拒绝的情况
void _handlePermissionDenied() async {
// 检查是否永久拒绝
bool isPermanentlyDenied = false;
if (GetPlatform.isAndroid) {
PermissionStatus bluetoothScan = await Permission.bluetoothScan.status;
PermissionStatus bluetoothConnect = await Permission.bluetoothConnect.status;
PermissionStatus location = await Permission.location.status;
// 3. 刷新设备列表
statusDescription.value = '正在搜索设备...';
await refreshConnectedDevices();
// 如果任一权限被永久拒绝
isPermanentlyDenied =
bluetoothScan.isPermanentlyDenied ||
bluetoothConnect.isPermanentlyDenied ||
location.isPermanentlyDenied;
}
_updateStatus('缺少必要权限');
if (isPermanentlyDenied) {
_setError('缺少必要权限,您已永久拒绝权限,请前往系统设置中手动开启');
// 显示前往设置的提示
_showGoToSettingsDialog();
} else {
_setError('缺少必要权限,请授予蓝牙和位置权限以继续');
// 4. 处理结果
if (_bluetoothService.connectedDevices.isNotEmpty) {
statusDescription.value = '找到已连接的设备';
// 延迟跳转到主页
_autoNavigateToHome();
} else {
// 没有找到设备
statusDescription.value = '未找到已连接的蓝牙设备';
}
} catch (e) {
Logger.error('初始化配对控制器失败: $e');
statusDescription.value = '初始化失败';
_isBluetoothServiceAvailable.value = false;
} finally {
isLoading.value = false;
}
}
// 显示前往设置的对话框
void _showGoToSettingsDialog() {
Get.dialog(
AlertDialog(
title: const Text('需要权限'),
content: const Text('蓝牙搜索需要特定权限,请前往系统设置开启'),
actions: [
TextButton(
onPressed: () => Get.back(),
child: const Text('稍后'),
),
TextButton(
onPressed: () {
Get.back();
openAppSettings();
},
child: const Text('前往设置'),
),
],
),
barrierDismissible: false,
);
}
// 刷新设备信息
Future<void> _refreshDeviceInfo() async {
// 防止重复刷新
if (_isRefreshing) return;
_isRefreshing = true;
if (!_isBluetoothOn.value) {
_updateStatus('蓝牙已关闭');
_deviceInfo.value = {};
_isRefreshing = false;
return;
}
/// 刷新设备
Future<void> refreshConnectedDevices() async {
try {
// 更新已配对设备列表
_pairedDevices.value = _bluetoothService.systemPairedDevices;
isLoading.value = true;
statusDescription.value = '正在搜索设备...';
// 查找系统已连接设备
BluetoothDevice? device = await _bluetoothService.findConnectedSystemDevice();
final hasDevices = await _bluetoothService.refreshConnectedDevices();
if (device != null) {
// 已连接设备
_deviceInfo.value = _bluetoothService.getConnectedDeviceInfo();
_updateStatus('已连接到蓝牙设备');
_goToHomePage();
} else if (_bluetoothService.lastConnectedDeviceInfo.isNotEmpty) {
// 使用上次连接的设备信息
_deviceInfo.value = _bluetoothService.lastConnectedDeviceInfo;
_updateStatus('使用上次连接的设备信息');
_goToHomePage();
} else if (_pairedDevices.isNotEmpty) {
// 使用已配对设备
BluetoothDevice pairedDevice = _pairedDevices.first;
_deviceInfo.value = _bluetoothService.getPairedDeviceInfo(pairedDevice);
_bluetoothService.setSelectedDevice(pairedDevice);
_updateStatus('已找到配对设备');
_goToHomePage();
if (hasDevices) {
statusDescription.value = '找到已连接的设备';
// 延迟跳转到主页
_autoNavigateToHome();
} else {
// 没有找到任何设备
_deviceInfo.value = {};
_updateStatus('未找到已连接或配对的蓝牙设备');
// 没有找到设备
statusDescription.value = '未找到已连接的蓝牙设备';
}
} catch (e) {
Logger.error('刷新设备信息失败: $e');
_updateStatus('获取设备信息失败');
Logger.error('刷新设备失败: $e');
statusDescription.value = '刷新设备失败';
} finally {
_isRefreshing = false;
isLoading.value = false;
}
}
// 更新状态描述
void _updateStatus(String description) {
_statusDescription.value = description;
Logger.info('状态更新: $description');
}
// 设置错误信息
void _setError(String message) {
_errorMessage.value = message;
Logger.warning('错误: $message');
/// 自动跳转到主页
void _autoNavigateToHome() {
if (_bluetoothService.connectedDevices.isEmpty) return;
Logger.info('已找到连接的设备,自动跳转到主页');
Future.delayed(const Duration(milliseconds: 500), () {
Get.offAllNamed(Routes.home);
});
}
// 跳转到主页
void _goToHomePage() {
Get.offAllNamed(Routes.home, arguments: _deviceInfo.value);
/// 打开系统蓝牙设置
void openBluetoothSettings() {
_bluetoothService.openBluetoothSettings();
}
// 刷新搜索
void refreshSearch() {
// 防止频繁刷新,限制最小刷新间隔为2秒
final now = DateTime.now();
if (_lastRefreshTime != null &&
now.difference(_lastRefreshTime!).inSeconds < 2) {
return;
}
_lastRefreshTime = now;
_refreshDeviceInfo();
/// 刷新设备(兼容旧代码)
Future<void> refreshDevices() async {
await refreshConnectedDevices();
}
// 跳过连接
/// 跳过连接
void skipConnecting() {
Get.offAllNamed(Routes.home);
}

489
lib/modules/pairing/views/pairing_view.dart

@ -4,7 +4,6 @@ import 'package:flutter_screenutil/flutter_screenutil.dart';
import '../controllers/pairing_controller.dart';
import '../../../core/widgets/common_bottom_nav.dart';
import '../../../core/themes/app_theme.dart';
import 'package:permission_handler/permission_handler.dart';
class PairingView extends GetView<PairingController> {
const PairingView({Key? key}) : super(key: key);
@ -16,15 +15,10 @@ class PairingView extends GetView<PairingController> {
return Scaffold(
backgroundColor: isDarkMode ? Colors.black : Colors.white,
body: SafeArea(
child: Stack(
children: [
// 主要内容
_buildMainContent(isDarkMode),
],
),
child: _buildMainContent(isDarkMode),
),
bottomNavigationBar: Opacity(
opacity: 0.5, // 保持半透明效果
opacity: 0.5,
child: const CommonBottomNav(currentIndex: 0),
),
);
@ -32,40 +26,57 @@ class PairingView extends GetView<PairingController> {
// 主要内容
Widget _buildMainContent(bool isDarkMode) {
return Padding(
padding: EdgeInsets.fromLTRB(24.w, 40.h, 24.w, 24.h),
return Container(
width: double.infinity,
height: MediaQuery.of(Get.context!).size.height,
padding: EdgeInsets.symmetric(horizontal: 24.w),
// 添加SingleChildScrollView以防止溢出
child: SingleChildScrollView(
child: Column(
children: [
// 标题部分
_buildHeader(isDarkMode),
// 设备动画
SizedBox(height: 40.h),
_buildDeviceAnimation(),
// 搜索状态
SizedBox(height: 40.h),
_buildSearchStatus(isDarkMode),
// 底部按钮
SizedBox(height: 60.h),
_buildBottomButtons(isDarkMode),
SizedBox(height: 80.h), // 留出底部导航的空间
],
physics: BouncingScrollPhysics(),
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: MediaQuery.of(Get.context!).size.height - 100.h, // 减去底部导航和状态栏高度
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
SizedBox(height: 20.h), // 顶部间距
// 标题部分
_buildHeader(isDarkMode),
SizedBox(height: 20.h),
// 设备动画 - 更大尺寸
_buildDeviceAnimation(),
SizedBox(height: 20.h),
// 状态信息
_buildStatusInfo(isDarkMode),
SizedBox(height: 40.h),
// 底部按钮
_buildActionButtons(isDarkMode),
SizedBox(height: 20.h),
],
),
),
),
);
}
// 标题部分
// 头部标题
Widget _buildHeader(bool isDarkMode) {
return Column(
children: [
Text(
'DeepVoice',
style: TextStyle(
fontSize: 24.sp,
fontSize: 30.sp,
fontWeight: FontWeight.bold,
color: isDarkMode ? Colors.white : Colors.black,
),
@ -74,8 +85,8 @@ class PairingView extends GetView<PairingController> {
Text(
'连接世界,超越声音',
style: TextStyle(
fontSize: 14.sp,
color: isDarkMode ? Colors.white70 : Colors.black54,
fontSize: 16.sp,
color: isDarkMode ? Colors.white60 : Colors.black54,
),
),
],
@ -84,298 +95,236 @@ class PairingView extends GetView<PairingController> {
// 设备动画
Widget _buildDeviceAnimation() {
return SizedBox(
return Container(
width: 240.w,
height: 240.w,
child: Stack(
alignment: Alignment.center,
children: [
// 脉动动画背景
_buildPulseBackground(),
// 脉搏背景 - 更显眼的渐变效果
TweenAnimationBuilder<double>(
tween: Tween<double>(begin: 0.8, end: 1.1),
duration: const Duration(seconds: 2),
curve: Curves.easeInOut,
builder: (context, value, child) {
return Container(
width: 200.w * value,
height: 200.w * value,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Color(0xFF0066CC).withOpacity(0.3),
Color(0xFF5E5CE6).withOpacity(0.3),
],
),
shape: BoxShape.circle,
),
);
},
onEnd: () {
// 重新开始动画
},
),
// 耳机图片
_buildHeadphoneImage(),
// 旋转边框
SizedBox(
width: 100.w,
height: 100.w,
child: CircularProgressIndicator(
strokeWidth: 4.w,
valueColor: AlwaysStoppedAnimation<Color>(
Color(0xFF0066CC),
),
backgroundColor: Colors.grey.withOpacity(0.2),
),
),
// 旋转加载指示器
_buildLoadingIndicator(),
],
),
);
}
// 脉动背景
Widget _buildPulseBackground() {
return TweenAnimationBuilder<double>(
tween: Tween<double>(begin: 0.8, end: 1.1),
duration: const Duration(seconds: 2),
curve: Curves.easeInOut,
builder: (context, value, child) {
return Transform.scale(
scale: value,
child: Container(
width: 200.w,
height: 200.w,
// 设备图标
Container(
width: 120.w,
height: 120.w,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
const Color(0xFF0066CC).withOpacity(0.2),
const Color(0xFF5E5CE6).withOpacity(0.2),
Color(0xFF0066CC),
Color(0xFF5E5CE6),
],
),
shape: BoxShape.circle,
),
child: Icon(
Icons.headphones,
color: Colors.white,
size: 60.w,
),
),
);
},
);
}
// 耳机图片
Widget _buildHeadphoneImage() {
return Container(
width: 140.w,
height: 140.w,
decoration: BoxDecoration(
gradient: const LinearGradient(
begin: Alignment.topLeft,
end: Alignment.bottomRight,
colors: [
Color(0xFF0066CC),
Color(0xFF5E5CE6),
],
),
shape: BoxShape.circle,
),
child: Icon(
Icons.headset,
size: 80.sp,
color: Colors.white,
),
);
}
// 加载指示器
Widget _buildLoadingIndicator() {
return SizedBox(
width: 180.w,
height: 180.w,
child: CircularProgressIndicator(
strokeWidth: 4.w,
valueColor: const AlwaysStoppedAnimation<Color>(Color(0xFF0066CC)),
backgroundColor: const Color(0xFF0066CC).withOpacity(0.1),
],
),
);
}
// 搜索状态
Widget _buildSearchStatus(bool isDarkMode) {
// 状态信息
Widget _buildStatusInfo(bool isDarkMode) {
return Obx(() {
final String status = controller.statusDescription;
final bool hasDevice = controller.deviceInfo.isNotEmpty && controller.deviceInfo['device'] != null;
final bool hasError = controller.errorMessage.isNotEmpty;
// 已找到设备
if (hasDevice) {
return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.check_circle, color: Colors.green, size: 20.sp),
SizedBox(width: 8.w),
Text(
'设备信息获取成功',
style: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.w600,
color: Colors.green,
),
),
],
),
SizedBox(height: 8.h),
Text(
'正在跳转到主页...',
style: TextStyle(
fontSize: 14.sp,
color: isDarkMode ? Colors.white70 : Colors.black54,
),
),
],
);
}
final status = controller.statusDescription.value;
final isLoading = controller.isLoading.value;
// 有错误信息
if (hasError) {
// 蓝牙关闭错误
if (status.contains('蓝牙已关闭')) {
return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.bluetooth_disabled, color: Colors.orange, size: 20.sp),
SizedBox(width: 8.w),
Text(
'蓝牙已关闭',
style: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.w600,
color: Colors.orange,
),
),
],
),
SizedBox(height: 8.h),
Text(
controller.errorMessage,
style: TextStyle(
fontSize: 14.sp,
color: isDarkMode ? Colors.white70 : Colors.black54,
),
textAlign: TextAlign.center,
),
],
);
}
// 权限错误
if (status.contains('权限')) {
return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.no_encryption, color: Colors.orange, size: 20.sp),
SizedBox(width: 8.w),
Text(
'缺少必要权限',
style: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.w600,
color: Colors.orange,
),
),
],
),
SizedBox(height: 8.h),
Text(
controller.errorMessage,
style: TextStyle(
fontSize: 14.sp,
color: isDarkMode ? Colors.white70 : Colors.black54,
),
textAlign: TextAlign.center,
),
SizedBox(height: 16.h),
ElevatedButton(
onPressed: () => openAppSettings(),
style: ElevatedButton.styleFrom(
backgroundColor: Colors.orange,
foregroundColor: Colors.white,
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8.r),
),
),
child: const Text('打开设置'),
),
],
);
}
// 其他错误
return Column(
children: [
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.error, color: Colors.red, size: 20.sp),
SizedBox(width: 8.w),
Text(
status.isEmpty ? '未找到设备' : status,
style: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.w600,
color: Colors.red,
),
),
],
),
SizedBox(height: 8.h),
Text(
controller.errorMessage.isEmpty ?
'请确保耳机已开机并在附近' : controller.errorMessage,
style: TextStyle(
fontSize: 14.sp,
color: isDarkMode ? Colors.white70 : Colors.black54,
),
textAlign: TextAlign.center,
),
],
);
}
// 默认状态(正在搜索/初始化)
return Column(
children: [
// 状态文本 - 更大更醒目
Text(
status,
style: TextStyle(
fontSize: 18.sp,
fontSize: 20.sp,
fontWeight: FontWeight.w600,
color: isDarkMode ? Colors.white : Colors.black,
),
textAlign: TextAlign.center,
),
SizedBox(height: 8.h),
SizedBox(height: 10.h),
// 子状态提示文本
Text(
'请打开耳机充电盒,将耳机放入您的耳朵',
style: TextStyle(
fontSize: 14.sp,
color: isDarkMode ? Colors.white70 : Colors.black54,
color: isDarkMode ? Colors.white60 : Colors.grey[600],
),
textAlign: TextAlign.center,
),
// 设备列表(有设备时才显示)
if (controller.connectedClassicDevices.isNotEmpty) ...[
SizedBox(height: 24.h),
_buildConnectedDevicesList(isDarkMode),
],
],
);
});
}
// 底部按钮
Widget _buildBottomButtons(bool isDarkMode) {
return Column(
children: [
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: controller.refreshSearch,
// 已连接设备列表
Widget _buildConnectedDevicesList(bool isDarkMode) {
return Obx(() {
final devices = controller.connectedClassicDevices;
if (devices.isEmpty) {
return const SizedBox.shrink();
}
return Container(
width: double.infinity,
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'当前已连接:',
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
color: isDarkMode ? Colors.white70 : Colors.black87,
),
),
SizedBox(height: 8.h),
...devices.map((device) => _buildDeviceItem(device, isDarkMode)),
],
),
);
});
}
// 设备项
Widget _buildDeviceItem(device, bool isDarkMode) {
return Container(
margin: EdgeInsets.only(bottom: 8.h),
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h),
decoration: BoxDecoration(
color: isDarkMode ? Colors.grey[900] : Colors.grey[100],
borderRadius: BorderRadius.circular(8.r),
),
child: Row(
children: [
Icon(
Icons.headphones,
color: isDarkMode ? Colors.white70 : Colors.black54,
size: 24.w,
),
SizedBox(width: 12.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
device.name,
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w500,
color: isDarkMode ? Colors.white : Colors.black,
),
),
SizedBox(height: 4.h),
Text(
device.address,
style: TextStyle(
fontSize: 12.sp,
color: isDarkMode ? Colors.white54 : Colors.black45,
),
),
],
),
),
Icon(
Icons.bluetooth_connected,
color: Colors.green,
size: 20.w,
),
],
),
);
}
// 操作按钮
Widget _buildActionButtons(bool isDarkMode) {
return Container(
width: double.infinity,
child: Column(
children: [
// 主按钮 - 改为更宽的圆角按钮
ElevatedButton(
onPressed: () => controller.openBluetoothSettings(),
style: ElevatedButton.styleFrom(
backgroundColor: isDarkMode ? Color(0xFF1A1A1A) : Colors.grey.withOpacity(0.1),
foregroundColor: isDarkMode ? Colors.white : Colors.black,
backgroundColor: isDarkMode ? Colors.grey[800] : Colors.grey[200],
foregroundColor: isDarkMode ? Colors.white : Colors.black87,
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.w500,
fontWeight: FontWeight.w600,
),
),
),
),
SizedBox(height: 12.h),
SizedBox(
width: double.infinity,
child: TextButton(
onPressed: controller.skipConnecting,
SizedBox(height: 12.h),
// 次要按钮
TextButton(
onPressed: () => controller.skipConnecting(),
style: TextButton.styleFrom(
foregroundColor: const Color(0xFF0066CC),
foregroundColor: Color(0xFF0066CC),
padding: EdgeInsets.symmetric(vertical: 12.h),
minimumSize: Size(double.infinity, 48.h),
),
child: Text(
'稍后再说',
@ -385,8 +334,8 @@ class PairingView extends GetView<PairingController> {
),
),
),
),
],
],
),
);
}
}

66
lib/modules/translation/views/translation_view.dart

@ -41,22 +41,25 @@ class TranslationView extends GetView<TranslationController> {
mainAxisSize: MainAxisSize.min,
children: [
// 源语言选择
GestureDetector(
onTap: () => _showLanguageSelectionDialog(true, isDarkMode),
child: Container(
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
decoration: BoxDecoration(
color: isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[100],
borderRadius: BorderRadius.circular(16.r),
),
child: Obx(() => Text(
controller.sourceLanguage.value.split(' ')[0], // Just show the Chinese part
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
color: isDarkMode ? Colors.white : Colors.black,
Flexible(
child: GestureDetector(
onTap: () => _showLanguageSelectionDialog(true, isDarkMode),
child: Container(
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
decoration: BoxDecoration(
color: isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[100],
borderRadius: BorderRadius.circular(16.r),
),
)),
child: Obx(() => Text(
controller.sourceLanguage.value.split(' ')[0], // Just show the Chinese part
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
color: isDarkMode ? Colors.white : Colors.black,
),
overflow: TextOverflow.ellipsis,
)),
),
),
),
@ -79,22 +82,25 @@ class TranslationView extends GetView<TranslationController> {
),
// 目标语言选择
GestureDetector(
onTap: () => _showLanguageSelectionDialog(false, isDarkMode),
child: Container(
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
decoration: BoxDecoration(
color: isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[100],
borderRadius: BorderRadius.circular(16.r),
),
child: Obx(() => Text(
controller.targetLanguage.value.split(' ')[0], // Just show the English part
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
color: isDarkMode ? Colors.white : Colors.black,
Flexible(
child: GestureDetector(
onTap: () => _showLanguageSelectionDialog(false, isDarkMode),
child: Container(
padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h),
decoration: BoxDecoration(
color: isDarkMode ? Colors.white.withOpacity(0.1) : Colors.grey[100],
borderRadius: BorderRadius.circular(16.r),
),
)),
child: Obx(() => Text(
controller.targetLanguage.value.split(' ')[0], // Just show the English part
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.w600,
color: isDarkMode ? Colors.white : Colors.black,
),
overflow: TextOverflow.ellipsis,
)),
),
),
),
],

1
pubspec.yaml

@ -46,6 +46,7 @@ dependencies:
audio_waveforms: ^1.3.0
font_awesome_flutter: ^10.8.0
lottie: ^3.3.1
app_settings: 5.2.0
dev_dependencies:
flutter_test:

Loading…
Cancel
Save