diff --git a/lib/modules/translation/bindings/translation_binding.dart b/lib/modules/translation/bindings/translation_binding.dart index b9071e619..afc89fc87 100644 --- a/lib/modules/translation/bindings/translation_binding.dart +++ b/lib/modules/translation/bindings/translation_binding.dart @@ -1,10 +1,10 @@ import 'package:get/get.dart'; import '../controllers/translation_controller.dart'; - +//主翻译页面依赖绑定 class TranslationBinding implements Bindings { @override void dependencies() { Get.put(TranslationController()); } -} \ No newline at end of file +} diff --git a/lib/modules/translation/bindings/translation_detail_binding.dart b/lib/modules/translation/bindings/translation_detail_binding.dart index 5e779995d..5b50d238f 100644 --- a/lib/modules/translation/bindings/translation_detail_binding.dart +++ b/lib/modules/translation/bindings/translation_detail_binding.dart @@ -1,9 +1,11 @@ import 'package:get/get.dart'; import '../controllers/translation_detail_controller.dart'; +//翻译详情页面依赖绑定 class TranslationDetailBinding extends Bindings { @override void dependencies() { - Get.lazyPut(() => TranslationDetailController()); + Get.lazyPut( + () => TranslationDetailController()); } -} \ No newline at end of file +} diff --git a/lib/modules/translation/bindings/translation_history_binding.dart b/lib/modules/translation/bindings/translation_history_binding.dart index 613b525f6..8b63d17c0 100644 --- a/lib/modules/translation/bindings/translation_history_binding.dart +++ b/lib/modules/translation/bindings/translation_history_binding.dart @@ -1,6 +1,7 @@ import 'package:get/get.dart'; import '../controllers/translation_audiohistory_controller.dart'; +//翻译历史记录页面依赖绑定 class TranslationHistoryBinding extends Bindings { @override void dependencies() { diff --git a/lib/modules/translation/bindings/translation_list_binding.dart b/lib/modules/translation/bindings/translation_list_binding.dart index 4dc264ada..9d753c170 100644 --- a/lib/modules/translation/bindings/translation_list_binding.dart +++ b/lib/modules/translation/bindings/translation_list_binding.dart @@ -1,6 +1,7 @@ import 'package:get/get.dart'; import '../controllers/translation_list_controller.dart'; +//翻译列表页面依赖绑定 class TranslationListBinding extends Bindings { @override void dependencies() { diff --git a/lib/modules/translation/bindings/translation_stats_binding.dart b/lib/modules/translation/bindings/translation_stats_binding.dart index 031cdc0ea..45c2824b1 100644 --- a/lib/modules/translation/bindings/translation_stats_binding.dart +++ b/lib/modules/translation/bindings/translation_stats_binding.dart @@ -2,6 +2,7 @@ import 'package:get/get.dart'; import '../controllers/translation_stats_controller.dart'; import '../../../data/services/translation_usage_service.dart'; +//翻译统计页面依赖绑定 class TranslationStatsBinding implements Bindings { @override void dependencies() { @@ -9,7 +10,7 @@ class TranslationStatsBinding implements Bindings { if (!Get.isRegistered()) { Get.put(TranslationUsageService(), permanent: true); } - + // 注册控制器 Get.lazyPut( () => TranslationStatsController(), diff --git a/lib/modules/translation/controllers/translation_controller.dart b/lib/modules/translation/controllers/translation_controller.dart index f1bfff331..cfb187c98 100644 --- a/lib/modules/translation/controllers/translation_controller.dart +++ b/lib/modules/translation/controllers/translation_controller.dart @@ -31,7 +31,7 @@ enum AudioOutputDevice { speaker, // 强制扬声器 } -/// 翻译控制器 - 负责管理语音识别、翻译和TTS功能(主控制器) +/// 翻译主控制器 - 负责管理语音识别、翻译和TTS功能(主控制器) class TranslationController extends GetxController with WidgetsBindingObserver { static const String TAG = 'TranslationController'; @@ -62,6 +62,8 @@ class TranslationController extends GetxController with WidgetsBindingObserver { // 存储相关配置 static const String _sourceLanguageKey = 'translation_source_language'; static const String _targetLanguageKey = 'translation_target_language'; + // 添加悬浮按钮位置存储键 + static const String _floatingButtonPositionKey = 'floating_button_position'; // 分页加载相关状态 final pageSize = 20; // 每页记录数量 @@ -77,6 +79,9 @@ class TranslationController extends GetxController with WidgetsBindingObserver { final isCreateRecord = false.obs; final hasRecordPermission = false.obs; + // 添加悬浮按钮位置状态 + final floatingButtonPosition = Rx(Offset(2.0, 100.0)); // 默认位置 + // ==================== 语言相关 ==================== final sourceLanguage = '中文(简体)'.obs; final targetLanguage = '英语'.obs; @@ -319,6 +324,28 @@ class TranslationController extends GetxController with WidgetsBindingObserver { supportedLanguages.containsKey(savedTargetLanguage)) { targetLanguage.value = savedTargetLanguage; } + + // 加载悬浮按钮位置 + _loadFloatingButtonPosition(); + } + + /// 加载悬浮按钮位置 + void _loadFloatingButtonPosition() { + final savedPosition = _storage.read(_floatingButtonPositionKey); + if (savedPosition != null && savedPosition is Map) { + final dx = savedPosition['dx']?.toDouble() ?? 2.0; + final dy = savedPosition['dy']?.toDouble() ?? 100.0; + floatingButtonPosition.value = Offset(dx, dy); + } + } + + /// 保存悬浮按钮位置 + void saveFloatingButtonPosition(Offset position) { + floatingButtonPosition.value = position; + _storage.write(_floatingButtonPositionKey, { + 'dx': position.dx, + 'dy': position.dy, + }); } /// 保存当前语言设置 @@ -686,6 +713,8 @@ class TranslationController extends GetxController with WidgetsBindingObserver { handleFinalResult(event.text); } else { faceToFaceRecognitionText.value += event.text; + // 添加日志打印 + Logger.info('lxm-面对面翻译识别文本更新: ${faceToFaceRecognitionText.value}'); } if (currentMode.value == 'faceToFace' && activeSpeaker.value == 0) { handleFinalResult(faceToFaceRecognitionText.value); @@ -955,6 +984,20 @@ class TranslationController extends GetxController with WidgetsBindingObserver { translationHistory[index].targetLanguageCode = languageCodes['target']!; translationHistory.refresh(); + // 添加TranslationItem日志打印 + if (isFinal) { + final item = translationHistory[index]; + Logger.info('lxm-翻译完成 - TranslationItem详情: ' + '源文本: ${item.sourceText}, ' + '翻译文本: ${item.translatedText}, ' + '源语言: ${item.sourceLanguageCode}, ' + '目标语言: ${item.targetLanguageCode}, ' + '时间戳: ${item.timestamp.toIso8601String()}, ' + '会话ID: ${item.sessionId}, ' + '是否会话首项: ${item.isFirstInSession}, ' + '是否中间结果: ${item.isIntermediate}'); + } + if (isFinal) { saveTranslationHistory(); await _recordTranslationStats( diff --git a/lib/modules/translation/models/translation_models.dart b/lib/modules/translation/models/translation_models.dart index 4f9679539..d4df1913d 100644 --- a/lib/modules/translation/models/translation_models.dart +++ b/lib/modules/translation/models/translation_models.dart @@ -12,7 +12,7 @@ class TranslationItem { String sessionId; final bool isFirstInSession; bool isIntermediate; // 是否为中间结果 - + TranslationItem({ required this.sourceText, required this.translatedText, @@ -23,7 +23,7 @@ class TranslationItem { this.isFirstInSession = false, this.isIntermediate = false, }); - + /// 从JSON创建TranslationItem实例 factory TranslationItem.fromJson(Map json) { DateTime timestamp; @@ -32,7 +32,7 @@ class TranslationItem { } catch (e) { timestamp = DateTime.now(); } - + return TranslationItem( sourceText: json['sourceText'] ?? '', translatedText: json['translatedText'] ?? '', @@ -45,7 +45,7 @@ class TranslationItem { isIntermediate: false, // 从存储加载的都是最终结果 ); } - + /// 转换为JSON格式 Map toJson() { return { @@ -59,7 +59,7 @@ class TranslationItem { // 不保存isIntermediate,因为存储的都是最终结果 }; } - + /// 获取源语言的中文名称 String get sourceLanguageName { final languageManager = Get.find(); @@ -73,4 +73,4 @@ class TranslationItem { return languageManager.getChineseNameByAsrCode(targetLanguageCode) ?? 'meetingUnknownLanguage'.tr; // 对应中文:未知语言 } -} \ No newline at end of file +} diff --git a/lib/modules/translation/views/translation_view.dart b/lib/modules/translation/views/translation_view.dart index b1f91a87c..ae20249ce 100644 --- a/lib/modules/translation/views/translation_view.dart +++ b/lib/modules/translation/views/translation_view.dart @@ -20,13 +20,173 @@ class TranslationView extends GetView { Widget build(BuildContext context) { final isDarkMode = Theme.of(context).brightness == Brightness.dark; + return Obx(() { + // 面对面翻译模式使用特殊布局 + if (controller.currentMode.value == 'faceToFace') { + return _buildFaceToFaceScaffold(isDarkMode); + } + + // 其他模式使用原有布局 + return Scaffold( + backgroundColor: + isDarkMode ? AppColors.darkBackground : Colors.grey[50], + appBar: _buildAppBar(isDarkMode), + body: Stack( + children: [ + _buildBody(isDarkMode), + // 可拖拽的悬浮按钮 + _buildDraggableFloatingButton(isDarkMode), + ], + ), + ); + }); + } + + // 构建面对面翻译模式的专用Scaffold + Widget _buildFaceToFaceScaffold(bool isDarkMode) { return Scaffold( backgroundColor: isDarkMode ? AppColors.darkBackground : Colors.grey[50], - appBar: _buildAppBar(isDarkMode), - body: _buildBody(isDarkMode), + body: Stack( + children: [ + // 主要内容区域 + _buildFaceToFaceLayout(isDarkMode), + + // 顶部按钮(倒过来) + Positioned( + top: 50.h, + left: 0, + right: 0, + child: Transform.rotate( + angle: 3.14159, // 180度旋转 + child: _buildFaceToFaceTopButton(isDarkMode), + ), + ), + + // 底部按钮 + Positioned( + bottom: 40.h, + left: 0, + right: 0, + child: _buildFaceToFaceBottomButton(isDarkMode), + ), + + // 可拖拽的悬浮按钮 - 在所有模式下都显示 + _buildDraggableFloatingButton(isDarkMode), + ], + ), ); } + // 构建面对面翻译顶部按钮(原右边按钮,现在移到顶部并旋转) + Widget _buildFaceToFaceTopButton(bool isDarkMode) { + return Center( + child: Transform.rotate( + angle: 0, // 180度旋转 + child: _buildHoldToTalkButton( + isDarkMode: isDarkMode, + speakerId: 2, // 说话者2(原右边按钮) + activeSpeaker: controller.activeSpeaker.value, + icon: Icons.mic_none, + label: controller.targetLanguage.value.split(' ')[0].tr, // 目标语言名称 + ), + ), + ); + } + + // 构建面对面翻译底部按钮(原左边按钮,保留在底部) + Widget _buildFaceToFaceBottomButton(bool isDarkMode) { + return Center( + child: _buildHoldToTalkButton( + isDarkMode: isDarkMode, + speakerId: 1, // 说话者1(原左边按钮) + activeSpeaker: controller.activeSpeaker.value, + icon: Icons.mic_none, + label: controller.sourceLanguage.value.split(' ')[0].tr, // 源语言名称 + ), + ); + } + + // 构建可拖拽的悬浮按钮 + Widget _buildDraggableFloatingButton(bool isDarkMode) { + return Obx(() => Positioned( + right: controller.floatingButtonPosition.value.dx, // 使用保存的位置 + bottom: controller.floatingButtonPosition.value.dy, // 使用保存的位置 + child: Draggable( + feedback: Material( + color: Colors.transparent, + child: Container( + width: 56.w, + height: 56.w, + decoration: BoxDecoration( + color: (isDarkMode ? Colors.grey[700] : Colors.blue) + ?.withOpacity(0.8), + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.3), + blurRadius: 8, + offset: const Offset(0, 4), + ), + ], + ), + child: Icon( + Icons.translate, + color: Colors.white, + size: 24.sp, + ), + ), + ), + childWhenDragging: Container( + width: 56.w, + height: 56.w, + decoration: BoxDecoration( + color: (isDarkMode ? Colors.grey[700] : Colors.blue) + ?.withOpacity(0.3), + shape: BoxShape.circle, + ), + ), + onDragEnd: (details) { + // 计算新位置(相对于屏幕右下角) + final screenSize = MediaQuery.of(Get.context!).size; + final newRight = screenSize.width - details.offset.dx - 56.w; + final newBottom = screenSize.height - details.offset.dy - 56.w; + + // 边界检查,确保按钮不会超出屏幕 + final clampedRight = newRight.clamp(0.0, screenSize.width - 56.w); + final clampedBottom = + newBottom.clamp(0.0, screenSize.height - 56.w); + + // 保存新位置 + controller.saveFloatingButtonPosition( + Offset(clampedRight, clampedBottom)); + }, + child: GestureDetector( + onTap: () => _showTranslationModeSelector(isDarkMode), + child: Container( + width: 56.w, + height: 56.w, + decoration: BoxDecoration( + color: isDarkMode ? Colors.grey[700] : Colors.blue, + shape: BoxShape.circle, + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.2), + blurRadius: 6, + offset: const Offset(0, 3), + ), + ], + ), + child: Icon( + Icons.translate, + color: Colors.white, + size: 24.sp, + ), + ), + ), + ), + )); + } + PreferredSizeWidget _buildAppBar(bool isDarkMode) { return AppBar( backgroundColor: isDarkMode ? AppColors.cardBackground : Colors.white, @@ -260,6 +420,7 @@ class TranslationView extends GetView { Widget _buildTranslationContent(bool isDarkMode) { return Obx(() { + // 其他模式使用原有的列表布局 final itemCount = controller.translationHistory.length + (controller.hasMoreHistory.value ? 1 : 0); @@ -299,6 +460,99 @@ class TranslationView extends GetView { }); } + /// 构建面对面翻译的对称布局 + Widget _buildFaceToFaceLayout(bool isDarkMode) { + return Obx(() { + // 获取最新的翻译记录 + final latestItem = controller.translationHistory.isNotEmpty + ? controller.translationHistory.last + : null; + + return Column( + children: [ + // 上半部分 - (旋转180度) + Expanded( + child: Transform.rotate( + angle: 3.14159, // 180度 = π弧度 + child: Container( + width: double.infinity, + padding: EdgeInsets.all(24.w), + decoration: BoxDecoration( + color: isDarkMode + ? AppColors.cardBackground.withValues(alpha: 0.5) + : Colors.blue[50]?.withValues(alpha: 0.3), + border: Border( + bottom: BorderSide( + color: isDarkMode ? Colors.grey[700]! : Colors.grey[300]!, + width: 1, + ), + ), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // 上半部的文本 + Text( + latestItem?.targetLanguageCode == + controller.targetLanguageCode.value + ? latestItem?.translatedText ?? '' //如果为null则显示空字符 + : latestItem?.sourceText ?? '', + style: TextStyle( + fontSize: 20.sp, + fontWeight: FontWeight.w500, + color: isDarkMode ? Colors.white : Colors.black87, + height: 1.4, + ), + textAlign: TextAlign.center, + ), + ], + ), + ), + ), + ), + + // 语言选择栏 + _buildLanguageSelectionBar(isDarkMode), + + // 下半部分 - (正常方向) + Expanded( + child: Container( + width: double.infinity, + padding: EdgeInsets.all(24.w), + decoration: BoxDecoration( + color: isDarkMode + ? AppColors.cardBackground.withValues(alpha: 0.5) + : Colors.green[50]?.withValues(alpha: 0.3), + ), + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // 下半部的文本 + Text( + latestItem?.sourceLanguageCode == + controller.sourceLanguageCode.value + ? latestItem?.sourceText ?? + (controller.faceToFaceRecognitionText.value.isEmpty + ? '' + : controller.faceToFaceRecognitionText.value) + : latestItem?.translatedText ?? '', + style: TextStyle( + fontSize: 20.sp, + fontWeight: FontWeight.w500, + color: isDarkMode ? Colors.white : Colors.black87, + height: 1.4, + ), + textAlign: TextAlign.center, + ), + ], + ), + ), + ), + ], + ); + }); + } + Widget _buildTranslationItemWidget( BuildContext context, TranslationItem item) { return Container( @@ -1201,4 +1455,106 @@ class TranslationView extends GetView { ), ); } + + // 构建语言选择栏 + Widget _buildLanguageSelectionBar(bool isDarkMode) { + return Container( + height: 44.h, + padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 6.h), + decoration: BoxDecoration( + color: isDarkMode ? AppColors.cardBackground : Colors.white, + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 4, + offset: const Offset(0, 2), + ), + BoxShadow( + color: Colors.black.withOpacity(0.1), + blurRadius: 4, + offset: const Offset(0, -2), + ), + ], + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + // 源语言选择 + Expanded( + child: GestureDetector( + onTap: () => _showLanguageSelectionDialog(true, isDarkMode), + child: Container( + padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h), + decoration: BoxDecoration( + color: isDarkMode + ? Colors.white.withOpacity(0.1) + : Colors.grey[100], + borderRadius: BorderRadius.circular(16.r), + ), + child: Text( + '${controller.sourceLanguage.value.split(' ')[0]}'.tr, + style: TextStyle( + fontSize: 15.sp, + fontWeight: FontWeight.w600, + color: isDarkMode ? Colors.white : Colors.black, + ), + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + ), + ), + ), + ), + + // 语言互换按钮 + GestureDetector( + onTap: controller.swapLanguages, + child: Container( + margin: EdgeInsets.symmetric(horizontal: 8.w), + padding: EdgeInsets.all(4.r), + decoration: BoxDecoration( + color: isDarkMode + ? AppColors.primary.withOpacity(0.2) + : Colors.blue[50], + shape: BoxShape.circle, + ), + child: Icon( + Icons.swap_horiz, + color: isDarkMode ? AppColors.primary : Colors.blue, + size: 20.sp, + ), + ), + ), + + // 目标语言选择 + Expanded( + child: GestureDetector( + onTap: () => _showLanguageSelectionDialog(false, isDarkMode), + child: Container( + padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h), + decoration: BoxDecoration( + color: isDarkMode + ? Colors.white.withOpacity(0.1) + : Colors.grey[100], + borderRadius: BorderRadius.circular(16.r), + ), + child: Transform.rotate( + angle: 3.14159, // 旋转180度 + child: Text( + '${controller.targetLanguage.value.split(' ')[0]}'.tr, + style: TextStyle( + fontSize: 15.sp, + fontWeight: FontWeight.w600, + color: isDarkMode ? Colors.white : Colors.black, + ), + textAlign: TextAlign.center, + overflow: TextOverflow.ellipsis, + ), + ), + ), + ), + ), + ], + ), + ); + } }