Browse Source

refactor(translation): 提取翻译项组件并优化交互逻辑

将翻译项组件从视图文件中提取为独立组件,优化点击交互逻辑
统一操作按钮显隐控制,修改导入路径为绝对路径
调整部分UI细节和文案国际化
weicu
lxm 11 months ago
parent
commit
587122bb95
  1. 4
      lib/core/bindings/initial_binding.dart
  2. 4
      lib/core/widgets/common_bottom_nav.dart
  3. 2
      lib/modules/translation/views/dialogs/translation_long_press_menu.dart
  4. 160
      lib/modules/translation/views/translation_view.dart
  5. 191
      lib/modules/translation/views/widgets/translation_item_widget.dart

4
lib/core/bindings/initial_binding.dart

@ -1,5 +1,5 @@
import 'package:voitrans/data/services/pay_service.dart'; import '/data/services/pay_service.dart';
import 'package:voitrans/data/services/qq_music.dart'; import '/data/services/qq_music.dart';
import '../../../data/services/user_portrait.dart'; import '../../../data/services/user_portrait.dart';

4
lib/core/widgets/common_bottom_nav.dart

@ -17,8 +17,8 @@ class CommonBottomNav extends StatelessWidget {
return BottomNavigationBar( return BottomNavigationBar(
currentIndex: currentIndex, currentIndex: currentIndex,
onTap: (index) { onTap: (index) {
// Navigate to the appropriate page based on index // 点击当前项不执行跳转
if(index ==currentIndex) return;//如果是当前页面,不跳转 if (index == currentIndex) return; //如果是当前页面,不跳转
switch (index) { switch (index) {
case 0: case 0:
Get.offAllNamed(Routes.home); Get.offAllNamed(Routes.home);

2
lib/modules/translation/views/dialogs/translation_long_press_menu.dart

@ -174,7 +174,7 @@ class TranslationLongPressMenu {
'copySuccess'.tr, // 复制成功 'copySuccess'.tr, // 复制成功
backgroundColor: Colors.blue.withOpacity(0.8), backgroundColor: Colors.blue.withOpacity(0.8),
colorText: Colors.white, colorText: Colors.white,
snackPosition: SnackPosition.BOTTOM, snackPosition: SnackPosition.TOP,
duration: const Duration(milliseconds: 1200), duration: const Duration(milliseconds: 1200),
icon: Icon( icon: Icon(
Icons.check_circle_outline, Icons.check_circle_outline,

160
lib/modules/translation/views/translation_view.dart

@ -17,6 +17,7 @@ import '../../common/widgets/language_selector_bar.dart';
// 导入拆分后的组件 // 导入拆分后的组件
import 'widgets/face_to_face_layout.dart'; // 面对面翻译布局 import 'widgets/face_to_face_layout.dart'; // 面对面翻译布局
import 'widgets/draggable_floating_button.dart'; // 可拖拽的悬浮按钮 import 'widgets/draggable_floating_button.dart'; // 可拖拽的悬浮按钮
import 'widgets/translation_item_widget.dart'; // 翻译项组件
import 'dialogs/language_selection_dialog.dart'; // 语言选择对话框 import 'dialogs/language_selection_dialog.dart'; // 语言选择对话框
import 'dialogs/translation_mode_selector.dart'; // 翻译模式选择器 import 'dialogs/translation_mode_selector.dart'; // 翻译模式选择器
import 'dialogs/translation_long_press_menu.dart'; // 长按菜单 import 'dialogs/translation_long_press_menu.dart'; // 长按菜单
@ -27,6 +28,17 @@ import 'dialogs/translation_long_press_menu.dart'; // 长按菜单
class TranslationView extends GetView<TranslationController> { class TranslationView extends GetView<TranslationController> {
const TranslationView({Key? key}) : super(key: key); const TranslationView({Key? key}) : super(key: key);
// 本地交互状态:当前显示操作按钮的消息时间戳(不改动控制器)
static final Rx<DateTime?> _activeActionTimestamp = Rx<DateTime?>(null);
// 切换当前操作按钮展示的项(再次点击同一项则隐藏)
void _toggleActiveActionItem(TranslationItem item) {
final DateTime ts = item.timestamp;
final DateTime? current = _activeActionTimestamp.value;
_activeActionTimestamp.value =
(current != null && current.isAtSameMomentAs(ts)) ? null : ts;
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final isDarkMode = Theme.of(context).brightness == Brightness.dark; final isDarkMode = Theme.of(context).brightness == Brightness.dark;
@ -137,7 +149,7 @@ class TranslationView extends GetView<TranslationController> {
size: 20.sp, size: 20.sp,
), ),
onPressed: () { onPressed: () {
// Stop recognition and TTS before navigating back // 点击返回时,停止所有语音识别和TTS
controller.stopAll(); controller.stopAll();
Get.back(); Get.back();
}, },
@ -271,10 +283,8 @@ class TranslationView extends GetView<TranslationController> {
children: [ children: [
// 统一通用语言选择栏组件 // 统一通用语言选择栏组件
LanguageSelectorBar( LanguageSelectorBar(
sourceText: () => sourceText: () => controller.sourceLanguage.value.split(' ')[0],
controller.sourceLanguage.value.split(' ')[0], targetText: () => controller.targetLanguage.value.split(' ')[0],
targetText: () =>
controller.targetLanguage.value.split(' ')[0],
onTapSource: () => onTapSource: () =>
LanguageSelectionDialog.show(true, isDarkMode), LanguageSelectionDialog.show(true, isDarkMode),
onTapTarget: () => onTapTarget: () =>
@ -286,9 +296,7 @@ class TranslationView extends GetView<TranslationController> {
? Icon( ? Icon(
Icons.phone_in_talk, Icons.phone_in_talk,
size: 20.sp, size: 20.sp,
color: isDarkMode color: isDarkMode ? Colors.white70 : Colors.grey[600],
? Colors.white70
: Colors.grey[600],
) )
: null, : null,
), ),
@ -396,128 +404,18 @@ class TranslationView extends GetView<TranslationController> {
final historyItem = controller.translationHistory[historyIndex]; final historyItem = controller.translationHistory[historyIndex];
// 构建项目widget // 构建项目widget
return _buildTranslationItemWidget(context, historyItem); return TranslationItemWidget(
item: historyItem,
activeActionTimestamp: _activeActionTimestamp,
onToggleActionItem: _toggleActiveActionItem,
onLongPressStart: _onLongPressTranslationItem,
onDeleteItemConfirm: _showDeleteItemConfirmDialog,
);
}, },
); );
}); });
} }
Widget _buildTranslationItemWidget(
BuildContext context, TranslationItem item) {
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
// 定义自适应颜色
final selfColor = isDarkMode
? Colors.blue[800]!.withOpacity(0.15) // 深色模式:深蓝+透明度
: Colors.blue[50]!; // 浅色模式:浅蓝
final otherColor = isDarkMode
? Colors.green[800]!.withOpacity(0.15) // 深色模式:深绿+透明度
: Colors.green[50]!; // 浅色模式:浅绿
return GestureDetector(
onLongPressStart: (details) => _onLongPressTranslationItem(
context,
isDarkMode,
details,
item,
),
child: Container(
margin: const EdgeInsets.only(bottom: 8),
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: item.isIntermediate
? Colors.grey.withOpacity(isDarkMode ? 0.15 : 0.1) // 中间结果
: (item.sourceLanguageCode == controller.sourceLanguageCode.value
? selfColor // 自己说的话
: otherColor), // 对方说的话
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: item.isIntermediate
? Colors.grey.withOpacity(isDarkMode ? 0.3 : 0.2)
: Colors.transparent,
width: 1,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 源文本和删除按钮行
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 源文本
Expanded(
child: Text(
item.sourceText,
style: TextStyle(
fontSize: 16,
color: Theme.of(context).textTheme.bodyLarge?.color,
),
),
),
// 删除按钮 - 只对完成态记录显示,与源文本平齐
if (!item.isIntermediate)
GestureDetector(
onTap: () => _showDeleteItemConfirmDialog(item),
child: Container(
padding: EdgeInsets.all(4.w),
child: Icon(
Icons.delete_outline,
size: 18.sp,
color: Colors.grey[500],
),
),
),
],
),
if (item.translatedText.isNotEmpty) ...[
const SizedBox(height: 8),
// 翻译文本
Text(
item.translatedText,
style: TextStyle(
fontSize: 16,
color: Theme.of(context).textTheme.bodyMedium?.color,
fontWeight: FontWeight.w500,
),
),
],
// 语言信息和时间戳
const SizedBox(height: 4),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(
child: Text(
'${item.sourceLanguageName.tr} → ${item.targetLanguageName.tr}',
style: TextStyle(
fontSize: 12,
color: Theme.of(context).textTheme.bodySmall?.color,
),
),
),
// 播放按钮
if (!item.isIntermediate)
GestureDetector(
onTap: () => controller.playTranslationItem(item),
child: Container(
padding: EdgeInsets.all(4.w),
child: Icon(
Icons.volume_up,
size: 20.sp,
color: Theme.of(context).colorScheme.primary,
),
),
),
],
),
],
),
),
);
}
Widget _buildControlBar(bool isDarkMode) { Widget _buildControlBar(bool isDarkMode) {
// 使用GlobalKey来获取控制栏的高度 // 使用GlobalKey来获取控制栏的高度
final controlBarKey = GlobalKey(); final controlBarKey = GlobalKey();
@ -781,10 +679,10 @@ class TranslationView extends GetView<TranslationController> {
mainAxisSize: MainAxisSize.min, mainAxisSize: MainAxisSize.min,
children: [ children: [
SizedBox( SizedBox(
width: 24, width: 24.w,
height: 24, height: 24.h,
child: CircularProgressIndicator( child: CircularProgressIndicator(
strokeWidth: 2.5, strokeWidth: 2.5.w,
valueColor: const AlwaysStoppedAnimation<Color>( valueColor: const AlwaysStoppedAnimation<Color>(
AppColors.primary, AppColors.primary,
), ),
@ -792,7 +690,7 @@ class TranslationView extends GetView<TranslationController> {
), ),
const SizedBox(height: 12), const SizedBox(height: 12),
Text( Text(
'正在加载历史记录...', 'loading'.tr, //加载中
style: TextStyle( style: TextStyle(
fontSize: 14.sp, fontSize: 14.sp,
color: Colors.grey[600], color: Colors.grey[600],
@ -828,7 +726,7 @@ class TranslationView extends GetView<TranslationController> {
), ),
SizedBox(width: 8.w), SizedBox(width: 8.w),
Text( Text(
'加载失败,点击重试', 'loadFailedClickRetry'.tr,
style: TextStyle( style: TextStyle(
fontSize: 14.sp, fontSize: 14.sp,
color: Colors.red[600], color: Colors.red[600],
@ -862,7 +760,7 @@ class TranslationView extends GetView<TranslationController> {
), ),
SizedBox(width: 4.w), SizedBox(width: 4.w),
Text( Text(
'向上滑动查看更多历史', 'swipeUpForMore'.tr,
style: TextStyle( style: TextStyle(
fontSize: 12.sp, fontSize: 12.sp,
color: Colors.grey[600], color: Colors.grey[600],

191
lib/modules/translation/views/widgets/translation_item_widget.dart

@ -0,0 +1,191 @@
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 '../dialogs/translation_long_press_menu.dart';
class TranslationItemWidget extends GetView<TranslationController> {
// 传入的翻译条目
final TranslationItem item;
// 传入用于控制操作按钮显隐的时间戳状态
final Rx<DateTime?> activeActionTimestamp;
// 点击气泡时切换操作按钮显隐
final void Function(TranslationItem item) onToggleActionItem;
// 长按时弹出菜单(需要上下文、暗色模式、手势详情、条目)
final void Function(
BuildContext context,
bool isDarkMode,
LongPressStartDetails details,
TranslationItem item,
) onLongPressStart;
// 删除按钮点击时的确认对话框
final void Function(TranslationItem item) onDeleteItemConfirm;
const TranslationItemWidget({
Key? key,
required this.item,
required this.activeActionTimestamp,
required this.onToggleActionItem,
required this.onLongPressStart,
required this.onDeleteItemConfirm,
}) : super(key: key);
@override
Widget build(BuildContext context) {
final isDarkMode = Theme.of(context).brightness == Brightness.dark;
// 定义自适应颜色(与原实现保持一致)
final selfColor =
isDarkMode ? Colors.blue[800]!.withOpacity(0.15) : Colors.blue[50]!;
final otherColor =
isDarkMode ? Colors.green[800]!.withOpacity(0.15) : Colors.green[50]!;
// 判断是否是己方的翻译项(依据控制器当前源语言)
final bool isSelf =
item.sourceLanguageCode == controller.sourceLanguageCode.value;
return GestureDetector(
onTap: () => onToggleActionItem(item), // 触碰气泡时切换按钮显隐
onLongPressStart: (details) =>
onLongPressStart(context, isDarkMode, details, item),
child: Align(
alignment: item.isIntermediate
? Alignment.center
: (isSelf ? Alignment.centerLeft : Alignment.centerRight),
child: Container(
margin: EdgeInsets.only(bottom: 8.h),
padding: EdgeInsets.all(12.w),
constraints: BoxConstraints(maxWidth: 0.8.sw, minWidth: 0.35.sw),
decoration: BoxDecoration(
color: item.isIntermediate
? Colors.grey.withOpacity(isDarkMode ? 0.15 : 0.1) // 中间结果
: (isSelf ? selfColor : otherColor), // 自己 / 对方
borderRadius: BorderRadius.circular(12.r),
border: Border.all(
color: item.isIntermediate
? Colors.grey.withOpacity(isDarkMode ? 0.3 : 0.2)
: Colors.transparent,
width: 1,
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// 源文本
Text(
item.sourceText,
style: TextStyle(
fontSize: 16.sp,
color: Theme.of(context).textTheme.bodyLarge?.color,
),
),
SizedBox(height: 8.h),
// 分隔线 + 译文
Container(
padding: EdgeInsets.only(top: 8.h),
decoration: BoxDecoration(
border: Border(
top: BorderSide(
color: item.isIntermediate
? (isDarkMode
? Colors.grey[400]!.withOpacity(0.15)
: Colors.grey[400]!.withOpacity(0.25))
: (isSelf
? (isDarkMode
? Colors.blue[300]!.withOpacity(0.15)
: Colors.blue[300]!.withOpacity(0.25))
: (isDarkMode
? Colors.green[300]!.withOpacity(0.15)
: Colors.green[300]!.withOpacity(0.25))),
width: 1,
),
),
),
child: Text(
item.translatedText,
style: TextStyle(
fontSize: 16.sp,
color: Theme.of(context).textTheme.bodyMedium?.color,
fontWeight: FontWeight.w500,
),
),
),
// 操作按钮:复制 / 播放 / 删除(仅非中间态且显隐状态为激活时显示)
Obx(() {
final bool isActive = activeActionTimestamp.value
?.isAtSameMomentAs(item.timestamp) ??
false;
if (!item.isIntermediate && isActive) {
return Column(
children: [
SizedBox(height: 8.h),
Row(
mainAxisSize: MainAxisSize.min,
children: [
// 复制按钮(复用长按菜单的复制逻辑)
GestureDetector(
onTap: () =>
TranslationLongPressMenu.copyTranslationItem(
item),
child: Container(
padding: EdgeInsets.all(4.w),
child: Tooltip(
message: 'copy'.tr, // 翻译键代表的中文文本内容
child: Icon(
Icons.copy,
size: 20.sp,
color: Theme.of(context).colorScheme.primary,
),
),
),
),
SizedBox(width: 8.w),
// 播放按钮(调用控制器原有方法)
GestureDetector(
onTap: () => controller.playTranslationItem(item),
child: Container(
padding: EdgeInsets.all(4.w),
child: Icon(
Icons.volume_up,
size: 20.sp,
color: Theme.of(context).colorScheme.primary,
),
),
),
SizedBox(width: 8.w),
// 删除按钮(弹出确认)
GestureDetector(
onTap: () => onDeleteItemConfirm(item),
child: Container(
padding: EdgeInsets.all(4.w),
child: Tooltip(
message: 'delete'.tr, // 翻译键代表的中文文本内容
child: Icon(
Icons.delete_outline,
size: 20.sp,
color: Colors.grey[500],
),
),
),
),
],
)
],
);
} else {
return const SizedBox.shrink();
}
}),
],
),
),
),
);
}
}
Loading…
Cancel
Save