wolfplus 2 years ago
parent
commit
55b1d5082f
  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. 563
      lib/modules/home/controllers/home_controller.dart
  8. 364
      lib/modules/pairing/controllers/pairing_controller.dart
  9. 475
      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.content.IntentFilter
import android.os.Handler import android.os.Handler
import android.os.Looper import android.os.Looper
import android.bluetooth.BluetoothAdapter
import android.bluetooth.BluetoothDevice
class MainActivity: FlutterActivity() { class MainActivity: FlutterActivity() {
@ -23,11 +25,15 @@ class MainActivity: FlutterActivity() {
private val AZURE_TTS_CHANNEL = "com.deep_voice.azure_tts" private val AZURE_TTS_CHANNEL = "com.deep_voice.azure_tts"
private val VOICE_INTERACTION_CHANNEL = "com.deep_voice.voice_interaction" private val VOICE_INTERACTION_CHANNEL = "com.deep_voice.voice_interaction"
private val VOICE_INTERACTION_EVENT_CHANNEL = "com.deep_voice.voice_interaction_events" 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 val TAG = "MainActivity"
private lateinit var azureAsrHelper: AzureAsrHelper private lateinit var azureAsrHelper: AzureAsrHelper
private lateinit var azureTtsHelper: AzureTtsHelper private lateinit var azureTtsHelper: AzureTtsHelper
private lateinit var classicBluetoothHelper: ClassicBluetoothHelper
private var azureAsrEventSink: EventChannel.EventSink? = null private var azureAsrEventSink: EventChannel.EventSink? = null
private var voiceInteractionEventSink: EventChannel.EventSink? = null private var voiceInteractionEventSink: EventChannel.EventSink? = null
private var bluetoothEventSink: EventChannel.EventSink? = null
// 广播接收器 // 广播接收器
private val voiceInteractionReceiver = object : BroadcastReceiver() { 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 { companion object {
// Azure 语音服务配置 // Azure 语音服务配置
@ -74,9 +149,10 @@ class MainActivity: FlutterActivity() {
Log.d(TAG, "初始化本地的 Azure 语音服务实例") Log.d(TAG, "初始化本地的 Azure 语音服务实例")
azureAsrHelper = AzureAsrHelper() azureAsrHelper = AzureAsrHelper()
azureTtsHelper = AzureTtsHelper() azureTtsHelper = AzureTtsHelper()
classicBluetoothHelper = ClassicBluetoothHelper(applicationContext)
// 注册广播接收器 // 注册广播接收器
registerVoiceInteractionReceiver() registerReceivers()
// 确保 Flutter 引擎准备好后立即设置事件通道 // 确保 Flutter 引擎准备好后立即设置事件通道
flutterEngine?.let { engine -> flutterEngine?.let { engine ->
@ -85,15 +161,21 @@ class MainActivity: FlutterActivity() {
} }
} }
/** private fun registerReceivers() {
* 注册语音交互服务的广播接收器 // 注册语音交互广播接收器
*/ val voiceInteractionFilter = IntentFilter().apply {
private fun registerVoiceInteractionReceiver() {
val intentFilter = IntentFilter().apply {
addAction(VoiceInteractionService.ACTION_RECOGNITION_STARTED) addAction(VoiceInteractionService.ACTION_RECOGNITION_STARTED)
addAction(VoiceInteractionService.ACTION_CHAT_HISTORY_UPDATED) 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) { 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) { } catch (e: Exception) {
Log.e(TAG, "设置事件通道时发生错误: ${e.message}", e) Log.e(TAG, "设置事件通道时发生错误: ${e.message}", e)
} }
@ -158,7 +260,9 @@ class MainActivity: FlutterActivity() {
* 设置方法通道 * 设置方法通道
*/ */
private fun setupMethodChannels(flutterEngine: FlutterEngine) { private fun setupMethodChannels(flutterEngine: FlutterEngine) {
// Azure ASR 方法通道 Log.d(TAG, "开始设置方法通道")
// 设置 Azure ASR 方法通道
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, AZURE_ASR_CHANNEL).setMethodCallHandler { call, result -> MethodChannel(flutterEngine.dartExecutor.binaryMessenger, AZURE_ASR_CHANNEL).setMethodCallHandler { call, result ->
when (call.method) { when (call.method) {
"initialize" -> { "initialize" -> {
@ -308,7 +412,7 @@ class MainActivity: FlutterActivity() {
} }
} }
// Azure TTS 方法通道 // 设置 Azure TTS 方法通道
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, AZURE_TTS_CHANNEL).setMethodCallHandler { call, result -> MethodChannel(flutterEngine.dartExecutor.binaryMessenger, AZURE_TTS_CHANNEL).setMethodCallHandler { call, result ->
when (call.method) { when (call.method) {
"initialize" -> { "initialize" -> {
@ -387,7 +491,7 @@ class MainActivity: FlutterActivity() {
} }
} }
// 语音交互服务通道 // 设置语音交互方法通道
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, VOICE_INTERACTION_CHANNEL).setMethodCallHandler { call, result -> MethodChannel(flutterEngine.dartExecutor.binaryMessenger, VOICE_INTERACTION_CHANNEL).setMethodCallHandler { call, result ->
when (call.method) { when (call.method) {
"startVoiceInteractionService" -> { "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 事件发送方法 // 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() { override fun onDestroy() {
// 注销广播接收器 // 注销所有广播接收器
try { try {
unregisterReceiver(voiceInteractionReceiver) unregisterReceiver(voiceInteractionReceiver)
unregisterReceiver(bluetoothStateReceiver)
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "注销广播接收器失败: ${e.message}") Log.e(TAG, "注销广播接收器失败: ${e.message}")
} }
@ -528,6 +727,7 @@ class MainActivity: FlutterActivity() {
// 释放资源 // 释放资源
azureAsrHelper.dispose() azureAsrHelper.dispose()
azureTtsHelper.dispose() azureTtsHelper.dispose()
classicBluetoothHelper.dispose()
super.onDestroy() 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/voice_interaction_service.dart';
import '../../data/services/language_manager.dart'; import '../../data/services/language_manager.dart';
import '../../data/services/volcano_ai_service.dart'; import '../../data/services/volcano_ai_service.dart';
import '../../data/services/classic_bluetooth_service.dart';
import '../../core/utils/logger.dart'; import '../../core/utils/logger.dart';
/// 初始绑定,用于管理全局依赖 /// 初始绑定,用于管理全局依赖
@ -34,5 +35,8 @@ class InitialBinding extends Bindings {
// 注意:VoiceInteractionService 在 HomeController 中初始化 // 注意:VoiceInteractionService 在 HomeController 中初始化
Get.lazyPut<VoiceInteractionService>(() => VoiceInteractionService(), fenix: true); 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(), translations: AppTranslations(),
locale: const Locale('zh', 'CN'), locale: const Locale('zh', 'CN'),
fallbackLocale: const Locale('en', 'US'), fallbackLocale: const Locale('en', 'US'),
// 注意:VoiceInteractionService 在 HomeController 的 onInit 方法中初始化
// 不要在这里重复初始化,以避免多次注册事件监听器
); );
}, },
); );

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

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

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

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

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

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

Loading…
Cancel
Save