diff --git a/lib/core/bindings/initial_binding.dart b/lib/core/bindings/initial_binding.dart index c372b985a..c3ee74f7b 100644 --- a/lib/core/bindings/initial_binding.dart +++ b/lib/core/bindings/initial_binding.dart @@ -3,7 +3,6 @@ import 'package:deep_voice/data/services/music_manager.dart'; import 'package:deep_voice/data/services/navigation_manager.dart'; import 'package:get/get.dart'; import '../../data/services/speech_factory.dart'; -import '../../data/services/chat_history_service.dart'; import '../../data/services/volcano_translation_service.dart'; import '../../data/services/language_manager.dart'; import '../../core/utils/logger.dart'; @@ -26,9 +25,6 @@ class InitialBinding extends Bindings { Get.lazyPut(() => SpeechFactory(), fenix: true); Get.find().initialize(initialType: SpeechServiceType.azure); - // 历史记录服务 - Get.lazyPut(() => ChatHistoryService(), fenix: true); - // 火山翻译服务 Get.lazyPut(() => VolcanoTranslationService(), fenix: true); @@ -48,7 +44,8 @@ class InitialBinding extends Bindings { Get.lazyPut(() => AccountService(), fenix: true); // 版本更新服务 - Get.lazyPut(() => VersionUpdateService(), fenix: true); + Get.lazyPut(() => VersionUpdateService(), + fenix: true); // 杰理蓝牙服务 diff --git a/lib/core/widgets/message_bubble.dart b/lib/core/widgets/message_bubble.dart deleted file mode 100644 index 0ddbd836e..000000000 --- a/lib/core/widgets/message_bubble.dart +++ /dev/null @@ -1,94 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:flutter_screenutil/flutter_screenutil.dart'; -import '../../modules/chat/models/message_model.dart'; -import '../theme/app_colors.dart'; - -class MessageBubble extends StatelessWidget { - final Message message; - final bool isUser; - final String? userAvatar; - final String? agentAvatar; - - const MessageBubble({ - Key? key, - required this.message, - required this.isUser, - this.userAvatar, - this.agentAvatar, - }) : super(key: key); - - @override - Widget build(BuildContext context) { - return Padding( - padding: EdgeInsets.only(bottom: 16.h), - child: Row( - mainAxisAlignment: isUser ? MainAxisAlignment.end : MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Flexible( - child: Container( - padding: EdgeInsets.all(12.r), - decoration: BoxDecoration( - color: isUser ? AppColors.primary : Colors.grey[200], - borderRadius: BorderRadius.circular(16.r), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // 消息内容 - if (message.content.isNotEmpty) - SelectableText( - message.content, - style: TextStyle( - color: isUser ? Colors.white : Colors.black, - fontSize: 16.sp, - height: 1.5, - letterSpacing: 0.2, - ), - textAlign: TextAlign.left, - maxLines: null, - enableInteractiveSelection: true, - textWidthBasis: TextWidthBasis.longestLine, - ), - - // 加载指示器 - if (message.isIntermediate) - Padding( - padding: EdgeInsets.only(top: message.content.isNotEmpty ? 8.h : 0), - child: _buildLoadingIndicator(), - ), - ], - ), - ), - ), - ], - ), - ); - } - - Widget _buildLoadingIndicator() { - return Row( - mainAxisSize: MainAxisSize.min, - children: [ - SizedBox( - width: 12.w, - height: 12.h, - child: CircularProgressIndicator( - strokeWidth: 2, - valueColor: AlwaysStoppedAnimation( - isUser ? Colors.white : AppColors.primary, - ), - ), - ), - SizedBox(width: 8.w), - Text( - '思考中...', - style: TextStyle( - color: isUser ? Colors.white : Colors.black, - fontSize: 12.sp, - ), - ), - ], - ); - } -} \ No newline at end of file diff --git a/lib/data/services/chat_history_service.dart b/lib/data/services/chat_history_service.dart deleted file mode 100644 index e1c017902..000000000 --- a/lib/data/services/chat_history_service.dart +++ /dev/null @@ -1,135 +0,0 @@ -import 'dart:convert'; -import 'package:get/get.dart'; -import '../../modules/chat/models/message_model.dart'; -import '../../core/utils/logger.dart'; -import 'package:chat_storage/chat_storage.dart'; - -/// 聊天记录管理服务,负责保存和加载聊天记录 -class ChatHistoryService extends GetxService { - // 最大历史记录数量 - static const int _maxHistoryCount = 200; // 每个对话最多保存的消息数 - - @override - void onInit() { - super.onInit(); - Logger.info('ChatHistoryService 已初始化'); - } - - /// 保存聊天记录 - /// - /// [agentId] 代理ID - /// [messages] 消息列表 - Future saveHistory(String agentId, List messages) async { - try { - if (messages.isEmpty) return; - - // 获取最新的消息 - final latestMessage = messages.last; - - // 如果是加载中的消息则跳过 - if (latestMessage.isIntermediate) return; - - // 直接插入最新的消息到数据库 - await ChatStorage.saveMessage( - sessionId: agentId, - message: latestMessage.content, - sender: latestMessage.role, - ); - - // 检查并清理过多的历史记录 - await _cleanupOldMessages(agentId); - } catch (e) { - Logger.error('保存聊天记录失败', e.toString()); - } - } - - /// 清理旧消息,保持每个会话的消息数量不超过最大限制 - Future _cleanupOldMessages(String agentId) async { - try { - final pagedResult = await ChatStorage.getMessages( - sessionId: agentId, - page: 1, - pageSize: 1, // 只获取总数信息 - ); - - // 如果消息总数超过最大限制,删除最早的消息 - if (pagedResult.totalCount > _maxHistoryCount) { - final messagesToDelete = pagedResult.totalCount - _maxHistoryCount; - // 获取最早的N条消息ID - final oldestMessages = await ChatStorage.getMessages( - sessionId: agentId, - page: 1, - pageSize: messagesToDelete, - ); - - // 构建要删除的消息ID列表 - final List messageIds = oldestMessages.messages - .where((m) => m.id != null) - .map((m) => m.id!) - .toList(); - - // 删除这些消息 - if (messageIds.isNotEmpty) { - await ChatStorage.deleteMessages(messageIds: messageIds); - } - } - } catch (e) { - Logger.error('清理旧消息失败', e); - } - } - - /// 加载聊天记录 - /// - /// [agentId] 代理ID - /// 返回消息列表,如果没有找到或解析错误则返回空列表 - Future> loadHistory(String agentId) async { - try { - final pagedResult = await ChatStorage.getMessages( - sessionId: agentId, - page: 1, - pageSize: _maxHistoryCount, - ); - - if (pagedResult.messages.isEmpty) { - return []; - } - - // 将ChatStorage.ChatMessage转换为Message - final messages = pagedResult.messages.map((chatMessage) { - return Message( - role: chatMessage.sender, - content: chatMessage.message, - timestamp: DateTime.parse( - chatMessage.timestamp), // 直接使用chatMessage的timestamp - // isLoading 默认false - // isLoading: metadata['isLoading'] ?? false, - ); - }).toList(); - - return messages; - } catch (e) { - Logger.error('加载聊天记录失败', e); - return []; - } - } - - /// 清除特定代理的聊天记录 - Future clearHistory(String agentId) async { - try { - await ChatStorage.deleteMessages(sessionId: agentId); - Logger.info('已清除聊天记录,代理: $agentId'); - } catch (e) { - Logger.error('清除聊天记录失败', e); - } - } - - /// 清除所有聊天记录 - Future clearAllHistory() async { - try { - await ChatStorage.clearDatabase(); - Logger.info('已清除所有聊天记录'); - } catch (e) { - Logger.error('清除所有聊天记录失败', e); - } - } -} diff --git a/lib/data/services/version_update_service.dart b/lib/data/services/version_update_service.dart index f0bf31f05..a71930c80 100644 --- a/lib/data/services/version_update_service.dart +++ b/lib/data/services/version_update_service.dart @@ -65,7 +65,7 @@ class VersionUpdateService extends GetxService { return const PlatformInfo( platform: 'android', storeType: AppStoreType.official, - channel: '官方内测', + channel: 'officialTesting', ); // switch (brand) { @@ -178,17 +178,17 @@ class VersionUpdateService extends GetxService { // 拆分版本号与构建号 final currentParts = current.split('+'); final latestParts = latest.split('+'); - + // 解析版本号 (major.minor.patch) final currentVersions = currentParts[0].split('.').map(int.parse).toList(); final latestVersions = latestParts[0].split('.').map(int.parse).toList(); - + // 逐级比较版本号 for (var i = 0; i < currentVersions.length; i++) { if (latestVersions[i] > currentVersions[i]) return true; if (latestVersions[i] < currentVersions[i]) return false; } - + // 版本号相同则比较构建号 if (latestParts.length > 1 && currentParts.length > 1) { return int.parse(latestParts[1]) > int.parse(currentParts[1]); diff --git a/lib/data/services/voice_interaction_impl/android_voice_interaction_service.dart b/lib/data/services/voice_interaction_impl/android_voice_interaction_service.dart deleted file mode 100644 index 5dd9f96e7..000000000 --- a/lib/data/services/voice_interaction_impl/android_voice_interaction_service.dart +++ /dev/null @@ -1,343 +0,0 @@ -import 'dart:async'; -import 'package:flutter/services.dart'; -import 'package:get/get.dart'; -import '../../../core/utils/logger.dart'; -import 'package:flutter_dotenv/flutter_dotenv.dart'; -import '../../../modules/chat/models/message_model.dart'; -import '../../models/events/voice_interaction_event.dart'; -import '../chat_history_service.dart'; - -/// Android语音交互服务 -/// -/// 该服务提供了与Android端的VoiceInteractionService.kt通信的接口, -/// 用于管理后台语音交互服务的生命周期和接收语音交互事件 -class AndroidVoiceInteractionService extends GetxService { - static AndroidVoiceInteractionService get to => Get.find(); - // 方法通道和事件通道 - static const MethodChannel _channel = MethodChannel('com.deep_voice.voice_interaction'); - static const EventChannel _eventChannel = EventChannel('com.deep_voice.voice_interaction_events'); - - // 服务状态 - final _isServiceRunning = false.obs; - bool get isServiceRunning => _isServiceRunning.value; - - // 事件流控制器 - StreamController? _eventStreamController; - Stream? _eventStream; - @override - Stream get eventStream => _eventStream ?? Stream.empty(); - - // 事件通道状态 - StreamSubscription? _eventSubscription; - - // 配置信息 - late String _azureSpeechKey; - late String _azureSpeechRegion; - late String _volcanoAiApiKey; - - // 初始化状态标志 - static bool _isInitialized = false; - - // 当前会话历史 - final List> _messageHistory = []; - - // 服务ID - static const String _serviceId = 'android_assistant'; - - /// 构造函数 - AndroidVoiceInteractionService() { - - } - - /// 从环境变量加载配置 - void _loadConfig() { - _azureSpeechKey = dotenv.env['AZURE_SPEECH_KEY'] ?? ''; - _azureSpeechRegion = dotenv.env['AZURE_SPEECH_REGION'] ?? ''; - _volcanoAiApiKey = dotenv.env['VOLCANO_AI_API_KEY'] ?? ''; - - if (_azureSpeechKey.isEmpty || _azureSpeechRegion.isEmpty) { - Logger.warning('未找到 Azure 语音服务配置。请在 .env 文件中设置 AZURE_SPEECH_KEY 和 AZURE_SPEECH_REGION'); - } - - if (_volcanoAiApiKey.isEmpty) { - Logger.warning('未找到火山 AI API 密钥。请在 .env 文件中设置 VOLCANO_AI_API_KEY'); - } - } - - /// 设置事件通道 - void _setupEventChannel() { - _eventSubscription = _eventChannel - .receiveBroadcastStream() - .listen((event) { - if (event is Map) { - final String eventType = event['type'] as String? ?? ''; - - // 直接处理事件,不再等待channelReady - _handleVoiceInteractionEvent(event); - } - }, onError: (error) { - Logger.error('语音交互事件通道错误: ${error.toString()}'); - }); - } - - /// 创建事件流 - void _createEventStream() { - _eventStreamController = StreamController.broadcast(); - _eventStream = _eventStreamController?.stream; - } - - /// 初始化服务 - @override - Future initialize() async { - try { - if (_isInitialized) { - return this; - } - _isInitialized = true; - - _loadConfig(); - _setupEventChannel(); - _createEventStream(); - // 检查服务是否正在运行 - await _checkServiceStatus(); - - await _startService(); - - Logger.info('语音交互服务初始化完成'); - return this; - } catch (e) { - Logger.error('语音交互服务初始化失败: ${e.toString()}'); - return this; - } - } - - @override - void onWakeup() { - Logger.info('语音交互服务唤醒'); - } - - /// 检查服务状态 - Future _checkServiceStatus() async { - try { - final bool isRunning = await _channel.invokeMethod('isVoiceInteractionServiceRunning') ?? false; - _isServiceRunning.value = isRunning; - Logger.info('语音交互服务状态: ${isRunning ? "运行中" : "未运行"}'); - } catch (e) { - Logger.error('检查语音交互服务状态失败: ${e.toString()}'); - _isServiceRunning.value = false; - } - } - - /// 启动语音交互服务 - Future _startService() async { - if (_isServiceRunning.value) { - Logger.info('语音交互服务已经在运行'); - return true; - } - - try { - final bool result = await _channel.invokeMethod('startVoiceInteractionService', { - 'azure_speech_key': _azureSpeechKey, - 'azure_speech_region': _azureSpeechRegion, - 'volcano_ai_api_key': _volcanoAiApiKey, - }) ?? false; - - if (result) { - _isServiceRunning.value = true; - Logger.info('语音交互服务启动成功'); - } else { - Logger.error('语音交互服务启动失败'); - } - - return result; - } catch (e) { - Logger.error('启动语音交互服务失败: ${e.toString()}'); - return false; - } - } - - /// 停止语音交互服务 - Future _stopService() async { - if (!_isServiceRunning.value) { - Logger.info('语音交互服务未运行'); - return true; - } - - try { - final bool result = await _channel.invokeMethod('stopVoiceInteractionService') ?? false; - - if (result) { - _isServiceRunning.value = false; - Logger.info('语音交互服务停止成功'); - } else { - Logger.error('语音交互服务停止失败'); - } - - return result; - } catch (e) { - Logger.error('停止语音交互服务失败: ${e.toString()}'); - return false; - } - } - - /// 暂停语音交互(停止TTS和语音识别,但保持服务运行) - @override - Future pauseVoiceInteraction() async { - if (!_isServiceRunning.value) { - Logger.info('语音交互服务未运行,无法暂停'); - return false; - } - - try { - final bool result = await _channel.invokeMethod('pauseVoiceInteraction') ?? false; - - if (result) { - Logger.info('语音交互暂停成功'); - } else { - Logger.error('语音交互暂停失败'); - } - - return result; - } catch (e) { - Logger.error('暂停语音交互失败: ${e.toString()}'); - return false; - } - } - - /// 处理来自原生端的语音交互事件 - void _handleVoiceInteractionEvent(dynamic event) { - if (event is! Map || _eventStreamController == null) return; - - final Map eventMap = event; - final String eventType = eventMap['type'] as String? ?? ''; - final int timestamp = eventMap['timestamp'] as int? ?? 0; - - // 添加时间戳日志,帮助调试 - Logger.info('收到原生端事件: ${eventType.toString()}, 时间戳: $timestamp, 当前时间: ${DateTime.now().millisecondsSinceEpoch}'); - - switch (eventType) { - case 'chatHistory': - final String agentId = eventMap['agentId'] as String? ?? ''; - final String userMessage = eventMap['userMessage'] as String? ?? ''; - final String assistantMessage = eventMap['assistantMessage'] as String? ?? ''; - - // 添加到消息历史 - if (userMessage.isNotEmpty) { - _messageHistory.add({'role': 'user', 'content': userMessage}); - _messageHistory.add({'role': 'assistant', 'content': assistantMessage}); - - // 保持历史记录在一定长度 - while (_messageHistory.length > 10) { - _messageHistory.removeAt(0); - } - } - - _eventStreamController?.add(ChatHistoryEvent( - agentId: agentId, - userMessage: userMessage, - assistantMessage: assistantMessage, - timestamp: timestamp, - )); - - // 保存聊天记录 - _saveChatHistory(agentId, userMessage, assistantMessage, timestamp); - - Logger.info('收到聊天历史事件: agentId=$agentId'); - break; - - case 'recognitionStarted': - _eventStreamController?.add(RecognitionStartedEvent( - timestamp: timestamp, - )); - break; - - default: - Logger.warning('收到未知类型的语音交互事件: ${eventType.toString()}'); - break; - } - } - - @override - void onClose() { - // 清理资源 - _stopService(); - _eventSubscription?.cancel(); - _eventStreamController?.close(); - super.onClose(); - } - - /// 保存聊天记录 - Future _saveChatHistory(String agentId, String userMessage, String assistantMessage, int timestamp) async { - try { - if (userMessage.isEmpty) { - return; - } - - final userMsg = Message( - role: 'user', - content: userMessage, - timestamp: DateTime.fromMillisecondsSinceEpoch(timestamp), - ); - - final assistantMsg = Message( - role: 'assistant', - content: assistantMessage, - timestamp: DateTime.fromMillisecondsSinceEpoch(timestamp + 1), // 确保助手消息时间戳晚于用户消息 - ); - - // 获取ChatHistoryService实例 - final chatHistoryService = Get.find(); - - // 加载现有历史记录 - final existingMessages = await chatHistoryService.loadHistory(agentId); - - // 添加新消息 - existingMessages.addAll([userMsg, assistantMsg]); - - // 保存更新后的历史记录 - await chatHistoryService.saveHistory(agentId, existingMessages); - } catch (e) { - Logger.error('保存聊天记录失败: ${e.toString()}'); - } - } - - /// 是否处于活跃状态 - @override - bool isActive() { - return _isServiceRunning.value; - } - - /// 获取当前对话历史 - @override - List> getMessageHistory() { - return List>.from(_messageHistory); - } - - /// 获取当前服务ID - @override - String getServiceId() { - return _serviceId; - } -} - - -/// 聊天历史事件 -class ChatHistoryEvent extends VoiceInteractionEvent { - final String agentId; - final String userMessage; - final String assistantMessage; - - ChatHistoryEvent({ - required this.agentId, - required this.userMessage, - required this.assistantMessage, - required int timestamp, - }) : super(timestamp: timestamp); -} - -/// 语音识别开始事件 -class RecognitionStartedEvent extends VoiceInteractionEvent { - RecognitionStartedEvent({ - required int timestamp, - }) : super(timestamp: timestamp); -} \ No newline at end of file diff --git a/lib/data/services/voice_interaction_impl/ios_voice_interaction_service.dart b/lib/data/services/voice_interaction_impl/ios_voice_interaction_service.dart deleted file mode 100644 index 1fc80ba89..000000000 --- a/lib/data/services/voice_interaction_impl/ios_voice_interaction_service.dart +++ /dev/null @@ -1,466 +0,0 @@ -import 'dart:async'; -import 'package:get/get.dart'; -import '../asr_service.dart'; -import '../tts_service.dart'; -import '../volcano_ai_service.dart'; -import '../chat_history_service.dart'; -import '../../../modules/chat/models/message_model.dart'; -import '../../models/events/voice_interaction_event.dart'; -import '../../../core/utils/logger.dart'; - -enum VoiceInteractionState { - idle, // 等待唤醒 - active, // 活跃状态 - 可以同时识别和播放 -} - -class IosVoiceInteractionService extends GetxService { - final AsrService _asrService = Get.find(); - final TtsService _ttsService = Get.find(); - final VolcanoAIService _aiService = Get.find(); - final ChatHistoryService _chatHistoryService = Get.find(); - - // 当前状态 - final Rx state = VoiceInteractionState.idle.obs; - - // 是否识别到用户语音 - final RxBool isSpeechDetected = false.obs; - - // 是否正在处理AI响应 - final RxBool isProcessingAI = false.obs; - - // 是否正在播放TTS - final RxBool isSpeaking = false.obs; - - // 对话历史记录 - final List> _messageHistory = []; - - // 最大历史记录数 - static const int _maxHistorySize = 10; - - // 超时计时器 - Timer? _inactivityTimer; - - // 流订阅 - StreamSubscription? _recognitionSubscription; - - // 当前识别的文本 - String _currentRecognizedText = ''; - // 音频输入源 - bool _audioSourceType = false; - // 事件流控制器 - final StreamController _eventStreamController = - StreamController.broadcast(); - - // 获取事件流 - @override - Stream get eventStream => - _eventStreamController.stream; - - // 标识符,用于存储聊天历史 - final String _agentId = 'ios_assistant'; - - /// 初始化服务 - @override - Future initialize() async { - try { - // 保持idle状态,不自动启动语音识别 - Logger.info('iOS语音交互服务初始化完成,处于idle状态'); - } catch (e) { - Logger.error('iOS语音交互服务初始化失败: ${e.toString()}'); - } - - return this; - } - - // 处理唤醒事件 - void onWakeup() { - _ttsService.speakOnce('我在呢!'); - isSpeaking.value = true; - - if (state.value == VoiceInteractionState.idle) { - _activateInteraction(); - } - } - - // 激活交互 - void _activateInteraction() { - // 设置状态为活跃 - state.value = VoiceInteractionState.active; - - // 开始连续监听 - _startContinuousListening(); - - // 启动不活动计时器 - _startInactivityTimer(); - - // 发送识别开始事件 - _sendRecognitionStartedEvent(); - } - - // 开始连续监听 - Future _startContinuousListening() async { - // 停止之前的监听 - _recognitionSubscription?.cancel(); - - try { - // 开始连续识别 - final recognitionStream = - await _asrService.startContinuousRecognition(_audioSourceType); - - // 监听识别结果 - _recognitionSubscription = - recognitionStream.listen(_handleRecognitionEvent, onError: (error) { - Logger.error('语音识别错误: ${error.toString()}'); - _startContinuousListening(); // 尝试重新启动 - }); - - Logger.info('开始连续语音识别'); - } catch (e) { - Logger.error('启动语音识别失败: ${e.toString()}'); - } - } - - // 处理识别事件 - void _handleRecognitionEvent(RecognitionEvent event) { - // 更新活动时间 - _updateActivityTime(); - - switch (event.type) { - case RecognitionEventType.intermediateResult: - // 检测用户是否开始说话 - if (event.text.isNotEmpty && !isSpeechDetected.value) { - isSpeechDetected.value = true; - - // 用户开始讲话时,立即中断当前响应 - _interruptCurrentResponse("检测到用户开始讲话,中断当前响应"); - } - break; - - case RecognitionEventType.finalResult: - if (event.text.isNotEmpty) { - // 最终结果,处理用户输入 - _processUserInput(event.text); - } - // 重置语音检测状态 - isSpeechDetected.value = false; - break; - - case RecognitionEventType.sessionStarted: - Logger.info('语音识别会话开始'); - break; - - case RecognitionEventType.sessionStopped: - Logger.info('语音识别会话结束'); - - break; - - case RecognitionEventType.error: - case RecognitionEventType.canceled: - Logger.error('语音识别错误: ${event.error}'); - - break; - } - } - - // 中断当前响应 - void _interruptCurrentResponse(String reason) { - Logger.info(reason); - - // 如果正在活跃状态,需要中断当前操作 - if (state.value == VoiceInteractionState.active) { - // 停止TTS播放 - _ttsService.stop(); - isSpeaking.value = false; - isProcessingAI.value = false; - } - } - - // 处理用户输入 - Future _processUserInput(String text) async { - Logger.info('处理用户输入: $text'); - - // 设置为处理状态 - isProcessingAI.value = true; - - // 添加用户消息到历史记录 - final userMessage = {'role': 'user', 'content': text}; - _addToHistory(userMessage); - - // 创建用户消息对象 - final userMsg = Message( - role: 'user', - content: text, - timestamp: DateTime.now(), - ); - - // AI响应处理标志 - bool isProcessingCancelled = false; - - try { - // 构建消息历史 - final messageHistory = _buildMessageHistory(); - - // 调用AI服务获取响应 - final responseStream = _aiService.sendMessageStream( - messages: messageHistory, - systemPrompt: "你是一个智能助手,请简明扼要地回答问题。", - ); - - String aiResponse = ''; - - // 等待AI响应 - await for (final chunk in responseStream) { - // 检查是否被用户打断 - if (isSpeechDetected.value) { - // 用户开始说话,标记处理被取消 - isProcessingCancelled = true; - Logger.info('AI响应生成过程中被用户打断'); - break; - } - - // 如果状态已改变(可能由其他原因导致),停止处理 - if (state.value != VoiceInteractionState.active) { - isProcessingCancelled = true; - break; - } - - aiResponse += chunk; - - // 立即播放当前文本块,实现边生成边播放 - _speakStreamResponse(chunk); - } - - // 如果处理被取消,不继续后续操作 - if (isProcessingCancelled) { - isProcessingAI.value = false; - return; - } - - // 确保完整响应被处理 - if (aiResponse.isNotEmpty) { - // 将AI响应添加到历史记录 - final assistantMessage = {'role': 'assistant', 'content': aiResponse}; - _addToHistory(assistantMessage); - - // 创建助手消息对象 - final assistantMsg = Message( - role: 'assistant', - content: aiResponse, - timestamp: DateTime.now(), - ); - - // 保存聊天历史 - await _saveChatHistory(userMsg, assistantMsg); - - // 发送聊天历史事件 - _sendChatHistoryEvent(userMsg.content, assistantMsg.content); - - // 刷新TTS流,确保所有文本都被播放 - await _ttsService.flushStream(); - isSpeaking.value = false; - } - - // 处理完成 - isProcessingAI.value = false; - } catch (e) { - Logger.error('AI响应处理失败: ${e.toString()}'); - // 错误恢复 - isProcessingAI.value = false; - } - } - - // 构建用于AI服务的消息历史 - List> _buildMessageHistory() { - return List>.from(_messageHistory); - } - - // 添加消息到历史记录 - void _addToHistory(Map message) { - _messageHistory.add(message); - - // 限制历史记录大小 - while (_messageHistory.length > _maxHistorySize) { - _messageHistory.removeAt(0); - } - } - - // 播放流式响应 - void _speakStreamResponse(String chunk) { - if (chunk.isEmpty) return; - - // 更新播放状态 - isSpeaking.value = true; - - // 使用流式TTS播放 - _ttsService.speakStream(chunk); - - // 更新活动时间 - _updateActivityTime(); - } - - // 停止TTS - void _stopTts() { - _ttsService.stop(); - isSpeaking.value = false; - } - - // 更新最后活动时间 - DateTime _lastActivityTime = DateTime.now(); - void _updateActivityTime() { - _lastActivityTime = DateTime.now(); - } - - // 启动不活动计时器(长时间无交互会切换到空闲状态) - void _startInactivityTimer() { - _cancelInactivityTimer(); - - _inactivityTimer = Timer.periodic(Duration(seconds: 5), (timer) { - // 计算空闲时间 - final idleTime = DateTime.now().difference(_lastActivityTime).inSeconds; - - // 如果空闲超过30秒,且不在播放或检测到语音,切换到空闲 - if (idleTime > 8 && - !isSpeaking.value && - !isProcessingAI.value && - !isSpeechDetected.value) { - _resetToIdle(); - timer.cancel(); - } - }); - } - - // 取消不活动计时器 - void _cancelInactivityTimer() { - _inactivityTimer?.cancel(); - _inactivityTimer = null; - } - - // 重置到空闲状态 - void _resetToIdle() { - _ttsService.speakOnce('没有听到声音,暂停对话,双击耳机唤醒!'); - _cancelInactivityTimer(); - - // 停止TTS - _stopTts(); - - // 停止语音识别 - _recognitionSubscription?.cancel(); - _asrService.stopContinuousRecognition(); - - // 重置状态 - state.value = VoiceInteractionState.idle; - isSpeechDetected.value = false; - isProcessingAI.value = false; - isSpeaking.value = false; - - // 清空历史记录 - _messageHistory.clear(); - - Logger.info('语音交互已重置为空闲状态'); - } - - // 暂停语音交互 - @override - void pauseVoiceInteraction() { - if (state.value != VoiceInteractionState.idle) { - _stopTts(); - _recognitionSubscription?.cancel(); - _asrService.stopContinuousRecognition(); - - // 重置所有状态 - state.value = VoiceInteractionState.idle; - isSpeechDetected.value = false; - isProcessingAI.value = false; - isSpeaking.value = false; - - // 清空历史记录 - _messageHistory.clear(); - } - } - - // 恢复语音交互 - void resumeVoiceInteraction() { - if (state.value == VoiceInteractionState.idle) { - _activateInteraction(); - } - } - - // 手动停止交互 - void stopInteraction() { - _resetToIdle(); - } - - // 获取当前状态 - VoiceInteractionState getCurrentState() { - return state.value; - } - - // 是否处于活跃状态 - @override - bool isActive() { - return state.value != VoiceInteractionState.idle; - } - - // 获取当前对话历史 - @override - List> getMessageHistory() { - return List>.from(_messageHistory); - } - - // 获取当前服务ID - @override - String getServiceId() { - return _agentId; - } - - // 释放资源 - @override - void onClose() { - _cancelInactivityTimer(); - _recognitionSubscription?.cancel(); - _resetToIdle(); - _eventStreamController.close(); - super.onClose(); - } - - // 保存聊天历史 - Future _saveChatHistory(Message userMsg, Message assistantMsg) async { - try { - // 加载现有历史记录 - final existingMessages = await _chatHistoryService.loadHistory(_agentId); - - // 添加新消息 - existingMessages.addAll([userMsg, assistantMsg]); - - // 保存更新后的历史记录 - await _chatHistoryService.saveHistory(_agentId, existingMessages); - - Logger.info('保存聊天历史记录成功'); - } catch (e) { - Logger.error('保存聊天历史记录失败: ${e.toString()}'); - } - } - - // 发送聊天历史事件 - void _sendChatHistoryEvent(String userMessage, String assistantMessage) { - final event = ChatHistoryEvent( - agentId: _agentId, - userMessage: userMessage, - assistantMessage: assistantMessage, - timestamp: DateTime.now().millisecondsSinceEpoch, - ); - - _eventStreamController.add(event); - Logger.info('已发送聊天历史事件'); - } - - // 发送识别开始事件 - void _sendRecognitionStartedEvent() { - final event = RecognitionStartedEvent( - timestamp: DateTime.now().millisecondsSinceEpoch, - ); - - _eventStreamController.add(event); - Logger.info('已发送识别开始事件'); - } -} \ No newline at end of file diff --git a/lib/modules/music/views/music_playlist_view.dart b/lib/modules/music/views/music_playlist_view.dart index eaffa21c2..bdf72da2a 100644 --- a/lib/modules/music/views/music_playlist_view.dart +++ b/lib/modules/music/views/music_playlist_view.dart @@ -10,6 +10,7 @@ class MusicPlaylistView extends StatelessWidget { @override Widget build(BuildContext context) { + ScreenUtil.init(context, designSize: const Size(375, 812)); // 添加设计尺寸参考 final controller = Get.find(); final List> playlist = Get.arguments ?? []; final isDarkMode = Theme.of(context).brightness == Brightness.dark; @@ -32,14 +33,14 @@ class MusicPlaylistView extends StatelessWidget { children: [ Icon( Icons.music_off, - size: 64, + size: 64.w, color: Colors.grey[400], ), - const SizedBox(height: 16), + SizedBox(height: 16.h), Text( '暂无音乐', style: TextStyle( - fontSize: 16, + fontSize: 16.sp, color: Colors.grey[600], ), ), @@ -48,10 +49,9 @@ class MusicPlaylistView extends StatelessWidget { ) : Column( children: [ - // 上部:当前播放音乐详情 - 进一步优化高度占比 - SizedBox( - height: MediaQuery.of(context).size.height * - 0.35, // 从35%减少到32%,为进度条优化腾出空间 + // 上部:当前播放音乐详情 - 响应式高度 + Container( + height: MediaQuery.of(context).size.height * 0.38, // 增大高度比例 child: _buildCurrentPlayingSection( controller, playlist, isDarkMode), ), @@ -70,105 +70,101 @@ class MusicPlaylistView extends StatelessWidget { // 构建当前播放音乐详情部分 Widget _buildCurrentPlayingSection(AgentController controller, List> playlist, bool isDarkMode) { - return Obx(() { - // 查找当前播放的歌曲 - 修复自动切换时不更新的问题 - Map? currentSong; - if (controller.currentId.value != 0) { - try { - currentSong = playlist.firstWhere( - (song) => - int.tryParse(song['id']?.toString() ?? '0') == - controller.currentId.value, - ); - } catch (e) { - // 如果没找到,使用第一首歌曲 + return SingleChildScrollView( + // 添加滚动支持 + child: Obx(() { + // 查找当前播放的歌曲 + Map? currentSong; + if (controller.currentId.value != 0) { + try { + currentSong = playlist.firstWhere( + (song) => + int.tryParse(song['id']?.toString() ?? '0') == + controller.currentId.value, + ); + } catch (e) { + // 如果没找到,使用第一首歌曲 + currentSong = playlist.isNotEmpty ? playlist.first : null; + } + } else { currentSong = playlist.isNotEmpty ? playlist.first : null; } - } else { - currentSong = playlist.isNotEmpty ? playlist.first : null; - } - if (currentSong == null) { - return Container( - padding: const EdgeInsets.all(16), // 减少padding - child: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon( - Icons.music_note, - size: 60, // 减小图标尺寸 - color: Colors.grey[400], - ), - const SizedBox(height: 12), // 减少间距 - Text( - '暂无播放音乐', - style: TextStyle( - fontSize: 16, // 减小字体 - color: isDarkMode ? Colors.grey[400] : Colors.grey[600], + if (currentSong == null) { + return Container( + padding: EdgeInsets.all(16.w), + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon( + Icons.music_note, + size: 60.w, + color: Colors.grey[400], ), - ), - ], + SizedBox(height: 12.h), + Text( + '暂无播放音乐', + style: TextStyle( + fontSize: 16.sp, + color: isDarkMode ? Colors.grey[400] : Colors.grey[600], + ), + ), + ], + ), ), - ), - ); - } + ); + } - final title = currentSong['name'] ?? currentSong['title'] ?? '未知歌曲'; - final artist = currentSong['singer'] ?? - currentSong['sgener'] ?? - currentSong['artist'] ?? - '未知歌手'; - final coverUrl = currentSong['image'] ?? currentSong['coverUrl'] ?? ''; + final title = currentSong['name'] ?? currentSong['title'] ?? '未知歌曲'; + final artist = currentSong['singer'] ?? + currentSong['sgener'] ?? + currentSong['artist'] ?? + '未知歌手'; + final coverUrl = currentSong['image'] ?? currentSong['coverUrl'] ?? ''; - return SingleChildScrollView( - // 添加滚动支持 - child: Container( - padding: const EdgeInsets.symmetric( - horizontal: 20, vertical: 12), // 优化padding:水平增加,垂直减少 + return Container( + padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 12.h), child: Column( mainAxisAlignment: MainAxisAlignment.center, children: [ - // 封面图片 - 进一步优化尺寸 - _buildCoverImage(coverUrl, isDarkMode, - size: 110), // 从120减小到110,为进度条优化腾出空间 - const SizedBox(height: 12), // 减少间距从16到14 + // 封面图片 - 响应式尺寸 + _buildCoverImage(coverUrl, isDarkMode, size: 110.w), + SizedBox(height: 12.h), // 歌曲信息 _buildSongInfo(title, artist, isDarkMode), - const SizedBox(height: 16), // 增加歌曲信息和进度条之间的间距,提升视觉层次 - // 进度条 - 现在是水平布局,需要更多关注 + SizedBox(height: 16.h), + // 进度条 _buildProgressBar(controller, isDarkMode), - const SizedBox(height: 15), // 增加进度条和控制按钮之间的间距 + SizedBox(height: 15.h), // 播放控制按钮 _buildPlayControls(controller, playlist, isDarkMode), ], ), - ), - ); - }); + ); + }), + ); } // 构建播放全部按钮部分 Widget _buildPlayAllSection(AgentController controller, List> playlist, bool isDarkMode) { return Container( - padding: const EdgeInsets.symmetric( - horizontal: 20, vertical: 12), // 优化padding,与音乐详情区域保持一致 + padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 12.h), child: SizedBox( width: double.infinity, - height: 48, // 从50减小到48,更紧凑 + height: 48.h, child: ElevatedButton.icon( onPressed: () async { if (playlist.isNotEmpty) { await controller.playPlaylist(playlist); } }, - icon: const Icon(Icons.play_arrow, - color: Colors.white, size: 20), // 调整图标大小 - label: const Text( + icon: Icon(Icons.play_arrow, color: Colors.white, size: 20.w), + label: Text( '播放全部', style: TextStyle( - fontSize: 15, // 从16减小到15 + fontSize: 15.sp, fontWeight: FontWeight.w600, color: Colors.white, ), @@ -176,12 +172,10 @@ class MusicPlaylistView extends StatelessWidget { style: ElevatedButton.styleFrom( backgroundColor: Theme.of(Get.context!).primaryColor, shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(14), // 从25减小到14,更协调 + borderRadius: BorderRadius.circular(14.r), ), - elevation: 3, // 从2增加到3,增强视觉层次 - shadowColor: Theme.of(Get.context!) - .primaryColor - .withValues(alpha: 0.4), // 添加阴影颜色 + elevation: 3, + shadowColor: Theme.of(Get.context!).primaryColor.withAlpha(102), ), ), ), @@ -192,8 +186,7 @@ class MusicPlaylistView extends StatelessWidget { Widget _buildPlaylistSection(AgentController controller, List> playlist, bool isDarkMode) { return ListView.builder( - padding: const EdgeInsets.symmetric( - horizontal: 20, vertical: 8), // 优化padding,与其他区域保持一致 + padding: EdgeInsets.symmetric(horizontal: 20.w, vertical: 8.h), itemCount: playlist.length, itemBuilder: (context, index) { final item = playlist[index]; @@ -237,40 +230,34 @@ class MusicPlaylistView extends StatelessWidget { return GestureDetector( onTap: () { - // 点击音乐卡片直接播放对应歌曲 _playSelectedSong(controller, song, Get.arguments ?? []); }, child: Container( - margin: const EdgeInsets.only(bottom: 10), // 从12减小到10,更紧凑 - padding: const EdgeInsets.all(14), // 从12增加到14,增加内部空间 + margin: EdgeInsets.only(bottom: 10.h), + padding: EdgeInsets.all(14.w), decoration: BoxDecoration( color: isDarkMode ? Colors.grey[800] : Colors.white, - borderRadius: BorderRadius.circular(14), // 从12增加到14,更现代的圆角 + borderRadius: BorderRadius.circular(14.r), boxShadow: [ BoxShadow( - color: Colors.black - .withValues(alpha: isDarkMode ? 0.25 : 0.05), // 增强阴影效果 - blurRadius: 4, // 从3增加到4 - spreadRadius: 0, // 从1减小到0,更自然的阴影 - offset: const Offset(0, 2), // 从(0,1)调整到(0,2),更明显的深度感 + color: Colors.black.withOpacity(isDarkMode ? 0.25 : 0.05), + blurRadius: 4, + offset: const Offset(0, 2), ), ], ), child: Row( children: [ - // 封面图片 - 优化设计 + // 封面图片 Container( - width: 52, // 从50增加到52,稍微大一点 - height: 52, + width: 52.w, + height: 52.w, decoration: BoxDecoration( - borderRadius: BorderRadius.circular(10), // 从8增加到10,更现代的圆角 - color: isDarkMode - ? Colors.grey[700] - : Colors.grey[200], // 根据主题调整背景色 + borderRadius: BorderRadius.circular(10.r), + color: isDarkMode ? Colors.grey[700] : Colors.grey[200], boxShadow: [ BoxShadow( - color: - Colors.black.withValues(alpha: isDarkMode ? 0.2 : 0.08), + color: Colors.black.withOpacity(isDarkMode ? 0.2 : 0.08), blurRadius: 2, offset: const Offset(0, 1), ), @@ -278,7 +265,7 @@ class MusicPlaylistView extends StatelessWidget { ), child: image.isNotEmpty ? ClipRRect( - borderRadius: BorderRadius.circular(10), + borderRadius: BorderRadius.circular(10.r), child: Image.network( image, fit: BoxFit.cover, @@ -288,7 +275,7 @@ class MusicPlaylistView extends StatelessWidget { color: isDarkMode ? Colors.grey[500] : Colors.grey[600], - size: 24, // 指定图标大小 + size: 24.w, ); }, ), @@ -296,34 +283,34 @@ class MusicPlaylistView extends StatelessWidget { : Icon( Icons.music_note, color: isDarkMode ? Colors.grey[500] : Colors.grey[600], - size: 24, // 指定图标大小 + size: 24.w, ), ), - const SizedBox(width: 14), // 从12增加到14,增加间距 - // 歌曲信息 - 优化文本样式 + SizedBox(width: 14.w), + // 歌曲信息 Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, - mainAxisAlignment: MainAxisAlignment.center, // 垂直居中对齐 + mainAxisAlignment: MainAxisAlignment.center, children: [ Text( name, style: TextStyle( - fontSize: 15.5, // 从16减小到15.5,更协调 - fontWeight: FontWeight.w600, // 从w500增加到w600,更突出 + fontSize: 15.5.sp, + fontWeight: FontWeight.w600, color: isDarkMode ? Colors.white : Colors.black87, - height: 1.2, // 添加行高控制 + height: 1.2, ), maxLines: 1, overflow: TextOverflow.ellipsis, ), - const SizedBox(height: 3), // 从4减小到3,更紧凑 + SizedBox(height: 3.h), Text( singer, style: TextStyle( - fontSize: 13, // 从14减小到13 + fontSize: 13.sp, color: isDarkMode ? Colors.grey[400] : Colors.grey[600], - height: 1.1, // 添加行高控制 + height: 1.1, ), maxLines: 1, overflow: TextOverflow.ellipsis, @@ -331,15 +318,12 @@ class MusicPlaylistView extends StatelessWidget { ], ), ), - const SizedBox(width: 14), // 从12增加到14,与封面图片间距保持一致 + SizedBox(width: 14.w), // 播放按钮 Obx(() { - // 修复类型比较问题:将id转换为int进行比较 final currentIdInt = int.tryParse(id) ?? 0; final isThisPlaying = controller.isPlaying.value && controller.currentId.value == currentIdInt; - - // 只有当前播放的歌曲才显示进度 final currentProgress = isThisPlaying ? controller.progress.value : 0.0; @@ -348,7 +332,7 @@ class MusicPlaylistView extends StatelessWidget { progress: currentProgress, primaryColor: Theme.of(context).primaryColor, backgroundColor: Colors.grey, - size: 40.0, + size: 40.w, strokeWidth: 2.5, onPlayPause: () async { try { @@ -356,7 +340,6 @@ class MusicPlaylistView extends StatelessWidget { '播放按钮被点击,当前播放状态: ${controller.isPlaying.value}'); Logger.d('MusicPlaylistView', '当前歌曲ID: ${controller.currentId.value}, 点击歌曲ID: $currentIdInt'); - if (controller.isPlaying.value) { if (controller.currentId.value == currentIdInt) { Logger.d('MusicPlaylistView', '暂停当前播放的歌曲'); @@ -391,15 +374,15 @@ class MusicPlaylistView extends StatelessWidget { // 构建封面图片 Widget _buildCoverImage(String coverUrl, bool isDarkMode, - {double size = 200}) { + {required double size}) { return Container( width: size, height: size, decoration: BoxDecoration( - borderRadius: BorderRadius.circular(16), + borderRadius: BorderRadius.circular(16.r), boxShadow: [ BoxShadow( - color: Colors.black.withValues(alpha: isDarkMode ? 0.3 : 0.1), + color: Colors.black.withOpacity(isDarkMode ? 0.3 : 0.1), blurRadius: 15, spreadRadius: 2, offset: const Offset(0, 6), @@ -407,27 +390,27 @@ class MusicPlaylistView extends StatelessWidget { ], ), child: ClipRRect( - borderRadius: BorderRadius.circular(16), + borderRadius: BorderRadius.circular(16.r), child: coverUrl.isNotEmpty ? Image.network( coverUrl, fit: BoxFit.cover, errorBuilder: (context, error, stackTrace) { - return _buildDefaultCover(isDarkMode); + return _buildDefaultCover(isDarkMode, size: size); }, ) - : _buildDefaultCover(isDarkMode), + : _buildDefaultCover(isDarkMode, size: size), ), ); } // 构建默认封面 - Widget _buildDefaultCover(bool isDarkMode) { + Widget _buildDefaultCover(bool isDarkMode, {required double size}) { return Container( color: isDarkMode ? Colors.grey[800] : Colors.grey[300], child: Icon( Icons.music_note, - size: 60, // 从20改为60,更合适的大小 + size: size * 0.55, color: isDarkMode ? Colors.grey[600] : Colors.grey[500], ), ); @@ -440,7 +423,7 @@ class MusicPlaylistView extends StatelessWidget { Text( title, style: TextStyle( - fontSize: 18, // 从20减小到18 + fontSize: 18.sp, fontWeight: FontWeight.bold, color: isDarkMode ? Colors.white : Colors.black87, ), @@ -448,11 +431,11 @@ class MusicPlaylistView extends StatelessWidget { maxLines: 2, overflow: TextOverflow.ellipsis, ), - const SizedBox(height: 6), // 从8减小到6 + SizedBox(height: 6.h), Text( artist, style: TextStyle( - fontSize: 14, // 从16减小到14 + fontSize: 14.sp, color: isDarkMode ? Colors.grey[400] : Colors.grey[600], ), textAlign: TextAlign.center, @@ -463,39 +446,36 @@ class MusicPlaylistView extends StatelessWidget { ); } - // 构建进度条 - 优化布局:将进度条和时间显示合并到同一水平线 + // 构建进度条 Widget _buildProgressBar(AgentController controller, bool isDarkMode) { return Obx(() { return Row( children: [ - // 左侧:当前播放时间 SizedBox( - width: 35.w, // 固定宽度确保对齐,足够显示 "99:59" 格式 - + width: 40.w, child: Text( _formatDuration(controller.currentPosition.value), style: TextStyle( - fontSize: 12, + fontSize: 12.sp, color: isDarkMode ? Colors.grey[400] : Colors.grey[600], - fontFeatures: const [FontFeature.tabularFigures()], // 使用等宽数字 + fontFeatures: const [FontFeature.tabularFigures()], ), textAlign: TextAlign.left, ), ), - SizedBox(width: 6.w), // 左侧时间和进度条之间的间距 - // 中间:可交互的进度条 + SizedBox(width: 8.w), Expanded( child: SliderTheme( - data: SliderTheme.of(Get.context!).copyWith( + data: SliderThemeData( activeTrackColor: Theme.of(Get.context!).primaryColor, inactiveTrackColor: isDarkMode ? Colors.grey[700] : Colors.grey[300], thumbColor: Theme.of(Get.context!).primaryColor, overlayColor: - Theme.of(Get.context!).primaryColor.withValues(alpha: 0.2), + Theme.of(Get.context!).primaryColor.withOpacity(0.2), trackHeight: 4.h, - thumbShape: const RoundSliderThumbShape(enabledThumbRadius: 0), - overlayShape: const RoundSliderOverlayShape(overlayRadius: 0), + thumbShape: const RoundSliderThumbShape(enabledThumbRadius: 8), + overlayShape: const RoundSliderOverlayShape(overlayRadius: 14), ), child: Slider( value: controller.progress.value.clamp(0.0, 1.0), @@ -507,16 +487,15 @@ class MusicPlaylistView extends StatelessWidget { ), ), ), - SizedBox(width: 6.w), // 进度条和右侧时间之间的间距 - // 右侧:音乐总时长 + SizedBox(width: 8.w), SizedBox( - width: 35.w, // 固定宽度确保对齐,足够显示 "99:59" 格式 + width: 40.w, child: Text( _formatDuration(controller.duration.value), style: TextStyle( - fontSize: 12, + fontSize: 12.sp, color: isDarkMode ? Colors.grey[400] : Colors.grey[600], - fontFeatures: const [FontFeature.tabularFigures()], // 使用等宽数字 + fontFeatures: const [FontFeature.tabularFigures()], ), textAlign: TextAlign.right, ), @@ -526,14 +505,6 @@ class MusicPlaylistView extends StatelessWidget { }); } - // 格式化时间显示 - String _formatDuration(int milliseconds) { - final duration = Duration(milliseconds: milliseconds); - final minutes = duration.inMinutes; - final seconds = duration.inSeconds % 60; - return '${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}'; - } - // 构建播放控制按钮 Widget _buildPlayControls(AgentController controller, List> playlist, bool isDarkMode) { @@ -541,50 +512,48 @@ class MusicPlaylistView extends StatelessWidget { return Row( mainAxisAlignment: MainAxisAlignment.center, children: [ - // 上一曲按钮 IconButton( onPressed: () => _playPrevious(controller, playlist), + iconSize: 32.w, + padding: EdgeInsets.all(8.w), // 增大触摸区域 icon: Icon( Icons.skip_previous, - size: 32, // 从36减小到32 color: isDarkMode ? Colors.white70 : Colors.black54, ), ), - const SizedBox(width: 16), // 从20减小到16 - // 播放/暂停按钮 + SizedBox(width: 16.w), Container( - width: 56, // 从64减小到56 - height: 56, + width: 56.w, + height: 56.w, decoration: BoxDecoration( color: Theme.of(Get.context!).primaryColor, shape: BoxShape.circle, boxShadow: [ BoxShadow( - color: Theme.of(Get.context!) - .primaryColor - .withValues(alpha: 0.3), - blurRadius: 10, // 从12减小到10 - spreadRadius: 1, // 从2减小到1 - offset: const Offset(0, 3), // 从4减小到3 + color: Theme.of(Get.context!).primaryColor.withOpacity(0.3), + blurRadius: 10, + spreadRadius: 1, + offset: const Offset(0, 3), ), ], ), child: IconButton( onPressed: () => _togglePlayPause(controller), + padding: EdgeInsets.zero, icon: Icon( controller.isPlaying.value ? Icons.pause : Icons.play_arrow, - size: 28, // 从32减小到28 + size: 28.w, color: Colors.white, ), ), ), - const SizedBox(width: 16), // 从20减小到16 - // 下一曲按钮 + SizedBox(width: 16.w), IconButton( onPressed: () => _playNext(controller, playlist), + iconSize: 32.w, + padding: EdgeInsets.all(8.w), // 增大触摸区域 icon: Icon( Icons.skip_next, - size: 32, // 从36减小到32 color: isDarkMode ? Colors.white70 : Colors.black54, ), ), @@ -593,6 +562,15 @@ class MusicPlaylistView extends StatelessWidget { }); } + // ...(其他辅助方法保持不变,如_formatDuration, _togglePlayPause等) + // 格式化时间显示 + String _formatDuration(int milliseconds) { + final duration = Duration(milliseconds: milliseconds); + final minutes = duration.inMinutes; + final seconds = duration.inSeconds % 60; + return '${minutes.toString().padLeft(2, '0')}:${seconds.toString().padLeft(2, '0')}'; + } + // 播放/暂停切换 Future _togglePlayPause(AgentController controller) async { if (controller.isPlaying.value) { @@ -605,7 +583,6 @@ class MusicPlaylistView extends StatelessWidget { // 播放上一曲 void _playPrevious( AgentController controller, List> playlist) { - // 确保播放列表上下文是最新的 controller.updateCurrentPlaylistContext( playlist, controller.currentId.value); controller.playPrevious(); @@ -614,7 +591,6 @@ class MusicPlaylistView extends StatelessWidget { // 播放下一曲 void _playNext( AgentController controller, List> playlist) { - // 确保播放列表上下文是最新的 controller.updateCurrentPlaylistContext( playlist, controller.currentId.value); controller.playNext(); @@ -623,11 +599,8 @@ class MusicPlaylistView extends StatelessWidget { // 播放选中的歌曲 void _playSelectedSong(AgentController controller, Map song, List> playlist) { - // 更新播放列表上下文 final songId = int.tryParse(song['id']?.toString() ?? '0') ?? 0; controller.updateCurrentPlaylistContext(playlist, songId); - - // 播放选中的歌曲 controller.musicPlay(song); } } diff --git a/lib/modules/pairing/controllers/pairing_controller.dart b/lib/modules/pairing/controllers/pairing_controller.dart index 1915aaba3..7a4505126 100644 --- a/lib/modules/pairing/controllers/pairing_controller.dart +++ b/lib/modules/pairing/controllers/pairing_controller.dart @@ -60,6 +60,9 @@ class PairingController extends GetxController { final showRetryButton = false.obs; final showAutoCodeInput = false.obs; final showSkipButton = false.obs; + // 增加临时跳过绑定按钮状态 + final showSkipBindingButton = false.obs; // 新增临时跳过绑定按钮 + final connectButtonText = '连接设备'.obs; final retryButtonText = '重试'.obs; @@ -242,6 +245,7 @@ class PairingController extends GetxController { isLoading.value = false; showConnectButton.value = false; showRetryButton.value = false; + showSkipBindingButton.value = false; // 重置临时跳过绑定按钮 showSkipButton.value = false; showAutoCodeInput.value = false; @@ -285,10 +289,11 @@ class PairingController extends GetxController { }); break; case PairingState.manualbinding: - statusText.value = '需要手动版定授权码...'; + statusText.value = '需要手动绑定授权码...'; isLoading.value = false; showAutoCodeInput.value = true; - showSkipButton.value = true; + // showSkipButton.value = true; + showSkipBindingButton.value = true; // 显示临时跳过绑定按钮 break; case PairingState.completed: statusText.value = '配对成功!'; @@ -557,6 +562,7 @@ class PairingController extends GetxController { }); Logger.i(_tag, '手动授权结果 ${response}'); if (response != null) { + User.instance.earphonemac = currentDevice?.address ?? ''; _transition(PairingState.verifying); _verifyDevice(_currentClassicDevice!); } else { @@ -575,7 +581,7 @@ class PairingController extends GetxController { } catch (e) { Logger.e(_tag, '启动AgentService失败: $e'); } - + Get.offAllNamed(Routes.home); }); } @@ -631,10 +637,19 @@ class PairingController extends GetxController { } } + /// 用户点击跳过绑定按钮 + void onSkipBindingTap() { + Logger.i(_tag, '用户选择临时跳过手动绑定'); + + User.instance.earphonemac = currentDevice?.address ?? ''; + _transition(PairingState.verifying); + _verifyDevice(_currentClassicDevice!); + } + /// 用户点击跳过按钮 void onSkipTap() async { Logger.i(_tag, '用户选择跳过配对'); - + // 启动AgentService服务 try { Logger.i(_tag, '启动AgentService服务'); @@ -642,7 +657,7 @@ class PairingController extends GetxController { } catch (e) { Logger.e(_tag, '启动AgentService失败: $e'); } - + Get.offAllNamed(Routes.home); } diff --git a/lib/modules/pairing/views/pairing_view.dart b/lib/modules/pairing/views/pairing_view.dart index 542e22ec9..9594f3760 100644 --- a/lib/modules/pairing/views/pairing_view.dart +++ b/lib/modules/pairing/views/pairing_view.dart @@ -436,6 +436,7 @@ class PairingView extends GetView { final showRetryButton = controller.showRetryButton.value; final showSkipButton = controller.showSkipButton.value; final showAutoCodeInput = controller.showAutoCodeInput.value; + final showSkipBindingButton = controller.showSkipBindingButton.value; return Container( width: double.infinity, child: Column( @@ -557,6 +558,26 @@ class PairingView extends GetView { ), ), ], + + // 跳过绑定按钮 + if (showSkipBindingButton) ...[ + SizedBox(height: 12.h), + TextButton( + onPressed: () => controller.onSkipBindingTap(), + style: TextButton.styleFrom( + foregroundColor: Color(0xFF0066CC), + padding: EdgeInsets.symmetric(vertical: 12.h), + minimumSize: Size(double.infinity, 48.h), + ), + child: Text( + '跳过绑定'.tr, // 稍后再说 + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w500, + ), + ), + ), + ], ], ), ); diff --git a/lib/routes/app_pages.dart b/lib/routes/app_pages.dart index 8f3a14799..1d544a9e7 100644 --- a/lib/routes/app_pages.dart +++ b/lib/routes/app_pages.dart @@ -15,8 +15,8 @@ import '../modules/opus_test/bindings/opus_test_binding.dart'; import '../modules/opus_test/views/opus_test_view.dart'; import '../modules/profile/views/profile_view.dart'; import '../modules/profile/bindings/profile_binding.dart'; -import '../modules/chat/bindings/chat_binding.dart'; -import '../modules/chat/views/chat_view.dart'; +// import '../modules/chat/bindings/chat_binding.dart'; +//import '../modules/chat/views/chat_view.dart'; import '../modules/timbre/bindings/timbre_binding.dart'; import '../modules/timbre/views/timbre_view.dart'; @@ -91,18 +91,18 @@ abstract class AppPages { transition: Transition.noTransition, transitionDuration: Duration(milliseconds: 250), ), - GetPage( - name: Routes.chat, - page: () => const ChatView(), - binding: ChatBinding(), - transition: Transition.rightToLeft, - transitionDuration: Duration(milliseconds: 250), - ), - GetPage( - name: Routes.recording, - page: () => const ChatView(), - binding: ChatBinding(), - ), + // GetPage( + // name: Routes.chat, + // page: () => const ChatView(), + // binding: ChatBinding(), + // transition: Transition.rightToLeft, + // transitionDuration: Duration(milliseconds: 250), + // ), + // GetPage( + // name: Routes.recording, + // page: () => const ChatView(), + // binding: ChatBinding(), + // ), GetPage( name: Routes.OTA_UPGRADE, page: () => const OtaUpgradeView(), diff --git a/pubspec.yaml b/pubspec.yaml index 200cfc6fb..398b7aa98 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,10 +1,10 @@ name: deep_voice description: "A new Flutter project." -publish_to: 'none' -version: 1.0.0+1 +publish_to: "none" +version: 1.0.0+6 environment: - sdk: '>=3.3.0 <4.0.0' + sdk: ">=3.3.0 <4.0.0" dependencies: flutter: @@ -107,8 +107,8 @@ dev_dependencies: flutter_test: sdk: flutter flutter_lints: ^2.0.0 - build_runner: ^2.3.3 # 用于生成代码 flutter pub run build_runner build - json_serializable: ^6.6.0 # 用于自动生成代码 + build_runner: ^2.3.3 # 用于生成代码 flutter pub run build_runner build + json_serializable: ^6.6.0 # 用于自动生成代码 dependency_overrides: permission_handler: ^12.0.0