From 94fc2e766d655d15aa1abba5da4b7242d54c71fa Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Thu, 18 Sep 2025 11:20:12 +0800 Subject: [PATCH] =?UTF-8?q?refactor(translation):=20=E5=B0=86=E7=BF=BB?= =?UTF-8?q?=E8=AF=91=E6=A8=A1=E5=9D=97=E7=BB=84=E4=BB=B6=E6=8B=86=E5=88=86?= =?UTF-8?q?=E4=B8=BA=E7=8B=AC=E7=AB=8B=E6=96=87=E4=BB=B6=E5=B9=B6=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E7=BB=93=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 重构翻译视图页面,将悬浮按钮、面对面布局、语言选择对话框和翻译模式选择器等组件拆分为独立文件 优化代码结构,提高可维护性和复用性 移除重复代码,保持功能一致性 --- .../dialogs/language_selection_dialog.dart | 161 ++++++ .../dialogs/translation_mode_selector.dart | 172 ++++++ .../translation/views/translation_view.dart | 516 +----------------- .../widgets/draggable_floating_button.dart | 103 ++++ .../views/widgets/face_to_face_layout.dart | 219 ++++++++ 5 files changed, 680 insertions(+), 491 deletions(-) create mode 100644 lib/modules/translation/views/dialogs/language_selection_dialog.dart create mode 100644 lib/modules/translation/views/dialogs/translation_mode_selector.dart create mode 100644 lib/modules/translation/views/widgets/draggable_floating_button.dart create mode 100644 lib/modules/translation/views/widgets/face_to_face_layout.dart diff --git a/lib/modules/translation/views/dialogs/language_selection_dialog.dart b/lib/modules/translation/views/dialogs/language_selection_dialog.dart new file mode 100644 index 000000000..cbd889f5c --- /dev/null +++ b/lib/modules/translation/views/dialogs/language_selection_dialog.dart @@ -0,0 +1,161 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import '../../controllers/translation_controller.dart'; + +/// 语言选择对话框 +class LanguageSelectionDialog { + /// 显示语言选择对话框 + static void show(bool isSource, bool isDarkMode) { + final controller = Get.find(); + + Get.dialog(Dialog( + backgroundColor: isDarkMode ? Colors.grey[900] : Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(16.r), // 统一圆角尺寸为16 + ), + alignment: isSource ? Alignment.centerLeft : Alignment.centerRight, + insetPadding: EdgeInsets.symmetric(horizontal: 10.w), + child: SizedBox( + width: 0.4.sw, + child: Padding( + // 使用Padding替代Container的padding + padding: EdgeInsets.all(12.w), // 统一内边距 + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 标题区域 + Padding( + padding: EdgeInsets.symmetric(horizontal: 8.w), + child: Text( + isSource + ? 'selectSourceLanguage'.tr + : 'selectTargetLanguage'.tr, // 保留源语言/目标语言区分 + style: TextStyle( + fontSize: 18.sp, // 调整字号 + fontWeight: FontWeight.w600, // 使用数字字重 + color: isDarkMode ? Colors.white : Colors.black87, // 优化文本颜色 + ), + ), + ), + SizedBox(height: 16.h), + + // 语言列表区域 + Container( + constraints: BoxConstraints(maxHeight: 0.5.sh), + child: SingleChildScrollView( + child: Column( + mainAxisSize: MainAxisSize.min, + children: + controller.supportedLanguages.keys.map((language) { + return Obx(() { + final currentLanguage = isSource + ? controller.sourceLanguage.value + : controller.targetLanguage.value; + final isSelected = currentLanguage == language; + + return GestureDetector( + // 改用手势检测器实现点击 + onTap: () { + if (isSource) { + controller.setSourceLanguage(language); + } else { + controller.setTargetLanguage(language); + } + Get.back(); + }, + child: Container( + margin: EdgeInsets.only(bottom: 10.h), // 添加项间距 + padding: EdgeInsets.all(10.w), // 统一内边距 + decoration: BoxDecoration( + color: isSelected // 选中状态背景色 + ? (isDarkMode + ? const Color(0xFF6200EE).withOpacity(0.2) + : Colors.blue.withOpacity(0.1)) + : (isDarkMode // 未选中状态背景色 + ? Colors.white.withOpacity(0.05) + : Colors.grey[200]!), + borderRadius: BorderRadius.circular(12.r), // 项圆角 + border: Border.all( + // 选中状态边框 + color: isSelected + ? (isDarkMode + ? const Color(0xFF6200EE) + : Colors.blue) + : (isDarkMode // 未选中状态背景色 + ? Colors.white.withOpacity(0.05) + : Colors.grey[200]!), + width: 1, + ), + ), + child: Row( + children: [ + // 语言名称 + Expanded( + child: Text( + language.tr, + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w500, + color: isDarkMode + ? Colors.white + : Colors.black87, + overflow: TextOverflow.ellipsis, + ), + ), + ), + // 选中指示器 + Container( + width: 24.w, + height: 24.w, + decoration: BoxDecoration( + color: isSelected // 动态颜色 + ? (isDarkMode + ? const Color(0xFF6200EE) + : Colors.blue) + : (isDarkMode + ? Colors.white.withOpacity(0.1) + : Colors.grey[300]), + shape: BoxShape.circle, // 圆形样式 + ), + child: isSelected + ? Icon( + // 选中时显示对勾 + Icons.check, + size: 16.w, + color: Colors.white, + ) + : null, + ), + ], + ), + ), + ); + }); + }).toList(), + ), + ), + ), + SizedBox(height: 16.h), + + // 取消按钮 + Align( + alignment: Alignment.centerRight, + child: TextButton( + onPressed: () => Get.back(), + child: Text( + 'cancel'.tr, + style: TextStyle( + color: isDarkMode ? const Color(0xFF6200EE) : Colors.blue, + ), + ), + ), + ), + ], + ), + ), + ), + )); + } +} diff --git a/lib/modules/translation/views/dialogs/translation_mode_selector.dart b/lib/modules/translation/views/dialogs/translation_mode_selector.dart new file mode 100644 index 000000000..75386980c --- /dev/null +++ b/lib/modules/translation/views/dialogs/translation_mode_selector.dart @@ -0,0 +1,172 @@ +import 'dart:ui'; +import 'dart:io'; +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import '../../controllers/translation_controller.dart'; + +/// 翻译模式选择对话框 +class TranslationModeSelector { + /// 显示翻译模式选择器 + static void show(bool isDarkMode) { + final controller = Get.find(); + + Get.bottomSheet( + BackdropFilter( + filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0), + child: _buildTranslationModeSelector(controller), + ), + isScrollControlled: true, + backgroundColor: Colors.transparent, + ); + } + + // 构建翻译模式选择器 - 参考会议模块样式 + static Widget _buildTranslationModeSelector(TranslationController controller) { + return Builder( + builder: (BuildContext context) { + return SafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + _buildModeItem( + context, + 'simultaneousTranslation'.tr, + Icons.translate, + Colors.blue, + controller, + onTap: () async { + Get.back(); + await controller.stopAll(); + await controller.changeTranslationMode('simultaneous'); + await controller.startRecognition(); + }, + ), + _buildModeItem( + context, + 'faceToFaceTranslation'.tr, + Icons.people, + Colors.green, + controller, + onTap: () async { + Get.back(); + await controller.stopAll(); + await controller.changeTranslationMode('faceToFace'); + await controller.startRecognition(); + }, + ), + _buildModeItem( + context, + 'audioVideoTranslation'.tr, + Icons.videocam, + Colors.orange, + controller, + onTap: controller.bleManager.isConnected + ? () async { + Get.back(); + await controller.stopAll(); + await controller.changeTranslationMode('audioVideo'); + await controller.startRecognition(); + } + : null, + ), + _buildModeItem( + context, + 'callTranslation'.tr, + Icons.phone, + Colors.purple, + controller, + onTap: controller.bleManager.isConnected + ? () async { + Get.back(); + await controller.stopAll(); + await controller.changeTranslationMode('call'); + await controller.startRecognition(); + } + : null, + ), + SizedBox(height: 24.w), + GestureDetector( + onTap: () => Get.back(), + child: Container( + width: 42, + height: 42, + decoration: BoxDecoration( + color: Colors.black.withValues(alpha: 0.8), + borderRadius: BorderRadius.circular(50), + ), + child: const Icon(Icons.close, color: Colors.white), + ), + ), + if (Platform.isAndroid) SizedBox(height: 20.h), + ], + ), + ); + }, + ); + } + + // 构建单个模式选项 - 完全参考会议模块的 _buildItem 方法 + static Widget _buildModeItem( + BuildContext context, + String title, + IconData icon, + Color color, + TranslationController controller, { + Function()? onTap, + }) { + final isDarkMode = Theme.of(context).brightness == Brightness.dark; + + return GestureDetector( + onTap: onTap, + child: Container( + width: 220.w, + height: 60.w, + padding: EdgeInsets.all(5.w), + margin: EdgeInsets.only(top: 15.w), + decoration: BoxDecoration( + color: isDarkMode + ? Colors.grey[800]!.withValues(alpha: onTap != null ? 1 : 0.5) + : Colors.white.withValues(alpha: onTap != null ? 1 : 0.5), + borderRadius: BorderRadius.circular(60.w), + ), + child: Row( + children: [ + Container( + width: 46.w, + height: 46.w, + margin: EdgeInsets.only(right: 5.w), + decoration: BoxDecoration( + color: color.withValues(alpha: onTap != null ? 0.3 : 0.2), + borderRadius: BorderRadius.circular(60.w), + ), + child: Icon( + icon, + color: color.withValues(alpha: onTap != null ? 1 : 0.5), + ), + ), + Expanded( + child: Text( + title, + style: TextStyle( + fontSize: 15.sp, + color: isDarkMode + ? Colors.white.withValues(alpha: onTap != null ? 1 : 0.5) + : Colors.black87 + .withValues(alpha: onTap != null ? 1 : 0.5), + fontWeight: FontWeight.bold, + ), + ), + ), + Icon( + Icons.arrow_forward_ios, + size: 16.w, + color: Colors.grey.withValues(alpha: onTap != null ? 1 : 0.5), + ), + SizedBox(width: 5.w), + ], + ), + ), + ); + } +} diff --git a/lib/modules/translation/views/translation_view.dart b/lib/modules/translation/views/translation_view.dart index ae20249ce..69abbd234 100644 --- a/lib/modules/translation/views/translation_view.dart +++ b/lib/modules/translation/views/translation_view.dart @@ -10,6 +10,13 @@ import '../views/translation_history_view.dart'; import '../bindings/translation_history_binding.dart'; import '../../../core/theme/app_colors.dart'; import '../../../routes/app_routes.dart'; + +// 导入拆分后的组件 +import 'widgets/face_to_face_layout.dart'; // 面对面翻译布局 +import 'widgets/draggable_floating_button.dart'; // 可拖拽的悬浮按钮 +import 'dialogs/language_selection_dialog.dart'; // 语言选择对话框 +import 'dialogs/translation_mode_selector.dart'; // 翻译模式选择器 + // import 'package:intl/intl.dart'; //主页面 @@ -35,7 +42,10 @@ class TranslationView extends GetView { children: [ _buildBody(isDarkMode), // 可拖拽的悬浮按钮 - _buildDraggableFloatingButton(isDarkMode), + DraggableFloatingButton( + isDarkMode: isDarkMode, + controller: controller, + ), ], ), ); @@ -49,7 +59,10 @@ class TranslationView extends GetView { body: Stack( children: [ // 主要内容区域 - _buildFaceToFaceLayout(isDarkMode), + FaceToFaceLayout( + isDarkMode: isDarkMode, + controller: controller, + ), // 顶部按钮(倒过来) Positioned( @@ -71,7 +84,10 @@ class TranslationView extends GetView { ), // 可拖拽的悬浮按钮 - 在所有模式下都显示 - _buildDraggableFloatingButton(isDarkMode), + DraggableFloatingButton( + isDarkMode: isDarkMode, + controller: controller, + ), ], ), ); @@ -106,87 +122,6 @@ class TranslationView extends GetView { ); } - // 构建可拖拽的悬浮按钮 - 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, @@ -206,7 +141,7 @@ class TranslationView extends GetView { title: Obx(() => GestureDetector( onTap: () { // 点击标题时弹出翻译模式选择器 - _showTranslationModeSelector(isDarkMode); + TranslationModeSelector.show(isDarkMode); }, child: Text( controller.currentModeTitle.value.tr, @@ -309,7 +244,7 @@ class TranslationView extends GetView { // 源语言选择 Expanded( child: GestureDetector( - onTap: () => _showLanguageSelectionDialog(true, isDarkMode), + onTap: () => LanguageSelectionDialog.show(true, isDarkMode), child: Container( padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h), @@ -357,7 +292,7 @@ class TranslationView extends GetView { // 目标语言选择 Expanded( child: GestureDetector( - onTap: () => _showLanguageSelectionDialog(false, isDarkMode), + onTap: () => LanguageSelectionDialog.show(false, isDarkMode), child: Container( padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h), @@ -460,99 +395,6 @@ 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( @@ -728,7 +570,7 @@ class TranslationView extends GetView { controller.stopAll(); } else { // 弹出翻译模式选择器 - _showTranslationModeSelector(isDarkMode); + TranslationModeSelector.show(isDarkMode); } }, child: Container( @@ -908,158 +750,6 @@ class TranslationView extends GetView { ); } - // 显示语言选择对话框 - void _showLanguageSelectionDialog(bool isSource, bool isDarkMode) { - Get.dialog(Dialog( - backgroundColor: isDarkMode ? Colors.grey[900] : Colors.white, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16.r), // 统一圆角尺寸为16 - ), - alignment: isSource ? Alignment.centerLeft : Alignment.centerRight, - insetPadding: EdgeInsets.symmetric(horizontal: 10.w), - child: SizedBox( - width: 0.4.sw, - child: Padding( - // 使用Padding替代Container的padding - padding: EdgeInsets.all(12.w), // 统一内边距 - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // 标题区域 - Padding( - padding: EdgeInsets.symmetric(horizontal: 8.w), - child: Text( - isSource - ? 'selectSourceLanguage'.tr - : 'selectTargetLanguage'.tr, // 保留源语言/目标语言区分 - style: TextStyle( - fontSize: 18.sp, // 调整字号 - fontWeight: FontWeight.w600, // 使用数字字重 - color: isDarkMode ? Colors.white : Colors.black87, // 优化文本颜色 - ), - ), - ), - SizedBox(height: 16.h), - - // 语言列表区域 - Container( - constraints: BoxConstraints(maxHeight: 0.5.sh), - child: SingleChildScrollView( - child: Column( - mainAxisSize: MainAxisSize.min, - children: - controller.supportedLanguages.keys.map((language) { - return Obx(() { - final currentLanguage = isSource - ? controller.sourceLanguage.value - : controller.targetLanguage.value; - final isSelected = currentLanguage == language; - - return GestureDetector( - // 改用手势检测器实现点击 - onTap: () { - if (isSource) { - controller.setSourceLanguage(language); - } else { - controller.setTargetLanguage(language); - } - Get.back(); - }, - child: Container( - margin: EdgeInsets.only(bottom: 10.h), // 添加项间距 - padding: EdgeInsets.all(10.w), // 统一内边距 - decoration: BoxDecoration( - color: isSelected // 选中状态背景色 - ? (isDarkMode - ? const Color(0xFF6200EE).withOpacity(0.2) - : Colors.blue.withOpacity(0.1)) - : (isDarkMode // 未选中状态背景色 - ? Colors.white.withOpacity(0.05) - : Colors.grey[200]!), - borderRadius: BorderRadius.circular(12.r), // 项圆角 - border: Border.all( - // 选中状态边框 - color: isSelected - ? (isDarkMode - ? const Color(0xFF6200EE) - : Colors.blue) - : (isDarkMode // 未选中状态背景色 - ? Colors.white.withOpacity(0.05) - : Colors.grey[200]!), - width: 1, - ), - ), - child: Row( - children: [ - // 语言名称 - Expanded( - child: Text( - language.tr, - style: TextStyle( - fontSize: 16.sp, - fontWeight: FontWeight.w500, - color: isDarkMode - ? Colors.white - : Colors.black87, - overflow: TextOverflow.ellipsis, - ), - ), - ), - // 选中指示器 - Container( - width: 24.w, - height: 24.w, - decoration: BoxDecoration( - color: isSelected // 动态颜色 - ? (isDarkMode - ? const Color(0xFF6200EE) - : Colors.blue) - : (isDarkMode - ? Colors.white.withOpacity(0.1) - : Colors.grey[300]), - shape: BoxShape.circle, // 圆形样式 - ), - child: isSelected - ? Icon( - // 选中时显示对勾 - Icons.check, - size: 16.w, - color: Colors.white, - ) - : null, - ), - ], - ), - ), - ); - }); - }).toList(), - ), - ), - ), - SizedBox(height: 16.h), - - // 取消按钮 - Align( - alignment: Alignment.centerRight, - child: TextButton( - onPressed: () => Get.back(), - child: Text( - 'cancel'.tr, - style: TextStyle( - color: isDarkMode ? const Color(0xFF6200EE) : Colors.blue, - ), - ), - ), - ), - ], - ), - ), - ), - )); - } - // 构建加载更多指示器 Widget _buildLoadMoreIndicator() { return Obx(() { @@ -1300,162 +990,6 @@ class TranslationView extends GetView { } } - // 显示翻译模式选择器 - void _showTranslationModeSelector(bool isDarkMode) { - Get.bottomSheet( - BackdropFilter( - filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0), - child: _buildTranslationModeSelector(), - ), - isScrollControlled: true, - backgroundColor: Colors.transparent, - ); - } - - // 构建翻译模式选择器 - 参考会议模块样式 - Widget _buildTranslationModeSelector() { - return Builder( - builder: (BuildContext context) { - return SafeArea( - child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - _buildModeItem( - context, - 'simultaneousTranslation'.tr, - Icons.translate, - Colors.blue, - onTap: () async { - Get.back(); - await controller.stopAll(); - await controller.changeTranslationMode('simultaneous'); - await controller.startRecognition(); - }, - ), - _buildModeItem( - context, - 'faceToFaceTranslation'.tr, - Icons.people, - Colors.green, - onTap: () async { - Get.back(); - await controller.stopAll(); - await controller.changeTranslationMode('faceToFace'); - await controller.startRecognition(); - }, - ), - _buildModeItem( - context, - 'audioVideoTranslation'.tr, - Icons.videocam, - Colors.orange, - onTap: controller.bleManager.isConnected - ? () async { - Get.back(); - await controller.stopAll(); - await controller.changeTranslationMode('audioVideo'); - await controller.startRecognition(); - } - : null, - ), - _buildModeItem( - context, - 'callTranslation'.tr, - Icons.phone, - Colors.purple, - onTap: controller.bleManager.isConnected - ? () async { - Get.back(); - await controller.stopAll(); - await controller.changeTranslationMode('call'); - await controller.startRecognition(); - } - : null, - ), - SizedBox(height: 24.w), - GestureDetector( - onTap: () => Get.back(), - child: Container( - width: 42, - height: 42, - decoration: BoxDecoration( - color: Colors.black.withValues(alpha: 0.8), - borderRadius: BorderRadius.circular(50), - ), - child: const Icon(Icons.close, color: Colors.white), - ), - ), - if (Platform.isAndroid) SizedBox(height: 20.h), - ], - ), - ); - }, - ); - } - - // 构建单个模式选项 - 完全参考会议模块的 _buildItem 方法 - Widget _buildModeItem( - BuildContext context, - String title, - IconData icon, - Color color, { - Function()? onTap, - }) { - final isDarkMode = Theme.of(context).brightness == Brightness.dark; - - return GestureDetector( - onTap: onTap, - child: Container( - width: 220.w, - height: 60.w, - padding: EdgeInsets.all(5.w), - margin: EdgeInsets.only(top: 15.w), - decoration: BoxDecoration( - color: isDarkMode - ? Colors.grey[800]!.withValues(alpha: onTap != null ? 1 : 0.5) - : Colors.white.withValues(alpha: onTap != null ? 1 : 0.5), - borderRadius: BorderRadius.circular(60.w), - ), - child: Row( - children: [ - Container( - width: 46.w, - height: 46.w, - margin: EdgeInsets.only(right: 5.w), - decoration: BoxDecoration( - color: color.withValues(alpha: onTap != null ? 0.3 : 0.2), - borderRadius: BorderRadius.circular(60.w), - ), - child: Icon( - icon, - color: color.withValues(alpha: onTap != null ? 1 : 0.5), - ), - ), - Expanded( - child: Text( - title, - style: TextStyle( - fontSize: 15.sp, - color: isDarkMode - ? Colors.white.withValues(alpha: onTap != null ? 1 : 0.5) - : Colors.black87 - .withValues(alpha: onTap != null ? 1 : 0.5), - fontWeight: FontWeight.bold, - ), - ), - ), - Icon( - Icons.arrow_forward_ios, - size: 16.w, - color: Colors.grey.withValues(alpha: onTap != null ? 1 : 0.5), - ), - SizedBox(width: 5.w), - ], - ), - ), - ); - } - // 构建语言选择栏 Widget _buildLanguageSelectionBar(bool isDarkMode) { return Container( @@ -1482,7 +1016,7 @@ class TranslationView extends GetView { // 源语言选择 Expanded( child: GestureDetector( - onTap: () => _showLanguageSelectionDialog(true, isDarkMode), + onTap: () => LanguageSelectionDialog.show(true, isDarkMode), child: Container( padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h), decoration: BoxDecoration( @@ -1528,7 +1062,7 @@ class TranslationView extends GetView { // 目标语言选择 Expanded( child: GestureDetector( - onTap: () => _showLanguageSelectionDialog(false, isDarkMode), + onTap: () => LanguageSelectionDialog.show(false, isDarkMode), child: Container( padding: EdgeInsets.symmetric(horizontal: 8.w, vertical: 4.h), decoration: BoxDecoration( diff --git a/lib/modules/translation/views/widgets/draggable_floating_button.dart b/lib/modules/translation/views/widgets/draggable_floating_button.dart new file mode 100644 index 000000000..98b25adb3 --- /dev/null +++ b/lib/modules/translation/views/widgets/draggable_floating_button.dart @@ -0,0 +1,103 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import '../../controllers/translation_controller.dart'; +import '../dialogs/translation_mode_selector.dart'; + +/// 可拖拽悬浮按钮组件 +class DraggableFloatingButton extends StatelessWidget { + final bool isDarkMode; + final TranslationController controller; + + const DraggableFloatingButton({ + Key? key, + required this.isDarkMode, + required this.controller, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + 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, + ), + ), + ), + ), + )); + } + + /// 显示翻译模式选择器 + void _showTranslationModeSelector(bool isDarkMode) { + TranslationModeSelector.show(isDarkMode); + } +} diff --git a/lib/modules/translation/views/widgets/face_to_face_layout.dart b/lib/modules/translation/views/widgets/face_to_face_layout.dart new file mode 100644 index 000000000..562a543b9 --- /dev/null +++ b/lib/modules/translation/views/widgets/face_to_face_layout.dart @@ -0,0 +1,219 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import '../../controllers/translation_controller.dart'; +import '../../models/translation_models.dart'; +import '../../../../core/theme/app_colors.dart'; +import '../dialogs/language_selection_dialog.dart'; + +/// 面对面翻译布局组件 +class FaceToFaceLayout extends StatelessWidget { + final bool isDarkMode; + final TranslationController controller; + + const FaceToFaceLayout({ + Key? key, + required this.isDarkMode, + required this.controller, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + 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 _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, + ), + ), + ), + ), + ), + ], + ), + ); + } + + /// 显示语言选择对话框 + void _showLanguageSelectionDialog(bool isSource, bool isDarkMode) { + LanguageSelectionDialog.show(isSource, isDarkMode); + } +}