Browse Source

bluteeth

weicu
wolfplus 2 years ago
parent
commit
13003cd286
  1. 30
      android/app/src/main/kotlin/com/example/deep_voice/MainActivity.kt
  2. 270
      lib/data/services/bluetooth_service.dart
  3. 5
      lib/modules/chat/controllers/chat_controller.dart
  4. 755
      lib/modules/chat/views/chat_view.dart
  5. 71
      lib/modules/explore/controllers/explore_controller.dart
  6. 351
      lib/modules/home/controllers/home_controller.dart
  7. 156
      lib/modules/pairing/controllers/pairing_controller.dart

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

@ -71,7 +71,7 @@ class MainActivity: FlutterActivity() {
// Azure 语音服务配置
var azureSpeechKey: String = "2K2VOHSuL8ZSEnbKgHSyTwo1ohj7C2gbxyVimsjpVUt6aETxVwlvJQQJ99BCACqBBLyXJ3w3AAAYACOGqCMj"
var azureSpeechRegion: String = "southeastasia"
var volcanoAiApiKey: String = "0fd7c93b-b9bf-4b0b-bca6-e284491792bf"
var volcanoAiApiKey: String = "168deb3d-fd0c-4912-b9f1-aaee5c6743e6"
// 标记是否已初始化
var isAzureHelpersInitialized = false
@ -158,20 +158,20 @@ class MainActivity: FlutterActivity() {
events?.success(mapOf("type" to "channelReady"))
Log.d(TAG, "已发送 channelReady 事件")
// 测试发送一个聊天记录事件
Handler(Looper.getMainLooper()).postDelayed({
if (voiceInteractionEventSink != null) {
Log.d(TAG, "发送测试聊天记录事件")
sendChatHistoryEvent(
"personal_assistant",
"这是一条测试用户消息",
"这是一条测试助手回复",
System.currentTimeMillis()
)
} else {
Log.e(TAG, "无法发送测试事件:事件通道未准备好")
}
}, 5000) // 5秒后发送测试事件
// // 测试发送一个聊天记录事件
// Handler(Looper.getMainLooper()).postDelayed({
// if (voiceInteractionEventSink != null) {
// Log.d(TAG, "发送测试聊天记录事件")
// sendChatHistoryEvent(
// "personal_assistant",
// "这是一条测试用户消息",
// "这是一条测试助手回复",
// System.currentTimeMillis()
// )
// } else {
// Log.e(TAG, "无法发送测试事件:事件通道未准备好")
// }
// }, 5000) // 5秒后发送测试事件
} catch (e: Exception) {
Log.e(TAG, "发送 channelReady 事件失败: ${e.message}", e)
}

270
lib/data/services/bluetooth_service.dart

@ -34,6 +34,9 @@ class BluetoothService extends GetxService {
// 连接状态
final _connectionState = Rx<BluetoothConnectionState>(BluetoothConnectionState.disconnected);
// 存储最后连接的设备信息,供其他控制器使用
final _lastConnectedDeviceInfo = Rx<Map<String, dynamic>>({});
// 订阅
StreamSubscription? _stateSubscription;
StreamSubscription? _scanSubscription;
@ -51,6 +54,7 @@ class BluetoothService extends GetxService {
List<ScanResult> get scanResults => _scanResults;
BluetoothDevice? get selectedDevice => _selectedDevice.value;
BluetoothConnectionState get connectionState => _connectionState.value;
Map<String, dynamic> get lastConnectedDeviceInfo => _lastConnectedDeviceInfo.value;
// Observable getters (for reactive programming)
RxBool get isAvailableObs => _isAvailable;
@ -59,6 +63,7 @@ class BluetoothService extends GetxService {
RxList<ScanResult> get scanResultsObs => _scanResults;
Rx<BluetoothDevice?> get selectedDeviceObs => _selectedDevice;
Rx<BluetoothConnectionState> get connectionStateObs => _connectionState;
Rx<Map<String, dynamic>> get lastConnectedDeviceInfoObs => _lastConnectedDeviceInfo;
/// 初始化蓝牙服务
Future<BluetoothService> init() async {
@ -66,6 +71,8 @@ class BluetoothService extends GetxService {
// 不在初始化时检查权限,改为在需要时才检查
// await _checkAndRequestPermissions();
// 在后台线程初始化蓝牙状态监听,避免主线程阻塞
Future.microtask(() {
// 监听蓝牙状态
_stateSubscription = FlutterBluePlus.adapterState.listen((state) {
_isOn.value = state == BluetoothAdapterState.on;
@ -74,21 +81,40 @@ class BluetoothService extends GetxService {
if (state == BluetoothAdapterState.on) {
// 蓝牙打开时,获取已连接设备,但不请求权限
// 只有在用户主动扫描时才请求权限
_getConnectedDevices();
// 在后台线程执行,避免主线程阻塞
Future.microtask(() => _getConnectedDevices());
} else if (state == BluetoothAdapterState.off) {
// 蓝牙关闭时,清空设备列表
_connectedDevices.clear();
_scanResults.clear();
_selectedDevice.value = null;
_lastConnectedDeviceInfo.value = {};
}
});
});
// 不在初始化时立即检查蓝牙状态,而是延迟到UI渲染完成后
// 这样可以避免启动时卡顿
Future.delayed(const Duration(milliseconds: 800), () async {
try {
// 使用超时机制,避免无限等待蓝牙
bool isOn = await FlutterBluePlus.isOn
.timeout(const Duration(seconds: 3), onTimeout: () {
Logger.error('检查蓝牙状态超时');
return false;
});
// 检查当前蓝牙状态,如果已开启则获取设备
if (await FlutterBluePlus.isOn) {
_isOn.value = true;
_isAvailable.value = true;
_getConnectedDevices();
_isOn.value = isOn;
_isAvailable.value = isOn;
if (isOn) {
// 在后台线程获取设备,避免主线程阻塞
Future.microtask(() => _getConnectedDevices());
}
} catch (e) {
Logger.error('延迟检查蓝牙状态失败: $e');
}
});
return this;
} catch (e) {
@ -114,14 +140,21 @@ class BluetoothService extends GetxService {
/// 获取已连接的设备
Future<void> _getConnectedDevices() async {
try {
// 获取已连接的设备
List<BluetoothDevice> devices = await FlutterBluePlus.connectedSystemDevices;
// 使用超时机制,避免无限等待
List<BluetoothDevice> devices = await FlutterBluePlus.connectedSystemDevices
.timeout(const Duration(seconds: 3), onTimeout: () {
Logger.error('获取已连接设备超时');
return <BluetoothDevice>[];
});
_connectedDevices.value = devices;
// 如果有已连接的设备但没有选中设备,则选中第一个
if (_connectedDevices.isNotEmpty && _selectedDevice.value == null) {
_selectedDevice.value = _connectedDevices[0];
_monitorConnectionState(_selectedDevice.value!);
// 更新最后连接的设备信息
_updateLastConnectedDeviceInfo();
}
Logger.info('已连接设备数量: ${_connectedDevices.length}');
@ -142,15 +175,17 @@ class BluetoothService extends GetxService {
}
}
/// 开始扫描附近的蓝牙设备
Future<void> startScan({Duration? timeout}) async {
if (_isScanning.value) return;
/// 开始扫描附近的蓝牙设备并尝试连接最强信号设备
Future<BluetoothDevice?> scanAndConnectToDevice({Duration? timeout}) async {
if (_isScanning.value) {
await stopScan();
}
// 清空旧的扫描结果
_scanResults.clear();
try {
// 开始扫描
// 设置扫描监听
_scanSubscription?.cancel();
_scanSubscription = FlutterBluePlus.scanResults.listen((results) {
_scanResults.value = results;
@ -161,16 +196,34 @@ class BluetoothService extends GetxService {
// 开始扫描
await FlutterBluePlus.startScan(
timeout: timeout ?? const Duration(seconds: 10),
timeout: timeout ?? const Duration(seconds: 5),
);
// 扫描完成后更新状态
// 扫描完成
_isScanning.value = false;
Logger.info('蓝牙扫描完成,发现 ${_scanResults.length} 个设备');
// 如果找到设备,尝试连接信号最强的
if (_scanResults.isNotEmpty) {
// 按信号强度排序
List<ScanResult> sortedResults = List.from(_scanResults);
sortedResults.sort((a, b) => b.rssi.compareTo(a.rssi));
// 尝试连接信号最强的设备
final topDevice = sortedResults.first.device;
Logger.info('尝试连接信号最强的设备: ${topDevice.platformName}');
bool success = await connectToDevice(topDevice);
if (success) {
return topDevice;
}
}
return null;
} catch (e) {
_isScanning.value = false;
Logger.error('蓝牙扫描失败: $e');
Logger.error('蓝牙扫描或连接失败: $e');
return null;
}
}
@ -186,66 +239,56 @@ class BluetoothService extends GetxService {
}
}
/// 连接到指定设备
/// 连接到指定的蓝牙设备 - 简化版本
Future<bool> connectToDevice(BluetoothDevice device) async {
if (_selectedDevice.value == device) {
// 如果是当前选中设备,检查连接状态
if (_connectionState.value == BluetoothConnectionState.connected) {
Logger.info('设备 ${device.platformName} 已经连接,无需重新连接');
return true;
}
}
Logger.info('尝试连接设备: ${device.platformName} (${device.remoteId.str})');
try {
// 检查设备是否已连接
bool isAlreadyConnected = false;
// 检查设备连接状态
BluetoothConnectionState state = await device.connectionState.first;
if (state == BluetoothConnectionState.connected) {
Logger.info('设备 ${device.platformName} 已处于连接状态');
isAlreadyConnected = true;
}
// 如果设备已连接,直接更新状态而不尝试连接
if (isAlreadyConnected) {
// 断开当前跟踪的设备(如果有)
if (_selectedDevice.value != null && _selectedDevice.value != device) {
_connectionSubscription?.cancel();
}
// 更新状态
_selectedDevice.value = device;
_monitorConnectionState(device);
// 如果是当前已连接设备,直接返回成功
if (_selectedDevice.value == device &&
_connectionState.value == BluetoothConnectionState.connected) {
Logger.info('设备已连接,无需重新连接');
return true;
}
// 断开当前连接
if (_selectedDevice.value != null && _selectedDevice.value != device) {
await disconnectFromDevice();
}
// 连接新设备
// 直接尝试连接
Logger.info('直接连接设备 ${device.platformName}');
await device.connect();
// 更新状态
// 更新设备信息
_selectedDevice.value = device;
_monitorConnectionState(device);
_updateLastConnectedDeviceInfo();
Logger.info('设备 ${device.platformName} 连接成功');
return true;
} catch (e) {
Logger.error('连接蓝牙设备失败: $e');
Logger.error('连接设备 ${device.platformName} 失败: $e');
return false;
}
}
/// 断开与当前设备的连接
Future<bool> disconnectFromDevice() async {
if (_selectedDevice.value == null) return true;
if (_selectedDevice.value == null) {
Logger.info('没有当前选中的设备,无需断开连接');
return true;
}
try {
Logger.info('正在断开设备 ${_selectedDevice.value!.platformName} 的连接');
await _selectedDevice.value!.disconnect();
Logger.info('设备连接已断开,取消连接状态监听');
_connectionSubscription?.cancel();
_connectionSubscription = null;
_selectedDevice.value = null;
// 清除最后连接的设备信息
_lastConnectedDeviceInfo.value = {};
return true;
} catch (e) {
@ -256,14 +299,35 @@ class BluetoothService extends GetxService {
/// 监控连接状态
void _monitorConnectionState(BluetoothDevice device) {
_connectionSubscription?.cancel();
Logger.info('设置设备 ${device.platformName} 的连接状态监听');
// 取消之前的监听
if (_connectionSubscription != null) {
Logger.info('取消之前的连接状态监听');
_connectionSubscription!.cancel();
_connectionSubscription = null;
}
// 设置新的监听
_connectionSubscription = device.connectionState.listen((state) {
Logger.info('设备 ${device.platformName} 连接状态变更为: $state');
_connectionState.value = state;
if (state == BluetoothConnectionState.disconnected) {
Logger.info('设备 ${device.platformName} 已断开连接');
_selectedDevice.value = null;
// 设备断开连接时,不清除最后连接的设备信息,以便下次可以重新连接
} else if (state == BluetoothConnectionState.connected) {
Logger.info('设备 ${device.platformName} 已成功连接');
// 设备连接成功时,更新最后连接的设备信息
_updateLastConnectedDeviceInfo();
} else if (state == BluetoothConnectionState.connecting) {
Logger.info('设备 ${device.platformName} 正在连接中...');
} else if (state == BluetoothConnectionState.disconnecting) {
Logger.info('设备 ${device.platformName} 正在断开连接...');
}
});
Logger.info('设备 ${device.platformName} 的连接状态监听已设置');
}
/// 获取已连接设备的信息
@ -275,7 +339,29 @@ class BluetoothService extends GetxService {
};
}
return {
final deviceInfo = {
'connected': _connectionState.value == BluetoothConnectionState.connected,
'device': {
'name': _selectedDevice.value!.platformName.isEmpty ?
'未命名设备' : _selectedDevice.value!.platformName,
'id': _selectedDevice.value!.remoteId.str,
'isConnected': _connectionState.value == BluetoothConnectionState.connected,
'rssi': _scanResults
.firstWhereOrNull((r) => r.device.remoteId == _selectedDevice.value!.remoteId)
?.rssi
}
};
// 更新最后连接的设备信息
_lastConnectedDeviceInfo.value = deviceInfo;
return deviceInfo;
}
/// 更新最后连接的设备信息
void _updateLastConnectedDeviceInfo() {
if (_selectedDevice.value != null) {
_lastConnectedDeviceInfo.value = {
'connected': _connectionState.value == BluetoothConnectionState.connected,
'device': {
'name': _selectedDevice.value!.platformName.isEmpty ?
@ -288,6 +374,90 @@ class BluetoothService extends GetxService {
}
};
}
}
/// 一个综合方法,执行自动查找并连接设备的完整流程
Future<BluetoothDevice?> autoConnectDevice() async {
try {
// 1. 先检查当前已连接设备
if (_selectedDevice.value != null &&
_connectionState.value == BluetoothConnectionState.connected) {
Logger.info('使用当前已连接设备: ${_selectedDevice.value!.platformName}');
return _selectedDevice.value;
}
// 2. 检查系统中已连接的设备
try {
List<BluetoothDevice> connectedDevices = await FlutterBluePlus.connectedSystemDevices
.timeout(const Duration(seconds: 3));
if (connectedDevices.isNotEmpty) {
// 有已连接的设备,直接使用第一个
Logger.info('发现系统已连接设备: ${connectedDevices.first.platformName}');
// 连接设备并更新服务状态
await connectToDevice(connectedDevices.first);
return connectedDevices.first;
}
} catch (e) {
Logger.error('检查系统连接设备失败: $e');
}
// 3. 尝试连接之前用过的设备(从已配对设备中寻找)
List<BluetoothDevice> pairedDevices = await FlutterBluePlus.bondedDevices;
// 如果有配对设备,尝试连接第一个
if (pairedDevices.isNotEmpty) {
Logger.info('尝试连接已配对设备: ${pairedDevices.first.platformName}');
bool success = await connectToDevice(pairedDevices.first);
if (success) {
return pairedDevices.first;
}
}
// 4. 没有找到可用设备
return null;
} catch (e) {
Logger.error('自动连接设备流程失败: $e');
return null;
}
}
/// 获取设备连接状态的简化方法 (用于兼容现有代码)
Future<BluetoothConnectionState> getReliableConnectionState(BluetoothDevice device) async {
try {
// 直接获取设备当前状态
BluetoothConnectionState state = await device.connectionState.first
.timeout(const Duration(seconds: 2), onTimeout: () {
Logger.info('获取设备 ${device.platformName} 连接状态超时');
return BluetoothConnectionState.disconnected;
});
// 如果获取到的状态是已连接,直接返回
if (state == BluetoothConnectionState.connected) {
return BluetoothConnectionState.connected;
}
// 作为备用检查,看设备是否在系统已连接列表中
try {
List<BluetoothDevice> connectedDevices = await FlutterBluePlus.connectedSystemDevices
.timeout(const Duration(seconds: 2));
for (var connectedDevice in connectedDevices) {
if (connectedDevice.remoteId.str == device.remoteId.str) {
return BluetoothConnectionState.connected;
}
}
} catch (e) {
// 忽略检查系统设备列表的错误
}
return state;
} catch (e) {
Logger.error('获取设备 ${device.platformName} 连接状态失败: $e');
return BluetoothConnectionState.disconnected;
}
}
@override
void onClose() {

5
lib/modules/chat/controllers/chat_controller.dart

@ -257,6 +257,8 @@ class ChatController extends GetxController {
Future<void> sendMessage(String text) async {
if (text.trim().isEmpty) return;
_voiceInteractionService.pauseVoiceInteraction();
// 中断当前响应
_interruptCurrentResponse("发送新消息,中断当前响应");
@ -420,6 +422,7 @@ class ChatController extends GetxController {
await _asrService.initialize(language: 'zh-CN');
// 启动连续语音识别
// 注意:在UI更新后,使用模拟波形动画替代实际麦克风输入的波形数据
final recognitionStream = await _asrService.startContinuousRecognition();
isVoiceRecognitionActive.value = true;
@ -429,6 +432,8 @@ class ChatController extends GetxController {
// 重置临时消息索引
_tempMessageIndex = -1;
// 监听识别事件
_recognitionSubscription = recognitionStream.listen(
_handleRecognitionEvent,

755
lib/modules/chat/views/chat_view.dart

@ -8,115 +8,68 @@ import '../models/message_model.dart';
import '../../../core/theme/app_colors.dart';
import '../../../core/widgets/message_bubble.dart';
class ChatView extends StatefulWidget {
class ChatView extends GetView<ChatController> {
const ChatView({Key? key}) : super(key: key);
@override
State<ChatView> createState() => _ChatViewState();
}
class _ChatViewState extends State<ChatView> with TickerProviderStateMixin {
// 获取控制器
ChatController get controller => Get.find<ChatController>();
// 添加ScrollController用于控制滚动
final ScrollController _scrollController = ScrollController();
// 添加一个动画控制器,用于波形动画
late final AnimationController _waveAnimationController;
// 添加录音控制器,用于获取麦克风数据
late final RecorderController _recorderController;
@override
void initState() {
super.initState();
// 初始化动画控制器
_waveAnimationController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 1000),
)..repeat();
// 初始化录音控制器
_recorderController = RecorderController()
..androidEncoder = AndroidEncoder.aac
..androidOutputFormat = AndroidOutputFormat.mpeg4
..iosEncoder = IosEncoder.kAudioFormatMPEG4AAC
..sampleRate = 44100
..bitRate = 128000
..updateFrequency = const Duration(milliseconds: 50); // 更新频率
// 初始化录音控制器
_initializeRecorder();
// 监听消息列表变化,自动滚动到底部
ever(controller.messages, (_) {
// 使用Future.delayed确保在UI更新后滚动
Future.delayed(Duration(milliseconds: 50), () {
if (_scrollController.hasClients &&
_scrollController.position.maxScrollExtent > 0 &&
(_scrollController.position.maxScrollExtent - _scrollController.offset) < 200) {
_scrollController.animateTo(
_scrollController.position.maxScrollExtent,
duration: Duration(milliseconds: 100),
curve: Curves.easeOut,
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.grey[50], // 使用浅灰背景,类似HTML模板
appBar: _buildAppBar(),
body: _buildBody(),
);
}
});
});
// 监听语音识别状态变化
ever(controller.isVoiceRecognitionActive, (isActive) {
if (isActive) {
// 开始录音以获取波形数据
_recorderController.record();
} else {
// 停止录音
_recorderController.stop();
}
});
}
// 初始化录音控制器
Future<void> _initializeRecorder() async {
try {
// 请求录音权限
await _recorderController.checkPermission();
// 如果当前正在进行语音识别,开始录音
if (controller.isVoiceRecognitionActive.value) {
await _recorderController.record();
}
} catch (e) {
print('初始化录音控制器失败: $e');
}
}
@override
void dispose() {
_scrollController.dispose();
_waveAnimationController.dispose();
_recorderController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white, // 设置Scaffold背景为白色
appBar: AppBar(
// 构建AppBar
PreferredSizeWidget _buildAppBar() {
return AppBar(
elevation: 1,
backgroundColor: Colors.white,
leading: IconButton(
icon: Icon(Icons.arrow_back_ios, color: Colors.black),
icon: Icon(Icons.arrow_back_ios, color: Colors.black87, size: 20.r),
onPressed: () => Get.back(),
),
titleSpacing: 0,
title: Row(
children: [
CircleAvatar(
backgroundImage: AssetImage(controller.agent.avatarUrl),
radius: 18.r,
// AI头像
Container(
width: 40.r,
height: 40.r,
decoration: BoxDecoration(
color: AppColors.primary,
borderRadius: BorderRadius.circular(20.r),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 3,
offset: Offset(0, 1),
),
],
),
child: ClipRRect(
borderRadius: BorderRadius.circular(20.r),
child: controller.agent.avatarUrl.isNotEmpty
? Image.asset(
controller.agent.avatarUrl,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return Icon(
Icons.smart_toy_rounded,
color: Colors.white,
size: 22.r,
);
},
)
: Icon(
Icons.smart_toy_rounded,
color: Colors.white,
size: 22.r,
),
SizedBox(width: 10.w),
),
),
SizedBox(width: 12.w),
// AI名称和状态
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@ -127,19 +80,28 @@ class _ChatViewState extends State<ChatView> with TickerProviderStateMixin {
style: TextStyle(
fontSize: 16.sp,
fontWeight: FontWeight.bold,
color: Colors.black,
color: Colors.black87,
),
),
Row(
children: [
Container(
width: 6.r,
height: 6.r,
decoration: BoxDecoration(
color: Colors.green,
shape: BoxShape.circle,
),
),
SizedBox(width: 4.w),
Text(
controller.agent.description.length > 20
? '${controller.agent.description.substring(0, 20)}...'
: controller.agent.description,
'在线',
style: TextStyle(
fontSize: 12.sp,
color: Colors.grey,
color: Colors.green,
),
),
maxLines: 1,
overflow: TextOverflow.ellipsis,
],
),
],
),
@ -160,8 +122,26 @@ class _ChatViewState extends State<ChatView> with TickerProviderStateMixin {
onPressed: controller.toggleTts,
)),
// 更多选项
PopupMenuButton(
icon: Icon(Icons.more_vert, color: Colors.black),
Container(
width: 34.r,
height: 34.r,
margin: EdgeInsets.only(right: 8.w),
decoration: BoxDecoration(
color: Colors.grey[100],
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 2,
spreadRadius: 0.5,
),
],
),
child: PopupMenuButton(
icon: Icon(Icons.more_vert, color: Colors.black87, size: 20.r),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12.r),
),
itemBuilder: (context) => [
PopupMenuItem(
value: 'clear',
@ -180,17 +160,97 @@ class _ChatViewState extends State<ChatView> with TickerProviderStateMixin {
}
},
),
],
elevation: 0,
backgroundColor: Colors.white,
),
body: Container(
color: Colors.white, // 确保body背景也是白色
child: Column(
],
);
}
// 构建主体内容
Widget _buildBody() {
return Column(
children: [
// 消息列表
Expanded(
child: Obx(() => ListView.builder(
child: Container(
color: Colors.grey[50], // 使用浅灰背景
child: Obx(() => controller.messages.isEmpty
? _buildEmptyChat()
: _buildChatList()),
),
),
// 输入区域 - 根据当前模式显示不同的输入面板
Obx(() => controller.isVoiceRecognitionActive.value
? _buildVoiceInputPanel()
: _buildTextInputPanel()),
],
);
}
// 构建空聊天界面
Widget _buildEmptyChat() {
return Center(
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Container(
width: 80.r,
height: 80.r,
decoration: BoxDecoration(
color: AppColors.primary.withOpacity(0.1),
shape: BoxShape.circle,
),
child: ClipRRect(
borderRadius: BorderRadius.circular(40.r),
child: controller.agent.avatarUrl.isNotEmpty
? Image.asset(
controller.agent.avatarUrl,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return Icon(
Icons.smart_toy_rounded,
size: 40.r,
color: AppColors.primary,
);
},
)
: Icon(
Icons.smart_toy_rounded,
size: 40.r,
color: AppColors.primary,
),
),
),
SizedBox(height: 24.h),
Text(
'你好!我是${controller.agent.name}助手',
style: TextStyle(
fontSize: 18.sp,
fontWeight: FontWeight.bold,
color: Colors.black87,
),
),
SizedBox(height: 12.h),
Padding(
padding: EdgeInsets.symmetric(horizontal: 40.w),
child: Text(
'我能帮你回答问题、实时语音翻译、推荐音乐和播客等。有什么我可以帮你的吗?',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 14.sp,
color: Colors.grey[600],
height: 1.5,
),
),
),
],
),
);
}
// 构建聊天列表
Widget _buildChatList() {
return ListView.builder(
controller: controller.scrollController,
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 10.h),
itemCount: controller.messages.length,
@ -199,76 +259,237 @@ class _ChatViewState extends State<ChatView> with TickerProviderStateMixin {
cacheExtent: 1000, // 增加缓存范围,减少重建
itemBuilder: (context, index) {
final message = controller.messages[index];
return MessageBubble(
key: ObjectKey(message), // 使用ObjectKey而不是ValueKey
message: message,
isUser: message.role == 'user',
return _buildMessageItem(message);
},
);
}
// 构建消息项
Widget _buildMessageItem(Message message) {
final bool isUser = message.role == 'user';
return Padding(
padding: EdgeInsets.only(bottom: 16.h),
child: Row(
mainAxisAlignment: isUser ? MainAxisAlignment.end : MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// AI头像(非用户消息时显示)
if (!isUser)
Container(
width: 36.r,
height: 36.r,
margin: EdgeInsets.only(right: 8.w, top: 2.h),
decoration: BoxDecoration(
color: AppColors.primary,
borderRadius: BorderRadius.circular(18.r),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 2,
offset: Offset(0, 1),
),
],
),
child: ClipRRect(
borderRadius: BorderRadius.circular(18.r),
child: controller.agent.avatarUrl.isNotEmpty
? Image.asset(
controller.agent.avatarUrl,
fit: BoxFit.cover,
errorBuilder: (context, error, stackTrace) {
return Icon(
Icons.smart_toy_rounded,
color: Colors.white,
size: 20.r,
);
},
)),
)
: Icon(
Icons.smart_toy_rounded,
color: Colors.white,
size: 20.r,
),
),
),
// 输入区域 - 根据当前模式显示不同的输入面板
Obx(() => controller.isVoiceRecognitionActive.value
? _buildVoiceInputPanel()
: _buildTextInputPanel()),
// 消息气泡
Flexible(
child: Container(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h),
decoration: BoxDecoration(
color: isUser ? AppColors.primary : Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(isUser ? 16.r : 0),
topRight: Radius.circular(isUser ? 0 : 16.r),
bottomLeft: Radius.circular(16.r),
bottomRight: Radius.circular(16.r),
),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 3,
spreadRadius: 0.5,
offset: Offset(0, 1),
),
],
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 消息内容
if (message.content.isNotEmpty)
SelectableText(
message.content,
style: TextStyle(
color: isUser ? Colors.white : Colors.black87,
fontSize: 15.sp,
height: 1.5,
letterSpacing: 0.2,
),
textAlign: TextAlign.left,
maxLines: null,
enableInteractiveSelection: true,
textWidthBasis: TextWidthBasis.longestLine,
),
// 加载指示器
if (message.isLoading)
Padding(
padding: EdgeInsets.only(top: message.content.isNotEmpty ? 8.h : 0),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
SizedBox(
width: 12.w,
height: 12.h,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(
isUser ? Colors.white : AppColors.primary,
),
),
),
SizedBox(width: 8.w),
Text(
'思考中...',
style: TextStyle(
color: isUser ? Colors.white : Colors.black54,
fontSize: 12.sp,
),
),
],
),
),
],
),
),
),
// 用户头像(用户消息时显示)
if (isUser)
Container(
width: 36.r,
height: 36.r,
margin: EdgeInsets.only(left: 8.w, top: 2.h),
decoration: BoxDecoration(
color: Colors.grey[200],
borderRadius: BorderRadius.circular(18.r),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 2,
offset: Offset(0, 1),
),
],
),
child: Center(
child: Icon(
Icons.person,
color: Colors.grey[600],
size: 20.r,
),
),
),
],
),
);
}
// 构建文本输入面板
Widget _buildTextInputPanel() {
// 创建一个TextEditingController来控制输入框
final TextEditingController textController = TextEditingController(text: controller.inputText.value);
final textController = TextEditingController(text: controller.inputText.value);
// 监听输入变化
textController.addListener(() {
controller.inputText.value = textController.text;
});
return Container(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h),
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 10.h),
decoration: BoxDecoration(
color: Colors.white,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 5,
color: Colors.black.withOpacity(0.06),
blurRadius: 10,
offset: Offset(0, -1),
),
],
),
child: SafeArea(
child: Row(
children: [
// 语音输入按钮
IconButton(
Container(
width: 40.r,
height: 40.r,
decoration: BoxDecoration(
color: Colors.grey[100],
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 3,
spreadRadius: 0.5,
),
],
),
child: IconButton(
icon: Icon(
Icons.mic_none,
color: Colors.grey,
color: Colors.grey[600],
size: 20.r,
),
padding: EdgeInsets.zero,
onPressed: controller.startVoiceRecognition,
),
),
// 文本输入框
Expanded(
child: Container(
margin: EdgeInsets.symmetric(horizontal: 8.w),
decoration: BoxDecoration(
color: Colors.grey[100],
borderRadius: BorderRadius.circular(20.r),
),
child: TextField(
controller: textController,
decoration: InputDecoration(
hintText: '发消息...',
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(20.r),
borderSide: BorderSide.none,
),
filled: true,
fillColor: Colors.grey[200],
hintText: '输入消息...',
hintStyle: TextStyle(color: Colors.grey[500]),
border: InputBorder.none,
contentPadding: EdgeInsets.symmetric(
horizontal: 16.w,
vertical: 8.h,
vertical: 10.h,
),
),
maxLines: null,
textInputAction: TextInputAction.send,
style: TextStyle(
fontSize: 15.sp,
color: Colors.black87,
),
onSubmitted: (value) {
if (value.trim().isNotEmpty) {
controller.sendMessage(value);
@ -277,55 +498,109 @@ class _ChatViewState extends State<ChatView> with TickerProviderStateMixin {
},
),
),
),
// 发送按钮
Obx(() => IconButton(
Obx(() => Container(
width: 40.r,
height: 40.r,
decoration: BoxDecoration(
color: controller.inputText.value.trim().isEmpty
? Colors.grey[100]
: AppColors.primary,
shape: BoxShape.circle,
boxShadow: controller.inputText.value.trim().isEmpty
? [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 3,
spreadRadius: 0.5,
),
]
: [
BoxShadow(
color: AppColors.primary.withOpacity(0.3),
blurRadius: 5,
spreadRadius: 0.5,
),
],
),
child: IconButton(
icon: Icon(
Icons.send,
size: 18.r,
color: controller.inputText.value.trim().isEmpty
? Colors.grey
: AppColors.primary,
? Colors.grey[500]
: Colors.white,
),
padding: EdgeInsets.zero,
onPressed: controller.inputText.value.trim().isEmpty
? null
: () {
controller.sendMessage(controller.inputText.value);
textController.clear();
},
),
)),
],
),
),
);
}
// 构建语音输入面板
Widget _buildVoiceInputPanel() {
final screenWidth = Get.width;
return Container(
width: screenWidth,
decoration: const BoxDecoration(
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color(0xFFFFFFFF), // 纯白色开始
Color(0xFFE8D5F5), // 更深的紫色结束
Colors.white,
Color(0xFFE8D5F5),
],
stops: [0.3, 1.0], // 调整颜色分布,让白色占据更多空间
stops: [0.3, 1.0],
),
// boxShadow: [
// BoxShadow(
// color: Colors.black.withOpacity(0.06),
// blurRadius: 10,
// offset: Offset(0, -1),
// ),
// ],
),
child: SafeArea(
child: SizedBox(
height: 100.h,
child: Center(
height: 80.h,
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 20.w),
padding: EdgeInsets.symmetric(horizontal: 30.w),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// 左侧麦克风按钮
_buildMicrophoneButton(),
// 语音录制按钮
Container(
width: 60.r,
height: 60.r,
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.1),
blurRadius: 8,
spreadRadius: 1,
),
],
),
child: Center(
child: Obx(() => Icon(
controller.isListening.value ? Icons.mic : Icons.mic_none,
color: controller.isListening.value ? AppColors.primary : Colors.grey.shade400,
size: 28.r,
)),
),
),
// 中间音频波形和状态文本
Expanded(
@ -333,75 +608,76 @@ class _ChatViewState extends State<ChatView> with TickerProviderStateMixin {
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 音频波形
_buildAudioWaveform(),
SizedBox(height: 10.h),
// 状态文本
Obx(() => Text(
controller.isSpeechDetected.value ? '正在聆听...' : '你可以开始说话',
style: TextStyle(
fontSize: 16.sp,
color: Colors.grey.shade700,
fontWeight: FontWeight.w500,
),
)),
],
),
),
// 音频波形区域
Container(
height: 50.h,
child: Obx(() {
final isSpeaking = controller.isSpeechDetected.value;
return Center(
child: Container(
width: 220.w,
height: 30.h,
child: Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(
23,
(index) {
// 随机高度模拟波形
double height = isSpeaking
? (Random().nextDouble() * 20.0 + 5.0).h
: 2.h;
// 右侧关闭按钮
_buildCloseButton(),
],
return SizedBox(
width: 6.w,
child: Center(
child: AnimatedContainer(
duration: Duration(milliseconds: 500),
width: 2.5.w,
height: height,
margin: EdgeInsets.symmetric(horizontal: 1.5.w),
decoration: BoxDecoration(
color: isSpeaking
? AppColors.primary
: Colors.grey.shade400,
borderRadius: BorderRadius.circular(2.r),
),
),
),
);
},
),
),
),
);
}
}),
),
SizedBox(height: 8.h),
// 麦克风按钮
Widget _buildMicrophoneButton() {
return Container(
width: 60.w,
height: 60.w,
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
spreadRadius: 1,
// 语音状态文本
Obx(() => Text(
controller.isSpeechDetected.value ? '正在聆听...' : '你可以开始说话',
style: TextStyle(
fontSize: 14.sp,
color: Colors.grey.shade700,
fontWeight: FontWeight.w500,
),
)),
],
),
child: Center(
child: Obx(() => IconButton(
icon: Icon(
controller.isListening.value ? Icons.mic : Icons.mic_none,
color: controller.isListening.value ? AppColors.primary : Colors.grey.shade400,
size: 28.sp,
),
onPressed: controller.stopVoiceRecognition,
padding: EdgeInsets.zero,
)),
),
);
}
// 关闭按钮
Widget _buildCloseButton() {
return Container(
width: 60.w,
height: 60.w,
Container(
width: 60.r,
height: 60.r,
decoration: BoxDecoration(
color: Colors.white,
shape: BoxShape.circle,
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(0.05),
blurRadius: 10,
color: Colors.black.withOpacity(0.1),
blurRadius: 8,
spreadRadius: 1,
),
],
@ -411,84 +687,17 @@ class _ChatViewState extends State<ChatView> with TickerProviderStateMixin {
icon: Icon(
Icons.close,
color: Colors.grey.shade600,
size: 28.sp,
size: 28.r,
),
onPressed: controller.stopVoiceRecognition,
padding: EdgeInsets.zero,
onPressed: controller.stopVoiceRecognition,
),
),
);
}
// 音频波形
Widget _buildAudioWaveform() {
return SizedBox(
width: 220.w,
height: 50.h,
child: Obx(() {
final isSpeaking = controller.isSpeechDetected.value;
final isRec = controller.isListening.value;
if (!isRec) {
// 如果没有在录音,显示静态波形
return _buildStaticWaveform();
}
// 使用AudioWaveforms组件显示实时波形
return AudioWaveforms(
enableGesture: false,
size: Size(220.w, 50.h),
recorderController: _recorderController,
waveStyle: WaveStyle(
waveColor: isSpeaking
? AppColors.primary
: AppColors.primary.withOpacity(0.3),
extendWaveform: true,
showMiddleLine: false,
spacing: 4.0,
waveThickness: 3.0,
showDurationLabel: false,
scaleFactor: 80, // 调整波形高度
),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(12.r),
color: Colors.transparent,
),
padding: EdgeInsets.symmetric(horizontal: 8.w),
margin: EdgeInsets.only(bottom: 5.h),
);
}),
);
}
// 静态波形(当没有录音时显示)
Widget _buildStaticWaveform() {
final barCount = 23; // 条形数量
return Row(
mainAxisSize: MainAxisSize.min,
mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(
barCount,
(index) {
// 静态高度
final height = 2.h;
return SizedBox(
width: 5.5.w,
child: Center(
child: Container(
width: 2.5.w,
height: height,
margin: EdgeInsets.symmetric(horizontal: 1.2.w),
decoration: BoxDecoration(
color: Colors.grey.shade400,
borderRadius: BorderRadius.circular(2.r),
],
),
),
),
);
},
),
);
}

71
lib/modules/explore/controllers/explore_controller.dart

@ -1,5 +1,7 @@
import 'package:get/get.dart';
import 'package:flutter/material.dart';
import '../../../data/providers/agent_provider.dart';
import '../../../core/routes/app_routes.dart';
class AIFeature {
final String title;
@ -9,6 +11,8 @@ class AIFeature {
final Color endColor;
final Color iconBgColor;
final Color iconColor;
final String agentId;
final String targetRoute;
AIFeature({
required this.title,
@ -18,6 +22,8 @@ class AIFeature {
required this.endColor,
required this.iconBgColor,
required this.iconColor,
required this.agentId,
required this.targetRoute,
});
}
@ -46,13 +52,26 @@ class ExploreController extends GetxController {
// AI功能列表
final List<AIFeature> aiFeatures = [
AIFeature(
title: '同声翻译',
description: '实时翻译40+种语言',
icon: Icons.translate,
title: '全能助理',
description: '帮你解决各类问题',
icon: Icons.assistant,
startColor: Colors.blue.shade100,
endColor: Colors.blue.shade200,
iconBgColor: Colors.blue.withOpacity(0.2),
iconColor: Colors.blue.shade600,
agentId: 'personal_assistant',
targetRoute: Routes.chat,
),
AIFeature(
title: '同声翻译',
description: '实时翻译40+种语言',
icon: Icons.translate,
startColor: Colors.green.shade100,
endColor: Colors.green.shade200,
iconBgColor: Colors.green.withOpacity(0.2),
iconColor: Colors.green.shade600,
agentId: 'simultaneous_interpreter',
targetRoute: Routes.translation,
),
AIFeature(
title: 'AI女友',
@ -62,24 +81,19 @@ class ExploreController extends GetxController {
endColor: Colors.pink.shade200,
iconBgColor: Colors.pink.withOpacity(0.2),
iconColor: Colors.pink.shade600,
agentId: 'cyber_girlfriend',
targetRoute: Routes.chat,
),
AIFeature(
title: '音乐创作',
description: '用AI创作专属音乐',
icon: Icons.music_note,
startColor: Colors.green.shade100,
endColor: Colors.green.shade200,
iconBgColor: Colors.green.withOpacity(0.2),
iconColor: Colors.green.shade600,
),
AIFeature(
title: '声音克隆',
description: '创建你的数字声音',
icon: Icons.mic,
title: '健康顾问',
description: '提供科学健康建议',
icon: Icons.healing,
startColor: Colors.amber.shade100,
endColor: Colors.amber.shade200,
iconBgColor: Colors.amber.withOpacity(0.2),
iconColor: Colors.amber.shade600,
agentId: 'health_advisor',
targetRoute: Routes.chat,
),
];
@ -129,13 +143,34 @@ class ExploreController extends GetxController {
void onAIFeatureTap(int index) {
if (index < 0 || index >= aiFeatures.length) return;
final feature = aiFeatures[index];
final agent = AgentProvider.getAgentById(feature.agentId);
if (agent == null) {
Get.snackbar(
'功能',
'您选择了: ${aiFeatures[index].title}',
'错误',
'未找到对应的AI助手',
snackPosition: SnackPosition.TOP,
);
return;
}
// 实际应用中,这里应该跳转到对应的功能页面
// 根据目标路由进行跳转
if (feature.targetRoute == Routes.chat) {
// 跳转到聊天页面
Get.toNamed(feature.targetRoute, arguments: {
'agentId': agent.id,
'agentName': agent.name,
'agentVoice': agent.voice,
'agentAvatar': agent.avatarUrl,
'agentSubtitle': agent.description,
'systemPrompt': agent.systemPrompt,
'welcomeMessage': agent.welcomeMessage,
});
} else if (feature.targetRoute == Routes.translation) {
// 跳转到翻译页面
Get.toNamed(feature.targetRoute);
}
}
// 点击功能场景

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

@ -4,7 +4,7 @@ import '../../../data/services/bluetooth_service.dart';
import '../../../core/utils/logger.dart';
import 'package:flutter_blue_plus/flutter_blue_plus.dart' hide BluetoothService;
import 'package:flutter/material.dart';
import '../../../data/services/voice_interaction_service.dart';
// 连接状态枚举
enum ConnectionStatus {
disconnected, // 未连接
@ -50,17 +50,70 @@ class HomeController extends GetxController {
@override
void onInit() {
super.onInit();
// 检查是否有传入的蓝牙设备信息
// 立即加载缓存的设备信息并显示
_loadCachedDeviceInfo();
// 先进行简单初始化,避免立即进行蓝牙操作
_initBasicServices();
// 延迟初始化蓝牙相关功能,让UI先渲染完成
Future.delayed(const Duration(milliseconds: 1200), () {
_initBluetoothService();
});
}
// 立即加载缓存的设备信息
void _loadCachedDeviceInfo() {
try {
// 1. 首先检查是否有传入的设备信息(从PairingController传入)
if (Get.arguments != null && Get.arguments is Map<String, dynamic>) {
Logger.info('使用传入的设备信息');
_deviceInfo.assignAll(Get.arguments);
_updateConnectionStatus();
update(); // 立即更新UI
return;
}
// 获取蓝牙服务
_initBluetoothService();
// 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('没有找到缓存的设备信息,显示未连接状态');
_connectionStatus.value = ConnectionStatus.disconnected;
_isConnected.value = false;
update(); // 立即更新UI
} catch (e) {
Logger.error('加载缓存设备信息失败: $e');
}
}
// 基础服务初始化,不涉及蓝牙等耗时操作
void _initBasicServices() {
try {
// 初始化语音交互
Future.microtask(() => VoiceInteractionService.to.initialize());
// 检查是否有传入的蓝牙设备信息
if (Get.arguments != null && Get.arguments is Map<String, dynamic>) {
_deviceInfo.assignAll(Get.arguments);
_updateConnectionStatus();
}
// 模拟电池状态更新(不涉及设备通信)
_simulateBatteryUpdates();
} catch (e) {
Logger.error('初始化基础服务失败: $e');
}
}
// 初始化蓝牙服务
@ -69,52 +122,220 @@ class HomeController extends GetxController {
if (Get.isRegistered<BluetoothService>()) {
_bluetoothService = Get.find<BluetoothService>();
// 如果已经有连接的设备但没有设备信息
if (_deviceInfo.isEmpty && _bluetoothService!.selectedDevice != null) {
_deviceInfo.assignAll(_bluetoothService!.getConnectedDeviceInfo());
_updateConnectionStatus();
} else if (_deviceInfo.isEmpty) {
// 如果没有设备信息,尝试获取已配对设备
_checkPairedDevices();
}
// 不再重复加载设备信息,因为已经在_loadCachedDeviceInfo中加载过了
// 只进行状态同步
// 主动检查当前连接状态并更新
_checkAndUpdateCurrentConnectionState();
// 监听连接状态变化
ever(_bluetoothService!.connectionStateObs, (state) {
Logger.info('蓝牙连接状态变化: $state');
_processConnectionStateChange(state);
});
// 监听选中设备变化
ever(_bluetoothService!.selectedDeviceObs, (device) {
Logger.info('蓝牙选中设备变化: ${device?.platformName ?? "无设备"}');
if (device != null) {
_deviceInfo.assignAll(_bluetoothService!.getConnectedDeviceInfo());
_updateConnectionStatus();
// 通知UI刷新
update();
} else if (_isConnected.value) {
// 如果设备断开连接,更新状态
_isConnected.value = false;
_deviceInfo.clear();
// 不清除设备信息,以便可以显示上次连接的设备
_connectionStatus.value = ConnectionStatus.disconnected;
// 通知UI刷新
update();
}
});
// 监听最后连接的设备信息变化
ever(_bluetoothService!.lastConnectedDeviceInfoObs, (info) {
Logger.info('最后连接的设备信息变化');
if (info.isNotEmpty) {
// 只有当设备信息为空或者连接状态不是已连接时,才更新设备信息
if (_deviceInfo.isEmpty || _connectionStatus.value != ConnectionStatus.connected) {
_deviceInfo.assignAll(info);
_updateConnectionStatus();
// 通知UI刷新
update();
}
}
});
// 如果当前没有连接状态,但有设备信息,尝试在后台重新连接
if (_connectionStatus.value != ConnectionStatus.connected &&
_deviceInfo.isNotEmpty &&
_deviceInfo['device'] != null &&
_deviceInfo['device']['id'] != null) {
// 延迟后台尝试连接设备,不阻塞UI
Future.delayed(const Duration(milliseconds: 2000), () {
_reconnectToLastDevice();
});
}
// 如果没有设备信息,延迟尝试获取已配对设备
else if (_deviceInfo.isEmpty) {
// 这个操作放在最后并延迟执行,因为它可能会触发蓝牙扫描
Future.delayed(const Duration(milliseconds: 2500), () {
_checkPairedDevices();
});
}
}
} catch (e) {
Logger.error('获取蓝牙服务失败: $e');
_connectionStatus.value = ConnectionStatus.failed;
_connectionErrorMsg.value = '获取蓝牙服务失败';
// 通知UI刷新
update();
}
}
// 检查并更新当前连接状态
void _checkAndUpdateCurrentConnectionState() {
if (_bluetoothService != null && _bluetoothService!.selectedDevice != null) {
// 如果有选择的设备,使用可靠方法获取一次状态
_bluetoothService!.getReliableConnectionState(_bluetoothService!.selectedDevice!).then((state) {
Logger.info('主动检查设备连接状态: $state');
_processConnectionStateChange(state);
}).catchError((e) {
Logger.error('检查连接状态失败: $e');
});
}
}
// 后台尝试重新连接到上次的设备
Future<void> _reconnectToLastDevice() async {
try {
Logger.info('尝试重新连接到上次的设备');
_connectionStatus.value = ConnectionStatus.connecting;
update(); // 立即更新UI显示连接中状态
// 使用超时机制,防止无响应
return await Future.sync(() async {
// 获取设备ID
String deviceId = _deviceInfo['device']['id'];
Logger.info('上次连接的设备ID: $deviceId');
// 获取已配对设备,使用超时保护
Logger.info('开始获取系统已配对设备列表...');
List<BluetoothDevice> pairedDevices = await _bluetoothService!.getPairedDevices()
.timeout(const Duration(seconds: 5), onTimeout: () {
Logger.error('获取已配对设备超时');
return <BluetoothDevice>[];
});
// 记录配对设备
if (pairedDevices.isNotEmpty) {
Logger.info('发现 ${pairedDevices.length} 个已配对设备:');
for (var i = 0; i < pairedDevices.length; i++) {
Logger.info('配对设备 #$i: ${pairedDevices[i].platformName ?? "未命名设备"} (${pairedDevices[i].remoteId.str})');
}
} else {
Logger.info('未找到任何已配对设备');
}
// 查找匹配ID的设备
Logger.info('开始查找匹配ID的设备: $deviceId');
BluetoothDevice? targetDevice;
for (var device in pairedDevices) {
Logger.info('检查设备 ${device.platformName ?? "未命名设备"} (${device.remoteId.str}) 是否匹配');
if (device.remoteId.str == deviceId) {
Logger.info('找到匹配ID的设备: ${device.platformName ?? "未命名设备"}');
targetDevice = device;
break;
}
}
// 如果找到目标设备,尝试连接
if (targetDevice != null) {
Logger.info('找到目标设备: ${targetDevice.platformName ?? "未命名设备"},尝试连接');
// 直接尝试连接设备,不做复杂状态检查
Logger.info('开始连接设备 ${targetDevice?.platformName ?? "未命名设备"}');
final success = await _bluetoothService!.connectToDevice(targetDevice)
.timeout(const Duration(seconds: 5), onTimeout: () {
Logger.error('连接设备 ${targetDevice?.platformName ?? "未命名设备"} 超时');
return false;
});
if (success) {
Logger.info('重新连接到设备 ${targetDevice?.platformName ?? "未命名设备"} 成功');
_deviceInfo.assignAll(_bluetoothService!.getConnectedDeviceInfo());
_connectionStatus.value = ConnectionStatus.connected;
_isConnected.value = true;
_updateConnectionStatus();
update(); // 立即更新UI
} else {
Logger.error('重新连接到设备 ${targetDevice?.platformName ?? "未命名设备"} 失败');
_connectionStatus.value = ConnectionStatus.failed;
_connectionErrorMsg.value = '重新连接设备失败,请手动刷新或重新连接';
update(); // 立即更新UI
}
} else {
Logger.info('未找到匹配ID的设备,尝试连接第一个配对设备');
// 如果没有找到目标设备但有其他配对设备,尝试连接第一个
if (pairedDevices.isNotEmpty) {
var firstDevice = pairedDevices.first;
Logger.info('尝试连接第一个配对设备: ${firstDevice.platformName ?? "未命名设备"}');
// 直接尝试连接设备
Logger.info('开始连接设备 ${firstDevice.platformName ?? "未命名设备"}');
final success = await _bluetoothService!.connectToDevice(firstDevice)
.timeout(const Duration(seconds: 5), onTimeout: () {
Logger.error('连接设备 ${firstDevice.platformName ?? "未命名设备"} 超时');
return false;
});
if (success) {
Logger.info('自动连接到已配对设备成功: ${firstDevice.platformName ?? "未命名设备"}');
_deviceInfo.assignAll(_bluetoothService!.getConnectedDeviceInfo());
_connectionStatus.value = ConnectionStatus.connected;
_isConnected.value = true; // 直接设置连接状态
_updateConnectionStatus();
update(); // 立即更新UI
} else {
Logger.error('连接到第一个配对设备 ${firstDevice.platformName ?? "未命名设备"} 失败');
_connectionStatus.value = ConnectionStatus.failed;
_connectionErrorMsg.value = '连接设备失败,请重试';
update(); // 立即更新UI
}
} else {
Logger.info('没有找到任何可用的配对设备');
_connectionStatus.value = ConnectionStatus.disconnected;
update(); // 立即更新UI
}
}
}).timeout(const Duration(seconds: 15), onTimeout: () {
Logger.error('重新连接设备整体流程超时');
_connectionStatus.value = ConnectionStatus.failed;
_connectionErrorMsg.value = '连接超时,请手动刷新或重新连接';
update(); // 立即更新UI
});
} catch (e) {
Logger.error('重新连接到设备失败: $e');
_connectionStatus.value = ConnectionStatus.failed;
_connectionErrorMsg.value = '连接失败,请手动刷新或重新连接';
update(); // 立即更新UI
}
}
// 处理连接状态变化
void _processConnectionStateChange(BluetoothConnectionState state) {
// 强制从蓝牙服务获取最新设备信息
_deviceInfo.assignAll(_bluetoothService!.getConnectedDeviceInfo());
switch (state) {
case BluetoothConnectionState.connected:
_connectionStatus.value = ConnectionStatus.connected;
_connectionErrorMsg.value = '';
_showConnectionSuccessMessage();
_isConnected.value = true; // 直接设置连接状态为true
// _showConnectionSuccessMessage();
break;
case BluetoothConnectionState.disconnected:
_connectionStatus.value = ConnectionStatus.disconnected;
_isConnected.value = false; // 直接设置连接状态为false
break;
case BluetoothConnectionState.connecting:
_connectionStatus.value = ConnectionStatus.connecting;
@ -124,7 +345,11 @@ class HomeController extends GetxController {
break;
}
_updateConnectionStatus();
// 延迟一帧后刷新状态,确保UI能更新
Future.microtask(() => _updateConnectionStatus());
// 通知UI刷新
update();
}
// 显示连接成功消息
@ -147,70 +372,81 @@ class HomeController extends GetxController {
Future<void> _checkPairedDevices() async {
try {
if (_bluetoothService != null) {
Logger.info('开始检查已配对设备流程');
// 更新状态为正在连接
_connectionStatus.value = ConnectionStatus.connecting;
update(); // 立即更新UI
// 如果已有选中的设备并且已连接,直接使用
if (_bluetoothService!.selectedDevice != null &&
_bluetoothService!.connectionState == BluetoothConnectionState.connected) {
Logger.info('使用已连接的设备: ${_bluetoothService!.selectedDevice!.platformName}');
Logger.info('使用已连接的设备: ${_bluetoothService!.selectedDevice!.platformName}, 连接状态: ${_bluetoothService!.connectionState}');
_deviceInfo.assignAll(_bluetoothService!.getConnectedDeviceInfo());
_connectionStatus.value = ConnectionStatus.connected;
_isConnected.value = true; // 直接设置连接状态
_updateConnectionStatus();
update(); // 立即更新UI
return;
}
// 获取已配对设备
final pairedDevices = await _bluetoothService!.getPairedDevices();
Logger.info('开始获取系统已配对设备列表...');
final pairedDevices = await _bluetoothService!.getPairedDevices()
.timeout(const Duration(seconds: 5), onTimeout: () {
Logger.error('获取已配对设备超时');
return <BluetoothDevice>[];
});
if (pairedDevices.isEmpty) {
Logger.info('没有找到已配对设备');
_connectionStatus.value = ConnectionStatus.disconnected;
update(); // 立即更新UI
return;
}
// 遍历配对设备,查找已连接的设备
for (var device in pairedDevices) {
try {
final state = await device.connectionState.first;
if (state == BluetoothConnectionState.connected) {
// 找到已连接的设备,更新UI
final success = await _bluetoothService!.connectToDevice(device);
if (success) {
Logger.info('使用已连接的已配对设备: ${device.platformName}');
_deviceInfo.assignAll(_bluetoothService!.getConnectedDeviceInfo());
_connectionStatus.value = ConnectionStatus.connected;
_updateConnectionStatus();
return;
}
}
} catch (e) {
// 忽略单个设备检查错误
Logger.error('检查设备连接状态失败: $e');
}
Logger.info('发现 ${pairedDevices.length} 个已配对设备');
for (var i = 0; i < pairedDevices.length; i++) {
var device = pairedDevices[i];
Logger.info('已配对设备 #$i: ${device.platformName} (${device.remoteId.str})');
}
// 如果没有已连接的设备,尝试连接第一个配对设备
// 遍历配对设备,检查是否有需要连接的设备
Logger.info('开始查找设备进行连接...');
if (pairedDevices.isNotEmpty) {
Logger.info('尝试连接第一个配对设备: ${pairedDevices.first.platformName}');
final success = await _bluetoothService!.connectToDevice(pairedDevices.first);
// 选择第一个配对设备进行连接
var deviceToConnect = pairedDevices.first;
Logger.info('尝试连接配对设备: ${deviceToConnect.platformName ?? "未命名设备"}');
final success = await _bluetoothService!.connectToDevice(deviceToConnect)
.timeout(const Duration(seconds: 5), onTimeout: () {
Logger.error('连接设备 ${deviceToConnect.platformName ?? "未命名设备"} 超时');
return false;
});
if (success) {
Logger.info('自动连接到已配对设备: ${pairedDevices.first.platformName}');
Logger.info('成功连接到设备: ${deviceToConnect.platformName ?? "未命名设备"}');
_deviceInfo.assignAll(_bluetoothService!.getConnectedDeviceInfo());
_connectionStatus.value = ConnectionStatus.connected;
_isConnected.value = true;
_updateConnectionStatus();
update(); // 立即更新UI
} else {
// 连接失败
Logger.error('连接设备 ${deviceToConnect.platformName ?? "未命名设备"} 失败');
_connectionStatus.value = ConnectionStatus.failed;
_connectionErrorMsg.value = '连接设备失败,请重试';
update(); // 立即更新UI
}
} else {
_connectionStatus.value = ConnectionStatus.disconnected;
update(); // 立即更新UI
}
}
} catch (e) {
Logger.error('检查已配对设备失败: $e');
_connectionStatus.value = ConnectionStatus.failed;
_connectionErrorMsg.value = '检查设备失败: ${e.toString()}';
update(); // 立即更新UI
}
}
@ -369,13 +605,18 @@ class HomeController extends GetxController {
return;
}
// 设置正在刷新状态
// 设置正在刷新状态,但保留当前设备信息显示
_connectionStatus.value = ConnectionStatus.connecting;
update(); // 立即更新UI状态
// 强制检查当前蓝牙连接状态
_checkAndUpdateCurrentConnectionState();
if (_bluetoothService!.selectedDevice != null) {
// 如果有选中的设备,刷新设备信息
_deviceInfo.assignAll(_bluetoothService!.getConnectedDeviceInfo());
_updateConnectionStatus();
update(); // 确保UI更新
// 检查刷新后的连接状态
if (_isConnected.value) {
@ -387,19 +628,22 @@ class HomeController extends GetxController {
colorText: Colors.green[800],
);
} else {
// 设备可能已断开连接
// 设备可能已断开连接,尝试使用存储的设备信息重新连接
if (_bluetoothService!.lastConnectedDeviceInfo.isNotEmpty) {
_connectionStatus.value = ConnectionStatus.disconnected;
Get.snackbar(
'提示',
'设备已断开连接',
snackPosition: SnackPosition.TOP,
backgroundColor: Colors.amber[100],
colorText: Colors.amber[800],
);
// 尝试重新连接
update(); // 更新状态
_reconnectToLastDevice();
} else {
// 上次连接的设备信息也为空,尝试连接已配对设备
await _checkPairedDevices();
}
}
} else if (_bluetoothService!.lastConnectedDeviceInfo.isNotEmpty) {
// 如果没有选中的设备但有上次连接的设备信息,尝试重新连接
_deviceInfo.assignAll(_bluetoothService!.lastConnectedDeviceInfo);
_updateConnectionStatus();
update(); // 确保UI更新
_reconnectToLastDevice();
} else {
// 如果没有选中的设备,尝试连接已配对设备
await _checkPairedDevices();
@ -426,6 +670,7 @@ class HomeController extends GetxController {
Logger.error('刷新设备信息失败: $e');
_connectionStatus.value = ConnectionStatus.failed;
_connectionErrorMsg.value = '刷新设备信息失败: ${e.toString()}';
update(); // 确保状态更新
Get.snackbar(
'提示',

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

@ -49,9 +49,9 @@ class PairingController extends GetxController {
// 启动搜索动画
_startSearchingAnimation();
// 延迟初始化蓝牙服务,避免在应用启动时就请求权限
// 延迟更久初始化蓝牙服务,避免在应用启动时就请求权限和阻塞主线程
// 改为延迟初始化,给UI足够时间渲染
Future.delayed(const Duration(milliseconds: 500), () {
Future.delayed(const Duration(milliseconds: 1500), () {
_initBluetoothService();
});
}
@ -69,6 +69,7 @@ class PairingController extends GetxController {
if (Get.isRegistered<BluetoothService>()) {
_bluetoothService = Get.find<BluetoothService>();
} else {
// 在后台初始化蓝牙服务
_bluetoothService = await Get.put(BluetoothService()).init();
}
@ -82,8 +83,8 @@ class PairingController extends GetxController {
// 如果蓝牙开启,开始扫描设备
if (on) {
// 延迟开始扫描,避免权限请求过早
Future.delayed(const Duration(milliseconds: 300), () {
// 延迟开始扫描,避免权限请求过早和主线程阻塞
Future.delayed(const Duration(milliseconds: 800), () {
_startScanningDevices();
});
} else {
@ -96,8 +97,8 @@ class PairingController extends GetxController {
// 如果蓝牙已经开启,直接开始扫描
if (_bluetoothService.isOn) {
_isBluetoothOn.value = true;
// 延迟开始扫描,避免权限请求过早
Future.delayed(const Duration(milliseconds: 300), () {
// 延迟开始扫描,避免权限请求过早和主线程阻塞
Future.delayed(const Duration(milliseconds: 800), () {
_startScanningDevices();
});
} else {
@ -155,116 +156,66 @@ class PairingController extends GetxController {
// 开始扫描蓝牙设备
Future<void> _startScanningDevices() async {
try {
// 先检查是否已有连接的设备
if (_bluetoothService.connectedDevices.isNotEmpty) {
// 有已连接的设备,直接使用
_deviceFound(_bluetoothService.connectedDevices.first);
return;
}
_searchStatus.value = SearchStatus.searching;
// 检查是否有已配对的设备
if (await _checkPairedDevices()) {
// 已找到并成功连接到配对设备
// 先检查是否有上次连接的设备信息
if (_bluetoothService.lastConnectedDeviceInfo.isNotEmpty &&
_bluetoothService.lastConnectedDeviceInfo['connected'] == true) {
Logger.info('使用上次连接的设备信息');
_deviceInfo.value = _bluetoothService.lastConnectedDeviceInfo;
_onDeviceFound();
return;
}
// 搜索新设备
_searchStatus.value = SearchStatus.searching;
await _bluetoothService.startScan(timeout: const Duration(seconds: 10));
// 使用BluetoothService的自动连接功能查找设备
Logger.info('尝试自动连接设备');
BluetoothDevice? device = await _bluetoothService.autoConnectDevice()
.timeout(const Duration(seconds: 8), onTimeout: () {
Logger.info('自动连接设备超时');
return null;
});
// 检查扫描结果
if (_bluetoothService.scanResults.isNotEmpty) {
// 找到设备,选择信号最强的一个
final strongestSignal = _bluetoothService.scanResults
.reduce((a, b) => (a.rssi > b.rssi) ? a : b);
if (device != null) {
Logger.info('自动连接设备成功: ${device.platformName}');
_deviceInfo.value = _bluetoothService.getConnectedDeviceInfo();
_onDeviceFound();
return;
}
// 连接设备
final success = await _bluetoothService.connectToDevice(strongestSignal.device);
// 如果自动连接失败,尝试扫描新设备
Logger.info('自动连接失败,开始扫描新设备');
device = await _bluetoothService.scanAndConnectToDevice(
timeout: const Duration(seconds: 5)
).timeout(const Duration(seconds: 6), onTimeout: () {
Logger.info('扫描并连接设备超时');
return null;
});
if (success) {
_deviceFound(strongestSignal.device);
} else {
_errorMessage.value = '连接设备失败,请重试';
_searchStatus.value = SearchStatus.failed;
_stopSearchingAnimation();
}
if (device != null) {
Logger.info('扫描并连接设备成功: ${device.platformName}');
_deviceInfo.value = _bluetoothService.getConnectedDeviceInfo();
_onDeviceFound();
} else {
// 未找到设备
// 未找到任何设备
_errorMessage.value = '未找到蓝牙设备,请确保设备已开启并在附近';
_searchStatus.value = SearchStatus.failed;
_stopSearchingAnimation();
}
} catch (e) {
Logger.error('扫描蓝牙设备失败: $e');
_errorMessage.value = '扫描蓝牙设备失败: ${e.toString()}';
Logger.error('设备搜索和连接失败: $e');
_errorMessage.value = '设备搜索失败: ${e.toString()}';
_searchStatus.value = SearchStatus.failed;
_stopSearchingAnimation();
}
}
// 检查已配对设备
Future<bool> _checkPairedDevices() async {
try {
// 先检查当前是否有设备已连接
if (_bluetoothService.selectedDevice != null &&
_bluetoothService.connectionState == BluetoothConnectionState.connected) {
Logger.info('已有设备连接: ${_bluetoothService.selectedDevice!.platformName}');
_deviceFound(_bluetoothService.selectedDevice!);
return true;
}
List<BluetoothDevice> pairedDevices = await _bluetoothService.getPairedDevices();
if (pairedDevices.isEmpty) {
Logger.info('没有找到已配对设备');
return false;
}
// 遍历配对设备,检查是否有设备已连接
for (BluetoothDevice device in pairedDevices) {
try {
BluetoothConnectionState state = await device.connectionState.first;
if (state == BluetoothConnectionState.connected) {
Logger.info('发现已连接的设备: ${device.platformName}');
// 设置为当前设备但不需要连接
final success = await _bluetoothService.connectToDevice(device);
if (success) {
_deviceFound(device);
return true;
}
}
} catch (e) {
// 忽略单个设备的连接状态检查错误,继续检查下一个
Logger.error('检查设备连接状态失败: $e');
}
}
// 如果没有找到已连接的设备,尝试连接第一个配对设备
Logger.info('尝试连接配对设备: ${pairedDevices.first.platformName}');
final success = await _bluetoothService.connectToDevice(pairedDevices.first);
if (success) {
_deviceFound(pairedDevices.first);
return true;
} else {
// 连接失败,继续搜索新设备
Logger.info('已配对设备连接失败,继续搜索新设备');
return false;
}
} catch (e) {
Logger.error('检查已配对设备失败: $e');
return false;
}
}
// 设备被找到时的处理
void _deviceFound(BluetoothDevice device) {
// 设备找到并连接成功后的处理
void _onDeviceFound() {
_searchStatus.value = SearchStatus.found;
_stopSearchingAnimation();
// 获取设备信息
_deviceInfo.value = _bluetoothService.getConnectedDeviceInfo();
// 延迟后跳转到主页,同时传递设备信息
// 更新日志并延迟跳转到主页
Logger.info('设备连接成功,准备跳转到主页');
Timer(const Duration(seconds: 1), () {
Get.offAllNamed(Routes.home, arguments: _deviceInfo.value);
});
@ -272,24 +223,25 @@ class PairingController extends GetxController {
// 手动连接
void manualConnect() {
if (_searchStatus.value == SearchStatus.searching) {
// 如果正在搜索,则停止搜索
// 停止当前扫描(如果有)
_bluetoothService.stopScan();
}
// 重新开始搜索
// 更新UI状态
_startSearchingAnimation();
_searchStatus.value = SearchStatus.searching;
_errorMessage.value = '';
// 直接开始扫描并连接
_startScanningDevices();
}
// 稍后再说
void skipConnecting() {
if (_searchStatus.value == SearchStatus.searching) {
// 如果正在搜索,则停止搜索
// 停止当前扫描
_bluetoothService.stopScan();
}
_stopSearchingAnimation();
// 直接跳转到主页
Get.offAllNamed(Routes.home);
}
}
Loading…
Cancel
Save