From ab2c638a5b07ee79eac0108ecd125b6131eacba2 Mon Sep 17 00:00:00 2001 From: wolfplus Date: Tue, 25 Feb 2025 10:31:24 +0000 Subject: [PATCH] add --- lib/core/bindings/initial_binding.dart | 13 +- lib/core/controllers/app_controller.dart | 40 -- lib/core/routes/app_pages.dart | 9 +- lib/core/views/main_view.dart | 46 --- lib/core/widgets/common_bottom_nav.dart | 47 +++ .../services/voice_recognition_service.dart | 167 -------- lib/data/services/volcano_asr_service.dart | 384 ------------------ lib/main.dart | 2 +- .../chat/controllers/chat_controller.dart | 141 ------- .../controllers/voice_input_controller.dart | 72 +--- lib/modules/explore/views/explore_view.dart | 2 + lib/modules/home/views/home_view.dart | 2 + lib/modules/profile/views/profile_view.dart | 3 +- 13 files changed, 61 insertions(+), 867 deletions(-) delete mode 100644 lib/core/controllers/app_controller.dart delete mode 100644 lib/core/views/main_view.dart create mode 100644 lib/core/widgets/common_bottom_nav.dart delete mode 100644 lib/data/services/voice_recognition_service.dart delete mode 100644 lib/data/services/volcano_asr_service.dart diff --git a/lib/core/bindings/initial_binding.dart b/lib/core/bindings/initial_binding.dart index e9ab76d9b..89e29b78f 100644 --- a/lib/core/bindings/initial_binding.dart +++ b/lib/core/bindings/initial_binding.dart @@ -1,13 +1,8 @@ import 'package:get/get.dart'; -import '../../core/controllers/app_controller.dart'; import '../../core/controllers/permission_controller.dart'; -import '../../modules/home/controllers/home_controller.dart'; -import '../../modules/explore/controllers/explore_controller.dart'; -import '../../modules/profile/controllers/profile_controller.dart'; import '../../data/services/volcano_tts_service.dart'; import '../../data/services/audio_service.dart'; import '../../data/services/notification_service.dart'; -import '../../data/services/voice_recognition_service.dart'; import '../../data/services/volcano_ai_service.dart'; /// 初始绑定,用于管理全局依赖 @@ -16,22 +11,16 @@ class InitialBinding extends Bindings { void dependencies() { // 初始化所有服务 final notificationService = Get.put(NotificationService(), permanent: true); - final voiceRecognitionService = Get.put(VoiceRecognitionService(), permanent: true); final volcanoTtsService = Get.put(VolcanoTtsService(), permanent: true); final audioService = Get.put(AudioServiceManager(), permanent: true); final volcanoAiService = Get.put(VolcanoAIService(), permanent: true); - // 注册 Controllers - Get.put(AppController(), permanent: true); - Get.put(HomeController(), permanent: true); - Get.put(ExploreController(), permanent: true); - Get.put(ProfileController(), permanent: true); + // 只注册全局控制器 Get.put(PermissionController(), permanent: true); // 异步初始化 Future.wait([ notificationService.init(), - voiceRecognitionService.onInit(), // 如果其他服务也需要异步初始化,在这里添加 ]); } diff --git a/lib/core/controllers/app_controller.dart b/lib/core/controllers/app_controller.dart deleted file mode 100644 index 01236a3bd..000000000 --- a/lib/core/controllers/app_controller.dart +++ /dev/null @@ -1,40 +0,0 @@ -import 'package:get/get.dart'; -import 'package:flutter/material.dart'; -import 'package:get_storage/get_storage.dart'; - -class AppController extends GetxController { - static AppController get to => Get.find(); - - final _isDarkMode = false.obs; - final _currentIndex = 0.obs; - final _storage = GetStorage(); - - bool get isDarkMode => _isDarkMode.value; - int get currentIndex => _currentIndex.value; - - @override - void onInit() { - super.onInit(); - _loadThemeMode(); - } - - void _loadThemeMode() { - _isDarkMode.value = _storage.read('isDarkMode') ?? false; - } - - Future toggleThemeMode() async { - _isDarkMode.value = !_isDarkMode.value; - Get.changeThemeMode(_isDarkMode.value ? ThemeMode.dark : ThemeMode.light); - await _storage.write('isDarkMode', _isDarkMode.value); - } - - void changeIndex(int index) { - _currentIndex.value = index; - } - - @override - void onClose() { - // 在这里清理全局资源 - super.onClose(); - } -} \ No newline at end of file diff --git a/lib/core/routes/app_pages.dart b/lib/core/routes/app_pages.dart index fc2a540ee..f2200a5da 100644 --- a/lib/core/routes/app_pages.dart +++ b/lib/core/routes/app_pages.dart @@ -1,22 +1,16 @@ import 'package:get/get.dart'; -import '../bindings/initial_binding.dart'; -import '../views/main_view.dart'; import '../../modules/home/views/home_view.dart'; import '../../modules/home/bindings/home_binding.dart'; import '../../modules/explore/views/explore_view.dart'; import '../../modules/explore/bindings/explore_binding.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 './app_routes.dart'; abstract class AppPages { static final pages = [ - GetPage( - name: Routes.initial, - page: () => const MainView(), - binding: InitialBinding(), - ), GetPage( name: Routes.home, page: () => const HomeView(), @@ -35,6 +29,7 @@ abstract class AppPages { GetPage( name: Routes.profile, page: () => const ProfileView(), + binding: ProfileBinding(), ), ]; } \ No newline at end of file diff --git a/lib/core/views/main_view.dart b/lib/core/views/main_view.dart deleted file mode 100644 index eb41d2437..000000000 --- a/lib/core/views/main_view.dart +++ /dev/null @@ -1,46 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:get/get.dart'; -import '../controllers/app_controller.dart'; -import '../../modules/home/views/home_view.dart'; -import '../../modules/explore/views/explore_view.dart'; -import '../../modules/profile/views/profile_view.dart'; - -class MainView extends GetView { - const MainView({Key? key}) : super(key: key); - - @override - Widget build(BuildContext context) { - return Scaffold( - body: Obx(() { - switch (controller.currentIndex) { - case 0: - return const HomeView(); - case 1: - return const ExploreView(); - case 2: - return const ProfileView(); - default: - return const HomeView(); - } - }), - bottomNavigationBar: Obx(() => BottomNavigationBar( - currentIndex: controller.currentIndex, - onTap: controller.changeIndex, - items: [ - BottomNavigationBarItem( - icon: const Icon(Icons.home), - label: 'home'.tr, - ), - BottomNavigationBarItem( - icon: const Icon(Icons.explore), - label: 'explore'.tr, - ), - BottomNavigationBarItem( - icon: const Icon(Icons.person), - label: 'profile'.tr, - ), - ], - )), - ); - } -} \ No newline at end of file diff --git a/lib/core/widgets/common_bottom_nav.dart b/lib/core/widgets/common_bottom_nav.dart new file mode 100644 index 000000000..1b35aed21 --- /dev/null +++ b/lib/core/widgets/common_bottom_nav.dart @@ -0,0 +1,47 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import '../routes/app_routes.dart'; + +class CommonBottomNav extends StatelessWidget { + final int currentIndex; + + const CommonBottomNav({ + Key? key, + required this.currentIndex, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + return BottomNavigationBar( + currentIndex: currentIndex, + onTap: (index) { + // Navigate to the appropriate page based on index + switch (index) { + case 0: + Get.offAllNamed(Routes.home); + break; + case 1: + Get.offAllNamed(Routes.explore); + break; + case 2: + Get.offAllNamed(Routes.profile); + break; + } + }, + items: [ + BottomNavigationBarItem( + icon: const Icon(Icons.home), + label: 'home'.tr, + ), + BottomNavigationBarItem( + icon: const Icon(Icons.explore), + label: 'explore'.tr, + ), + BottomNavigationBarItem( + icon: const Icon(Icons.person), + label: 'profile'.tr, + ), + ], + ); + } +} \ No newline at end of file diff --git a/lib/data/services/voice_recognition_service.dart b/lib/data/services/voice_recognition_service.dart deleted file mode 100644 index b8cb0d243..000000000 --- a/lib/data/services/voice_recognition_service.dart +++ /dev/null @@ -1,167 +0,0 @@ -// voice_recognition_service.dart - -import 'package:flutter_dotenv/flutter_dotenv.dart'; -import 'package:flutter/foundation.dart'; -import 'volcano_asr_service.dart'; -import 'package:get/get.dart'; - -class VoiceRecognitionService extends GetxService { - VolcanoAsrService? _asrService; - bool _isInitialized = false; - bool _isStoppingRecognition = false; - - final isRecognizing = false.obs; - - Function(String)? onRecognitionResult; - Function(String)? onError; - Function()? onRecognitionComplete; - Function(String)? onSentenceComplete; - Function(String)? onConnectionStatusChanged; - - VoiceRecognitionService(); - - void _logError(String message, [dynamic error, StackTrace? stackTrace]) { - debugPrint('🔴 VoiceRecognitionService Error: $message'); - if (error != null) debugPrint('Error details: $error'); - if (stackTrace != null) debugPrint('Stack trace: $stackTrace'); - } - - @override - Future onInit() async { - super.onInit(); - if (!_isInitialized) { - try { - await _initializeServices(); - _isInitialized = true; - debugPrint('✅ VoiceRecognitionService initialized successfully'); - } catch (e, stackTrace) { - _logError('Failed to initialize services', e, stackTrace); - rethrow; - } - } - } - - Future _initializeServices() async { - try { - final appKey = dotenv.env['VOLCANO_APP_KEY']; - final accessKey = dotenv.env['VOLCANO_ACCESS_KEY']; - if (appKey == null || accessKey == null) { - throw Exception('Volcano ASR service configuration is missing. Please check your .env file.'); - } - _asrService = VolcanoAsrService( - appKey: appKey, - accessKey: accessKey, - onResult: _handleRecognitionResult, - onError: _handleError, - onConnectionStatusChanged: _handleConnectionStatusChanged, - ); - await _setupServices(); - } catch (e, stackTrace) { - _logError('Error during service initialization', e, stackTrace); - rethrow; - } - } - - Future _setupServices() async { - try { - _asrService?.onResult = (result) { - debugPrint('ASR Result: $result'); - onRecognitionResult?.call(result); - onSentenceComplete?.call(result); - }; - _asrService?.onError = (error) { - _logError('ASR service error', error); - onError?.call('ASR error: $error'); - }; - _asrService?.onConnectionStatusChanged = (status) { - final isConnected = status == 'connected'; - debugPrint('ASR Connection status: $status'); - if (!isConnected && isRecognizing.value && !_isStoppingRecognition) { - stopRecognition(); - onRecognitionComplete?.call(); - } - onConnectionStatusChanged?.call(status); - }; - } catch (e, stackTrace) { - _logError('Error during services setup', e, stackTrace); - rethrow; - } - } - - Future startRecognition() async { - if (isRecognizing.value || _asrService == null) { - debugPrint('⚠️ Cannot start recognition: ${isRecognizing.value ? 'Already recognizing' : 'ASR service not initialized'}'); - return; - } - try { - _isStoppingRecognition = false; - debugPrint('Starting voice recognition...'); - await _asrService?.startRecognition(); - isRecognizing.value = true; - } catch (e, stackTrace) { - _logError('Failed to start recognition', e, stackTrace); - onError?.call('Failed to start recognition: $e'); - await stopRecognition(); - } - } - - Future stopRecognition() async { - if (!isRecognizing.value || _asrService == null || _isStoppingRecognition) return; - try { - _isStoppingRecognition = true; - debugPrint('Stopping voice recognition...'); - isRecognizing.value = false; - await _asrService?.stopRecognition().timeout( - const Duration(seconds: 3), - onTimeout: () { - debugPrint('ASR service stop timeout, forcing cleanup'); - _cleanupResources(); - }, - ); - final complete = onRecognitionComplete; - if (complete != null) { - Future.microtask(complete); - } - } catch (e, stackTrace) { - _logError('Failed to stop recognition', e, stackTrace); - onError?.call('Failed to stop recognition: $e'); - _cleanupResources(); - } finally { - _isStoppingRecognition = false; - } - } - - void _cleanupResources() { - if (_asrService != null) { - _asrService!.onResult = null; - _asrService!.onError = null; - _asrService!.onConnectionStatusChanged = null; - } - isRecognizing.value = false; - _isStoppingRecognition = false; - } - - void _handleRecognitionResult(String result) { - onRecognitionResult?.call(result); - } - - void _handleError(String error) { - onError?.call(error); - } - - void _handleConnectionStatusChanged(String status) { - onConnectionStatusChanged?.call(status); - } - - @override - void onClose() { - debugPrint('Cleaning up VoiceRecognitionService...'); - stopRecognition().then((_) { - debugPrint('VoiceRecognitionService cleanup completed'); - super.onClose(); - }).catchError((e) { - debugPrint('Error during VoiceRecognitionService cleanup: $e'); - super.onClose(); - }); - } -} \ No newline at end of file diff --git a/lib/data/services/volcano_asr_service.dart b/lib/data/services/volcano_asr_service.dart deleted file mode 100644 index 7c8b3764d..000000000 --- a/lib/data/services/volcano_asr_service.dart +++ /dev/null @@ -1,384 +0,0 @@ -import 'dart:async'; -import 'dart:convert'; -import 'dart:io'; -import 'package:uuid/uuid.dart'; -import 'package:get/get.dart'; -import 'package:flutter/foundation.dart'; -import 'package:record/record.dart'; - -/// 自定义异常类 -class AsrServiceException implements Exception { - final String message; - AsrServiceException(this.message); - - @override - String toString() => message; -} - -/// Volcano ASR 服务,负责音频采集、数据打包、WebSocket 连接和数据传输 -class VolcanoAsrService extends GetxService { - // 服务地址 - static const String _baseUrl = 'wss://openspeech.bytedance.com/api/v3/sauc/bigmodel'; - - // 配置参数 - final String appKey; - final String accessKey; - - // WebSocket 与录音相关 - WebSocket? _webSocket; - AudioRecorder? _audioRecorder; - StreamSubscription>? _audioStreamSubscription; - - // 状态管理 - bool _isConnected = false; - bool _isRecording = false; - int _currentSequence = 1; - int _serverSequence = 0; // 添加服务器序列号跟踪 - bool _isInitialized = false; - bool _waitingForInitResponse = false; - Completer? _initCompleter; - - // 回调 - Function(String)? onResult; - Function(String)? onError; - Function(String)? onConnectionStatusChanged; - - VolcanoAsrService({ - required this.appKey, - required this.accessKey, - this.onResult, - this.onError, - this.onConnectionStatusChanged, - }); - - Future _connectWebSocket() async { - if (_webSocket != null) { - debugPrint('⚠️ ASR: WebSocket already exists'); - return; - } - - final connectId = const Uuid().v4(); - final headers = { - 'X-Api-App-Key': appKey, - 'X-Api-Access-Key': accessKey, - 'X-Api-Resource-Id': 'volc.bigasr.sauc.duration', - 'X-Api-Connect-Id': connectId, - 'Connection': 'Upgrade', // 添加 WebSocket 升级头 - 'Upgrade': 'websocket', - }; - - try { - _webSocket = await WebSocket.connect( - _baseUrl, - headers: headers, - protocols: ['binary'], // 指定二进制协议 - ); - debugPrint('ℹ️ ASR: WebSocket connected'); - - _webSocket!.listen( - _handleWebSocketMessage, - onError: (error) { - debugPrint('🔴 ASR: WebSocket error: $error'); - onError?.call('WebSocket error: $error'); - _handleDisconnect(); - }, - onDone: () { - debugPrint('ℹ️ ASR: WebSocket closed'); - if (_waitingForInitResponse) { - _initCompleter?.completeError('WebSocket closed during initialization'); - _initCompleter = null; - } - _handleDisconnect(); - }, - cancelOnError: false, // 不要在错误时自动取消 - ); - - _isConnected = true; - _currentSequence = 1; // 重置序列号 - _isInitialized = false; // 重置初始化状态 - onConnectionStatusChanged?.call('connected'); - - // 发送初始化请求前等待一下 - await Future.delayed(const Duration(milliseconds: 100)); - await _sendInitRequest(connectId); - - } catch (e) { - debugPrint('🔴 ASR: Failed to connect: $e'); - rethrow; - } - } - - Future _sendInitRequest(String requestId) async { - _waitingForInitResponse = true; - _initCompleter = Completer(); - - final initRequest = { - 'audio': { - 'format': 'raw', - 'sample_rate': 16000, - 'channels': 1, - 'encoding': 'pcm', - 'bit_depth': 16, - }, - 'request': { - 'request_id': requestId, - 'language': 'zh', - 'model_name': 'bigmodel', - 'enable_punc': true, - 'enable_itn': true, - 'enable_voice_detection': true, - } - }; - - final jsonBytes = utf8.encode(json.encode(initRequest)); - debugPrint('ℹ️ ASR: Sending init request: ${json.encode(initRequest)}'); - - final header = Uint8List(4) - ..[0] = 0x11 // version=1, header_size=1 - ..[1] = 0x11 // message_type=1(FULL_CLIENT_REQUEST), flags=1(POS_SEQUENCE) - ..[2] = 0x10 // serialization=1(JSON), compression=0 - ..[3] = 0x00; // reserved - - final seqBytes = ByteData(4)..setInt32(0, _currentSequence, Endian.big); - final sizeBytes = ByteData(4)..setInt32(0, jsonBytes.length, Endian.big); - - final fullRequest = Uint8List(header.length + 8 + jsonBytes.length) - ..setAll(0, header) - ..setAll(4, seqBytes.buffer.asUint8List()) - ..setAll(8, sizeBytes.buffer.asUint8List()) - ..setAll(12, jsonBytes); - - _webSocket?.add(fullRequest); - debugPrint('ℹ️ ASR: Init request sent with sequence: $_currentSequence'); - - // 设置超时 - Future.delayed(const Duration(seconds: 10), () { - if (_waitingForInitResponse) { - debugPrint('⚠️ ASR: Init request timeout after 10 seconds'); - _waitingForInitResponse = false; - _initCompleter?.completeError('Init request timeout'); - _initCompleter = null; - } - }); - - try { - await _initCompleter?.future; - } catch (e) { - debugPrint('🔴 ASR: Init request failed: $e'); - rethrow; - } - } - - void _handleWebSocketMessage(dynamic message) { - debugPrint('ℹ️ ASR: Received message type: ${message.runtimeType}'); - - if (message is String) { - debugPrint('ℹ️ ASR: Received string message: $message'); - try { - final data = json.decode(message); - if (data is Map) { - if (data['error'] != null) { - debugPrint('🔴 ASR: Server error: ${data['error']}'); - if (_waitingForInitResponse) { - _waitingForInitResponse = false; - _initCompleter?.completeError(data['error']); - _initCompleter = null; - } - onError?.call(data['error']); - return; - } - } - } catch (e) { - debugPrint('⚠️ ASR: Failed to parse string message: $e'); - } - } - - if (message is! Uint8List || message.length < 12) { - debugPrint('⚠️ ASR: Invalid binary message format'); - return; - } - - final version = message[0] >> 4; - final headerSize = message[0] & 0x0F; - final messageType = message[1] >> 4; - final flags = message[1] & 0x0F; - final serialization = message[2] >> 4; - final compression = message[2] & 0x0F; - - debugPrint('ℹ️ ASR: Binary message: version=$version, headerSize=$headerSize, type=$messageType, flags=$flags, serialization=$serialization, compression=$compression'); - - final sequence = ByteData.view(message.buffer, 4, 4).getInt32(0, Endian.big); - debugPrint('ℹ️ ASR: Message sequence: $sequence'); - _serverSequence = sequence; - - final payloadSize = ByteData.view(message.buffer, 8, 4).getInt32(0, Endian.big); - if (message.length < 12 + payloadSize) { - debugPrint('⚠️ ASR: Incomplete message received'); - return; - } - - if (messageType == 0x0F) { // INIT_RESPONSE - if (_waitingForInitResponse) { - debugPrint('ℹ️ ASR: Received init response (type 15)'); - if (payloadSize > 0) { - final payload = message.sublist(12, 12 + payloadSize); - final jsonStr = utf8.decode(payload); - debugPrint('ℹ️ ASR: Init response payload: $jsonStr'); - try { - final response = json.decode(jsonStr); - if (response is Map) { - if (response['error'] != null) { - final error = response['error']; - debugPrint('🔴 ASR: Init response error: $error'); - _waitingForInitResponse = false; - _initCompleter?.completeError(error); - _initCompleter = null; - return; - } - debugPrint('ℹ️ ASR: Init response success'); - _waitingForInitResponse = false; - _currentSequence = (_serverSequence + 1); - _isInitialized = true; - _initCompleter?.complete(); - _initCompleter = null; - return; - } - } catch (e) { - debugPrint('⚠️ ASR: Failed to parse init response: $e'); - } - } else { - debugPrint('ℹ️ ASR: Empty init response success'); - _waitingForInitResponse = false; - _currentSequence = (_serverSequence + 1); - _isInitialized = true; - _initCompleter?.complete(); - _initCompleter = null; - return; - } - } - } else if (messageType == 0x09) { // FULL_SERVER_RESPONSE - final payload = message.sublist(12, 12 + payloadSize); - final jsonStr = utf8.decode(payload); - debugPrint('ℹ️ ASR: Received binary payload: $jsonStr'); - - try { - final response = json.decode(jsonStr); - if (response is Map && response['result'] != null) { - final result = response['result']; - if (result['text'] != null && result['text'].isNotEmpty) { - debugPrint('ℹ️ ASR: Result: ${result['text']}'); - onResult?.call(result['text']); - } - } - } catch (e) { - debugPrint('⚠️ ASR: Failed to parse binary payload: $e'); - } - } - } - - Future _startRecording() async { - _audioRecorder ??= AudioRecorder(); - - try { - if (!await _audioRecorder!.hasPermission()) { - throw Exception('Microphone permission denied'); - } - - const config = RecordConfig( - encoder: AudioEncoder.pcm16bits, - bitRate: 256000, - sampleRate: 16000, - numChannels: 1, - ); - - // 启动录音流 - final stream = await _audioRecorder!.startStream(config); - _isRecording = true; - - // 监听音频流 - _audioStreamSubscription = stream.listen( - (data) { - if (_isRecording && _isConnected) { - _sendAudioData(data, false); - } - }, - onError: (error) { - debugPrint('🔴 ASR: Stream error: $error'); - _stopRecording(); - }, - ); - - } catch (e) { - debugPrint('🔴 ASR: Recording error: $e'); - await _stopRecording(); - rethrow; - } - } - - void _sendAudioData(List audioData, bool isLast) { - if (!_isConnected || _webSocket == null || !_isInitialized) { - debugPrint('⚠️ ASR: Cannot send audio data: not ready'); - return; - } - - final header = Uint8List(4) - ..[0] = 0x11 // version=1, header_size=1 - ..[1] = isLast ? 0x23 : 0x21 // message_type=2(AUDIO_ONLY), flags=3/1 - ..[2] = 0x00 // no serialization, no compression - ..[3] = 0x00; // reserved - - final seqBytes = ByteData(4)..setInt32(0, _currentSequence, Endian.big); - final sizeBytes = ByteData(4)..setInt32(0, audioData.length, Endian.big); - - final packet = Uint8List(12 + audioData.length) - ..setAll(0, header) - ..setAll(4, seqBytes.buffer.asUint8List()) - ..setAll(8, sizeBytes.buffer.asUint8List()) - ..setAll(12, audioData); - - _webSocket?.add(packet); - debugPrint('🔍 ASR: Sent audio data with sequence: $_currentSequence'); - _currentSequence++; // 递增序列号 - } - - void _handleDisconnect() { - _webSocket = null; - _isConnected = false; - _isInitialized = false; // 重置初始化状态 - _currentSequence = 1; // 重置序列号 - onConnectionStatusChanged?.call('disconnected'); - _stopRecording(); - } - - Future _stopRecording() async { - _isRecording = false; - - try { - await _audioStreamSubscription?.cancel(); - _audioStreamSubscription = null; - - await _audioRecorder?.stop(); - _audioRecorder = null; - } catch (e) { - debugPrint('⚠️ ASR: Error stopping recording: $e'); - } - } - - Future startRecognition() async { - await _connectWebSocket(); - await _startRecording(); - } - - Future stopRecognition() async { - await _stopRecording(); - _webSocket?.close(); - _handleDisconnect(); - } - - @override - void onClose() { - _audioStreamSubscription?.cancel(); - stopRecognition(); - super.onClose(); - } -} \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index b48ae8d10..767d70961 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -61,7 +61,7 @@ class MainApp extends StatelessWidget { visualDensity: VisualDensity.adaptivePlatformDensity, ), initialBinding: InitialBinding(), - initialRoute: Routes.initial, + initialRoute: Routes.home, getPages: AppPages.pages, debugShowCheckedModeBanner: false, ); diff --git a/lib/modules/chat/controllers/chat_controller.dart b/lib/modules/chat/controllers/chat_controller.dart index dbe7968c0..a67d70d2e 100644 --- a/lib/modules/chat/controllers/chat_controller.dart +++ b/lib/modules/chat/controllers/chat_controller.dart @@ -5,7 +5,6 @@ import 'dart:convert'; import '../../../data/services/volcano_ai_service.dart'; import '../../../data/services/volcano_tts_service.dart'; import '../models/message_model.dart'; -import '../../../data/services/voice_recognition_service.dart'; import '../controllers/voice_input_controller.dart'; import 'package:flutter/services.dart'; @@ -20,7 +19,6 @@ class ChatController extends GetxController { final VolcanoAIService _aiService = VolcanoAIService(); late final VolcanoTtsService _ttsService; - late final VoiceRecognitionService _recognitionService; static const String _storagePrefix = 'chat_history_'; final messages = [].obs; @@ -50,7 +48,6 @@ class ChatController extends GetxController { messageController = TextEditingController(); scrollController = ScrollController(); _initTtsService(); - _initVoiceRecognitionService(); _initFromArguments(); _initializeController(); @@ -99,118 +96,6 @@ class ChatController extends GetxController { _ttsService.isEnabled.value = true; } - void _initVoiceRecognitionService() { - _recognitionService = Get.find(); - _setupVoiceRecognitionCallbacks(); - } - - void _setupVoiceRecognitionCallbacks() { - _recognitionService.onRecognitionResult = (result) { - if (isVoiceMuted.value) return; - recordingText.value = result; - handleRecognizing(result); - }; - - _recognitionService.onError = (error) { - // 不要立即显示错误,先尝试重新连接 - _handleRecognitionError(error); - }; - - _recognitionService.onRecognitionComplete = () { - stopRecording(); - if (!isVoiceMuted.value && recordingText.value.isNotEmpty) { - handleVoiceResult(recordingText.value); - } - }; - - _recognitionService.onConnectionStatusChanged = (status) { - if (status == 'disconnected' && isRecording.value) { - // 如果在录音过程中断开连接,尝试重新连接 - _handleWebSocketDisconnection(); - } - }; - - // 添加句子完成的回调 - _recognitionService.onSentenceComplete = (text) { - if (!isVoiceMuted.value && text.isNotEmpty) { - handleVoiceResult(text); - // 继续录音,等待下一句话 - if (isVoiceInputVisible.value) { - startRecording(); - } - } - }; - } - - Future _handleRecognitionError(String error) async { - // 如果是权限相关错误,请求权限 - if (error.toLowerCase().contains('permission')) { - try { - const platform = MethodChannel('com.example.deep_voice/audio'); - await platform.invokeMethod('requestAudioPermissions'); - // 权限请求后重试 - if (isVoiceInputVisible.value) { - await Future.delayed(const Duration(seconds: 1)); - startRecording(); - } - return; - } catch (e) { - debugPrint('Failed to request permissions: $e'); - } - } - - // 如果是连接错误,尝试重新连接 - if (error.toLowerCase().contains('connection') || error.toLowerCase().contains('websocket')) { - _handleWebSocketDisconnection(); - return; - } - - // 其他错误显示给用户 - Get.snackbar( - 'Error', - '语音识别失败: $error', - snackPosition: SnackPosition.BOTTOM, - ); - stopVoiceInput(); - } - - Future _handleWebSocketDisconnection() async { - if (!isVoiceInputVisible.value) return; - - isVoiceConnecting.value = true; - stopRecording(); - - // 保存当前的语音文本 - final currentText = recordingText.value; - - try { - // 等待一段时间后重试 - await Future.delayed(const Duration(seconds: 2)); - if (!isVoiceInputVisible.value) return; - - // 重新初始化语音识别 - await _recognitionService.onInit(); - - // 恢复之前的状态 - if (currentText.isNotEmpty) { - recordingText.value = currentText; - } - - isVoiceConnecting.value = false; - if (isVoiceInputVisible.value) { - startRecording(); - } - } catch (e) { - debugPrint('Failed to reconnect: $e'); - Get.snackbar( - 'Error', - '重新连接失败,请稍后再试', - snackPosition: SnackPosition.BOTTOM, - ); - stopVoiceInput(); - } - } - Future _initializeController() async { try { await _loadChatHistory(); @@ -584,40 +469,17 @@ class ChatController extends GetxController { onClosePanel: stopVoiceInput, onRecognizing: handleRecognizing, )); - - // 初始化语音识别 - _initializeVoiceRecognition(); - } - - Future _initializeVoiceRecognition() async { - try { - await _recognitionService.onInit(); - isVoiceConnecting.value = false; - if (isVoiceInputVisible.value) { - startRecording(); - } - } catch (e) { - debugPrint('Failed to initialize voice recognition: $e'); - Get.snackbar( - 'Error', - '初始化语音识别失败: $e', - snackPosition: SnackPosition.BOTTOM, - ); - stopVoiceInput(); - } } void startRecording() { if (!isVoiceConnecting.value && isVoiceInputVisible.value) { isRecording.value = true; - _recognitionService.startRecognition(); } } void stopRecording() { if (isRecording.value) { isRecording.value = false; - _recognitionService.stopRecognition(); } } @@ -631,9 +493,6 @@ class ChatController extends GetxController { isRecording.value = false; recordingText.value = ''; - // 停止语音识别服务 - _recognitionService.stopRecognition(); - // 如果存在临时语音消息但没有实际内容,则移除它 if (_currentVoiceMessage != null && _currentVoiceMessage!.content == '🎤 ...') { messages.remove(_currentVoiceMessage); diff --git a/lib/modules/chat/controllers/voice_input_controller.dart b/lib/modules/chat/controllers/voice_input_controller.dart index e42b65a66..5e2e81d0d 100644 --- a/lib/modules/chat/controllers/voice_input_controller.dart +++ b/lib/modules/chat/controllers/voice_input_controller.dart @@ -1,10 +1,7 @@ import 'package:get/get.dart'; import 'package:flutter/foundation.dart'; -import '../../../data/services/voice_recognition_service.dart'; class VoiceInputController extends GetxController { - final VoiceRecognitionService _recognitionService; - // Observable states final isConnecting = true.obs; final isMuted = false.obs; @@ -20,59 +17,17 @@ class VoiceInputController extends GetxController { required this.onRecordingResult, required this.onClosePanel, required this.onRecognizing, - }) : _recognitionService = Get.find(); + }); @override void onInit() { super.onInit(); - _setupRecognitionCallbacks(); - _initializeRecognition(); - } - - void _setupRecognitionCallbacks() { - _recognitionService.onRecognitionResult = (result) { - if (isMuted.value) return; - recognizedText.value = result; - onRecognizing(result); - }; - - _recognitionService.onError = (error) { - Get.snackbar( - 'Error', - '语音识别失败: $error', - snackPosition: SnackPosition.BOTTOM, - ); - stopRecording(); - }; - - _recognitionService.onRecognitionComplete = () { - stopRecording(); - if (!isMuted.value && recognizedText.value.isNotEmpty) { - onRecordingResult(recognizedText.value); - recognizedText.value = ''; - startRecording(); - } - }; - } - - Future _initializeRecognition() async { - try { - isConnecting.value = true; - await _recognitionService.startRecognition(); - isConnecting.value = false; - } catch (e) { - Get.snackbar( - 'Error', - '初始化语音识别失败: $e', - snackPosition: SnackPosition.BOTTOM, - ); - onClosePanel(); - } + // 初始化完成后更新状态 + isConnecting.value = false; } Future startRecording() async { try { - await _recognitionService.startRecognition(); isRecording.value = true; recognizedText.value = ''; } catch (e) { @@ -86,14 +41,7 @@ class VoiceInputController extends GetxController { Future stopRecording() async { if (!isRecording.value) return; - - try { - await _recognitionService.stopRecognition(); - } catch (e) { - debugPrint('Error stopping recording: $e'); - } finally { - isRecording.value = false; - } + isRecording.value = false; } void toggleMute() { @@ -105,24 +53,12 @@ class VoiceInputController extends GetxController { // 停止录音 stopRecording(); - // 清理回调 - _recognitionService.onRecognitionResult = null; - _recognitionService.onError = null; - _recognitionService.onRecognitionComplete = null; - // 重置状态 isConnecting.value = false; isMuted.value = false; isRecording.value = false; recognizedText.value = ''; - // 确保语音识别服务停止 - try { - _recognitionService.stopRecognition(); - } catch (e) { - debugPrint('Error stopping recognition service: $e'); - } - super.onClose(); } } \ No newline at end of file diff --git a/lib/modules/explore/views/explore_view.dart b/lib/modules/explore/views/explore_view.dart index 8d9960b8b..a1ca0fa5e 100644 --- a/lib/modules/explore/views/explore_view.dart +++ b/lib/modules/explore/views/explore_view.dart @@ -5,6 +5,7 @@ import '../controllers/explore_controller.dart'; import '../../chat/controllers/chat_controller.dart'; import '../../chat/views/chat_view.dart'; import '../../../data/providers/agent_provider.dart'; +import '../../../core/widgets/common_bottom_nav.dart'; class ExploreView extends GetView { const ExploreView({Key? key}) : super(key: key); @@ -152,6 +153,7 @@ class ExploreView extends GetView { ), itemBuilder: (context, index) => _buildChatItem(controller.chatList[index]), )), + bottomNavigationBar: const CommonBottomNav(currentIndex: 1), ); } } \ No newline at end of file diff --git a/lib/modules/home/views/home_view.dart b/lib/modules/home/views/home_view.dart index af46d504c..c43ae08d7 100644 --- a/lib/modules/home/views/home_view.dart +++ b/lib/modules/home/views/home_view.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import '../controllers/home_controller.dart'; +import '../../../core/widgets/common_bottom_nav.dart'; class HomeView extends GetView { const HomeView({Key? key}) : super(key: key); @@ -275,6 +276,7 @@ class HomeView extends GetView { ), ), ), + bottomNavigationBar: const CommonBottomNav(currentIndex: 0), ); } } \ No newline at end of file diff --git a/lib/modules/profile/views/profile_view.dart b/lib/modules/profile/views/profile_view.dart index ab4a672b0..1336c4c68 100644 --- a/lib/modules/profile/views/profile_view.dart +++ b/lib/modules/profile/views/profile_view.dart @@ -1,9 +1,9 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import '../../../core/controllers/app_controller.dart'; import '../controllers/profile_controller.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import '../../../data/services/volcano_tts_service.dart'; +import '../../../core/widgets/common_bottom_nav.dart'; class ProfileView extends GetView { const ProfileView({Key? key}) : super(key: key); @@ -134,6 +134,7 @@ class ProfileView extends GetView { ), ], ), + bottomNavigationBar: const CommonBottomNav(currentIndex: 2), ); } } \ No newline at end of file