Browse Source

refactor(translation): 将翻译模块组件拆分为独立文件并优化结构

重构翻译视图页面,将悬浮按钮、面对面布局、语言选择对话框和翻译模式选择器等组件拆分为独立文件
优化代码结构,提高可维护性和复用性
移除重复代码,保持功能一致性
weicu
lxm 1 year ago
parent
commit
109ec6d8ff
  1. 161
      lib/modules/translation/views/dialogs/language_selection_dialog.dart
  2. 172
      lib/modules/translation/views/dialogs/translation_mode_selector.dart
  3. 516
      lib/modules/translation/views/translation_view.dart
  4. 103
      lib/modules/translation/views/widgets/draggable_floating_button.dart
  5. 219
      lib/modules/translation/views/widgets/face_to_face_layout.dart

161
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<TranslationController>();
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,
),
),
),
),
],
),
),
),
));
}
}

172
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<TranslationController>();
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),
],
),
),
);
}
}

516
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<TranslationController> {
children: [
_buildBody(isDarkMode),
// 可拖拽的悬浮按钮
_buildDraggableFloatingButton(isDarkMode),
DraggableFloatingButton(
isDarkMode: isDarkMode,
controller: controller,
),
],
),
);
@ -49,7 +59,10 @@ class TranslationView extends GetView<TranslationController> {
body: Stack(
children: [
// 主要内容区域
_buildFaceToFaceLayout(isDarkMode),
FaceToFaceLayout(
isDarkMode: isDarkMode,
controller: controller,
),
// 顶部按钮(倒过来)
Positioned(
@ -71,7 +84,10 @@ class TranslationView extends GetView<TranslationController> {
),
// 可拖拽的悬浮按钮 - 在所有模式下都显示
_buildDraggableFloatingButton(isDarkMode),
DraggableFloatingButton(
isDarkMode: isDarkMode,
controller: controller,
),
],
),
);
@ -106,87 +122,6 @@ class TranslationView extends GetView<TranslationController> {
);
}
// 构建可拖拽的悬浮按钮
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<TranslationController> {
title: Obx(() => GestureDetector(
onTap: () {
// 点击标题时弹出翻译模式选择器
_showTranslationModeSelector(isDarkMode);
TranslationModeSelector.show(isDarkMode);
},
child: Text(
controller.currentModeTitle.value.tr,
@ -309,7 +244,7 @@ class TranslationView extends GetView<TranslationController> {
// 源语言选择
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<TranslationController> {
// 目标语言选择
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<TranslationController> {
});
}
/// 构建面对面翻译的对称布局
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<TranslationController> {
controller.stopAll();
} else {
// 弹出翻译模式选择器
_showTranslationModeSelector(isDarkMode);
TranslationModeSelector.show(isDarkMode);
}
},
child: Container(
@ -908,158 +750,6 @@ class TranslationView extends GetView<TranslationController> {
);
}
// 显示语言选择对话框
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<TranslationController> {
}
}
// 显示翻译模式选择器
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<TranslationController> {
// 源语言选择
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<TranslationController> {
// 目标语言选择
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(

103
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);
}
}

219
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);
}
}
Loading…
Cancel
Save