From d9d135ad10f8a18b44d8d8131bcfdfaf8331cfd9 Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Sat, 20 Sep 2025 17:07:39 +0800 Subject: [PATCH] =?UTF-8?q?feat(translation):=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E7=BF=BB=E8=AF=91=E9=95=BF=E6=8C=89=E8=8F=9C=E5=8D=95=E5=92=8C?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E7=A1=AE=E8=AE=A4=E5=AF=B9=E8=AF=9D=E6=A1=86?= =?UTF-8?q?=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将翻译长按菜单和删除确认对话框逻辑抽离为独立组件 重构翻译视图中的相关代码,提高代码复用性和可维护性 --- .../views/dialogs/delete_confirm_dialog.dart | 172 +++++++++++ .../dialogs/translation_long_press_menu.dart | 187 ++++++++++++ .../translation/views/translation_view.dart | 280 ++---------------- 3 files changed, 379 insertions(+), 260 deletions(-) create mode 100644 lib/modules/translation/views/dialogs/delete_confirm_dialog.dart create mode 100644 lib/modules/translation/views/dialogs/translation_long_press_menu.dart diff --git a/lib/modules/translation/views/dialogs/delete_confirm_dialog.dart b/lib/modules/translation/views/dialogs/delete_confirm_dialog.dart new file mode 100644 index 000000000..dbbd2b2fb --- /dev/null +++ b/lib/modules/translation/views/dialogs/delete_confirm_dialog.dart @@ -0,0 +1,172 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import '../../../../core/theme/app_colors.dart'; +import '../../models/translation_models.dart'; + +/// 删除确认对话框 +/// 提供翻译记录的删除确认功能 +class DeleteConfirmDialog { + /// 显示删除单条翻译记录确认对话框 + static void showDeleteItemDialog({ + required TranslationItem item, + required Function() onConfirm, + }) { + final isDarkMode = Theme.of(Get.context!).brightness == Brightness.dark; + + Get.dialog( + AlertDialog( + backgroundColor: isDarkMode ? AppColors.cardBackground : Colors.white, + title: Text( + 'deleteTranslationRecord'.tr, // 删除翻译记录 + style: TextStyle( + fontSize: 18.sp, + fontWeight: FontWeight.bold, + color: isDarkMode ? Colors.white : Colors.black, + ), + ), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'deleteTranslationConfirm'.tr, // 确定要删除这条翻译记录吗?此操作无法撤销。 + style: TextStyle( + fontSize: 16.sp, + color: isDarkMode ? Colors.grey[300] : Colors.black87, + ), + ), + SizedBox(height: 12.h), + // 显示要删除的记录预览 + Container( + padding: EdgeInsets.all(12.w), + decoration: BoxDecoration( + color: isDarkMode + ? Colors.grey[800]?.withOpacity(0.5) + : Colors.grey[100], + borderRadius: BorderRadius.circular(8.r), + border: Border.all( + color: isDarkMode ? Colors.grey[700]! : Colors.grey[300]!, + width: 1, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + item.sourceText, + style: TextStyle( + fontSize: 14.sp, + color: isDarkMode ? Colors.white : Colors.black, + fontWeight: FontWeight.w500, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + if (item.translatedText.isNotEmpty) ...[ + SizedBox(height: 4.h), + Text( + item.translatedText, + style: TextStyle( + fontSize: 14.sp, + color: isDarkMode ? Colors.grey[300] : Colors.grey[700], + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ], + ], + ), + ), + ], + ), + actions: [ + TextButton( + onPressed: () => Get.back(), + child: Text( + 'cancel'.tr, + style: TextStyle( + fontSize: 16.sp, + color: isDarkMode ? Colors.grey[400] : Colors.grey[700], + ), + ), + ), + TextButton( + onPressed: () { + Get.back(); + onConfirm(); + }, + child: Text( + 'confirm'.tr, + style: TextStyle( + fontSize: 16.sp, + color: Colors.red, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12.r), + ), + ), + ); + } + + /// 显示清除所有历史记录确认对话框 + static void showClearHistoryDialog({ + required Function() onConfirm, + }) { + final isDarkMode = Get.isDarkMode; + + Get.dialog( + AlertDialog( + backgroundColor: isDarkMode ? AppColors.cardBackground : Colors.white, + title: Text( + 'clearHistory'.tr, // 清除历史记录 + style: TextStyle( + fontSize: 18.sp, + fontWeight: FontWeight.bold, + color: isDarkMode ? Colors.white : Colors.black, + ), + ), + content: Text( + 'clearHistoryConfirm'.tr, // 确定要清除所有翻译历史记录吗?此操作无法撤销。 + style: TextStyle( + fontSize: 16.sp, + color: isDarkMode ? Colors.grey[300] : Colors.black87, + ), + ), + actions: [ + TextButton( + onPressed: () => Get.back(), + child: Text( + 'cancel'.tr, + style: TextStyle( + fontSize: 16.sp, + color: isDarkMode ? Colors.grey[400] : Colors.grey[700], + ), + ), + ), + TextButton( + onPressed: () { + Get.back(); + onConfirm(); + }, + child: Text( + 'confirm'.tr, + style: TextStyle( + fontSize: 16.sp, + color: Colors.red, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12.r), + ), + ), + ); + } +} diff --git a/lib/modules/translation/views/dialogs/translation_long_press_menu.dart b/lib/modules/translation/views/dialogs/translation_long_press_menu.dart new file mode 100644 index 000000000..7463c6f1f --- /dev/null +++ b/lib/modules/translation/views/dialogs/translation_long_press_menu.dart @@ -0,0 +1,187 @@ +import 'package:flutter/material.dart'; +import 'package:flutter/services.dart'; +import 'package:get/get.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import 'package:haptic_feedback/haptic_feedback.dart'; +import '../../../../core/theme/app_colors.dart'; +import '../../models/translation_models.dart'; + +/// 翻译长按菜单对话框 +/// 负责处理翻译条目的长按菜单显示和交互 +class TranslationLongPressMenu { + /// 显示翻译长按菜单 + static void show({ + required BuildContext context, + required bool isDarkMode, + required LongPressStartDetails details, + required TranslationItem item, + required Function(TranslationItem) onCopy, + required Function(TranslationItem) onDeleteItem, + required Function() onClearHistory, + }) { + // 只对完成态记录显示长按菜单 + if (item.isIntermediate) return; + + // 触发触觉反馈 + Haptics.vibrate(HapticsType.success); + + // 计算弹出菜单位置 + RenderBox? renderBox = + Overlay.of(context).context.findRenderObject() as RenderBox; + final RelativeRect position = RelativeRect.fromRect( + Rect.fromLTRB( + details.globalPosition.dx, + details.globalPosition.dy, + details.globalPosition.dx + 110, // 菜单显示位置X轴坐标 + details.globalPosition.dy - 40, // 菜单显示位置Y轴坐标 + ), + Offset.zero & renderBox.size, + ); + + // 显示弹出菜单 + showMenu( + context: context, + position: position, + color: isDarkMode ? AppColors.cardBackground : Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12.r), + ), + items: _buildMenuItems( + context: context, + isDarkMode: isDarkMode, + item: item, + onCopy: onCopy, + onDeleteItem: onDeleteItem, + onClearHistory: onClearHistory, + ), + ); + } + + /// 构建菜单项 + static List _buildMenuItems({ + required BuildContext context, + required bool isDarkMode, + required TranslationItem item, + required Function(TranslationItem) onCopy, + required Function(TranslationItem) onDeleteItem, + required Function() onClearHistory, + }) { + return [ + // 复制选项 + _buildPopupMenuItem( + Icon( + Icons.copy, + size: 20.w, + color: isDarkMode ? Colors.white : Colors.black87, + ), + 'copy'.tr, // 复制 + isDarkMode ? Colors.white : Colors.black87, + () => onCopy(item), + ), + + // 分隔线 + PopupMenuItem( + height: 1, + enabled: false, + child: Divider( + height: 1, + thickness: 0.8, + indent: 0, + endIndent: 0, + color: isDarkMode ? Colors.grey[700] : Colors.grey[300], + ), + ), + + // 删除该条记录选项 + _buildPopupMenuItem( + Icon( + Icons.delete_outline, + size: 20.w, + color: isDarkMode ? Colors.orange[300] : Colors.orange, + ), + 'delete'.tr, // 删除 + isDarkMode ? Colors.orange[300]! : Colors.orange, + () => onDeleteItem(item), + ), + + // 分隔线 + PopupMenuItem( + height: 1, + enabled: false, + child: Divider( + height: 1, + thickness: 0.8, + indent: 0, + endIndent: 0, + color: isDarkMode ? Colors.grey[700] : Colors.grey[300], + ), + ), + + // 删除所有记录选项 + _buildPopupMenuItem( + Icon( + Icons.delete_sweep, + size: 20.w, + color: Colors.red, + ), + 'clearHistory'.tr, // 清除历史记录 + Colors.red, + onClearHistory, + ), + ]; + } + + /// 构建弹出菜单项 + static PopupMenuEntry _buildPopupMenuItem( + Icon icon, + String title, + Color color, + Function() onTap, + ) { + return PopupMenuItem( + onTap: onTap, + child: SizedBox( + width: 150.w, + child: Row( + children: [ + icon, + 8.horizontalSpace, + Text( + title, + style: TextStyle( + color: color, + fontSize: 14.sp, + ), + ), + ], + ), + ), + ); + } + + /// 复制翻译条目内容 + /// 将原文和翻译文本复制到剪贴板 + static void copyTranslationItem(TranslationItem item) { + // 构建复制文本:原文 + 翻译 + String copyText = item.sourceText; + if (item.translatedText.isNotEmpty) { + copyText += '\n${item.translatedText}'; + } + + Clipboard.setData(ClipboardData(text: copyText)).then((_) { + Get.snackbar( + 'success'.tr, + 'copySuccess'.tr, // 复制成功 + backgroundColor: Colors.blue.withOpacity(0.8), + colorText: Colors.white, + snackPosition: SnackPosition.BOTTOM, + duration: const Duration(milliseconds: 1200), + icon: Icon( + Icons.check_circle_outline, + color: Colors.white, + size: 24.sp, + ), + ); + }); + } +} diff --git a/lib/modules/translation/views/translation_view.dart b/lib/modules/translation/views/translation_view.dart index 6b9ed4eab..28888303e 100644 --- a/lib/modules/translation/views/translation_view.dart +++ b/lib/modules/translation/views/translation_view.dart @@ -18,6 +18,7 @@ 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 'dialogs/translation_long_press_menu.dart'; // 长按菜单 // import 'package:intl/intl.dart'; @@ -893,161 +894,17 @@ class TranslationView extends GetView { LongPressStartDetails details, TranslationItem item, ) { - // 只对完成态记录显示长按菜单 - if (item.isIntermediate) return; - - // 触发触觉反馈 - Haptics.vibrate(HapticsType.success); - - // 计算弹出菜单位置 - RenderBox? renderBox = - Overlay.of(context).context.findRenderObject() as RenderBox; - final RelativeRect position = RelativeRect.fromRect( - Rect.fromLTRB( - details.globalPosition.dx, - details.globalPosition.dy, - details.globalPosition.dx + 110, // 菜单显示位置X轴坐标 - details.globalPosition.dy - 40, // 菜单显示位置Y轴坐标 - ), - Offset.zero & renderBox.size, - ); - - // 显示弹出菜单 - showMenu( + TranslationLongPressMenu.show( context: context, - position: position, - color: isDarkMode ? AppColors.cardBackground : Colors.white, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12.r), - ), - items: _buildTranslationMenuItems(context, isDarkMode, item), + isDarkMode: isDarkMode, + details: details, + item: item, + onCopy: (item) => TranslationLongPressMenu.copyTranslationItem(item), + onDeleteItem: (item) => _deleteTranslationItem(item), + onClearHistory: _showClearHistoryConfirmDialog, ); } - /// 构建翻译条目弹出菜单项 - List _buildTranslationMenuItems( - BuildContext context, - bool isDarkMode, - TranslationItem item, - ) { - return [ - // 复制选项 - _buildPopupMenuItem( - Icon( - Icons.copy, - size: 20.w, - color: isDarkMode ? Colors.white : Colors.black87, - ), - 'copy'.tr, // 复制 - isDarkMode ? Colors.white : Colors.black87, - () => _copyTranslationItem(item), - ), - - // 分隔线 - PopupMenuItem( - height: 1, - enabled: false, - child: Divider( - height: 1, - thickness: 0.8, - indent: 0, - endIndent: 0, - color: isDarkMode ? Colors.grey[700] : Colors.grey[300], - ), - ), - - // 删除该条记录选项 - _buildPopupMenuItem( - Icon( - Icons.delete_outline, - size: 20.w, - color: isDarkMode ? Colors.orange[300] : Colors.orange, - ), - '删除该条记录', - isDarkMode ? Colors.orange[300]! : Colors.orange, - () => _showDeleteItemConfirmDialog(item), - ), - - // 分隔线 - PopupMenuItem( - height: 1, - enabled: false, - child: Divider( - height: 1, - thickness: 0.8, - indent: 0, - endIndent: 0, - color: isDarkMode ? Colors.grey[700] : Colors.grey[300], - ), - ), - - // 删除所有记录选项 - _buildPopupMenuItem( - Icon( - Icons.delete_sweep, - size: 20.w, - color: Colors.red, - ), - '删除所有翻译历史', - Colors.red, - () => _showDeleteAllConfirmDialog(), - ), - ]; - } - - /// 构建弹出菜单项 - PopupMenuEntry _buildPopupMenuItem( - Icon icon, - String title, - Color color, - Function() onTap, - ) { - return PopupMenuItem( - onTap: onTap, - child: SizedBox( - width: 150.w, - child: Row( - children: [ - icon, - 8.horizontalSpace, - Text( - title, - style: TextStyle( - color: color, - fontSize: 14.sp, - ), - ), - ], - ), - ), - ); - } - - /// 复制翻译条目内容 - void _copyTranslationItem(TranslationItem item) { - // 构建复制文本:原文 + 翻译 - String copyText = item.sourceText; - if (item.translatedText.isNotEmpty) { - copyText += '\n${item.translatedText}'; - } - - Clipboard.setData(ClipboardData(text: copyText)).then((_) { - Get.snackbar( - 'success'.tr, - 'copySuccess'.tr, // 复制成功 - backgroundColor: Colors.blue.withOpacity(0.8), - colorText: Colors.white, - snackPosition: SnackPosition.BOTTOM, - duration: const Duration(milliseconds: 1200), - icon: Icon( - Icons.check_circle_outline, - color: Colors.white, - size: 24.sp, - ), - ); - }); - } - // 显示删除单条记录确认对话框 void _showDeleteItemConfirmDialog(TranslationItem item) { final isDarkMode = Theme.of(Get.context!).brightness == Brightness.dark; @@ -1181,86 +1038,26 @@ class TranslationView extends GetView { } // 显示删除所有记录确认对话框 - void _showDeleteAllConfirmDialog() { - final isDarkMode = Theme.of(Get.context!).brightness == Brightness.dark; + void _showClearHistoryConfirmDialog() { + final isDarkMode = Get.isDarkMode; Get.dialog( AlertDialog( backgroundColor: isDarkMode ? AppColors.cardBackground : Colors.white, title: Text( - '删除所有翻译历史', + 'clearHistory'.tr, style: TextStyle( fontSize: 18.sp, fontWeight: FontWeight.bold, color: isDarkMode ? Colors.white : Colors.black, ), ), - content: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Icon( - Icons.warning_amber_rounded, - color: Colors.orange, - size: 24.sp, - ), - SizedBox(width: 8.w), - Expanded( - child: Text( - '危险操作', - style: TextStyle( - fontSize: 16.sp, - fontWeight: FontWeight.bold, - color: Colors.orange, - ), - ), - ), - ], - ), - SizedBox(height: 12.h), - Text( - '确定要删除所有翻译历史记录吗?此操作将清空所有翻译记录,且无法撤销。', - style: TextStyle( - fontSize: 16.sp, - color: isDarkMode ? Colors.grey[300] : Colors.black87, - height: 1.4, - ), - ), - SizedBox(height: 12.h), - Container( - padding: EdgeInsets.all(12.w), - decoration: BoxDecoration( - color: Colors.red.withOpacity(0.1), - borderRadius: BorderRadius.circular(8.r), - border: Border.all( - color: Colors.red.withOpacity(0.3), - width: 1, - ), - ), - child: Row( - children: [ - Icon( - Icons.info_outline, - color: Colors.red, - size: 20.sp, - ), - SizedBox(width: 8.w), - Expanded( - child: Text( - '当前共有 ${controller.translationHistory.length} 条翻译记录', - style: TextStyle( - fontSize: 14.sp, - color: Colors.red[700], - fontWeight: FontWeight.w500, - ), - ), - ), - ], - ), - ), - ], + content: Text( + 'clearHistoryConfirm'.tr, + style: TextStyle( + fontSize: 16.sp, + color: isDarkMode ? Colors.grey[300] : Colors.black87, + ), ), actions: [ TextButton( @@ -1275,11 +1072,12 @@ class TranslationView extends GetView { ), TextButton( onPressed: () { + // 清除历史记录 + controller.clearTranslationHistory(); Get.back(); - _deleteAllTranslationHistory(); }, child: Text( - '确认删除', + 'confirm'.tr, style: TextStyle( fontSize: 16.sp, color: Colors.red, @@ -1295,44 +1093,6 @@ class TranslationView extends GetView { ); } - // 删除所有翻译历史记录的具体实现 - void _deleteAllTranslationHistory() { - try { - // 调用控制器的删除所有记录方法 - controller.clearTranslationHistory(); - - // 显示删除成功提示 - Get.snackbar( - '删除成功', - '已清空所有翻译历史记录', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Get.isDarkMode ? Colors.grey[800] : Colors.grey[200], - colorText: Get.isDarkMode ? Colors.white : Colors.black, - duration: const Duration(milliseconds: 1200), - icon: Icon( - Icons.check_circle_outline, - color: Colors.green, - size: 24.sp, - ), - ); - } catch (e) { - // 显示删除失败提示 - Get.snackbar( - 'error'.tr, - '删除翻译历史时发生错误: ${e.toString()}', - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Colors.red[400], - colorText: Colors.white, - duration: const Duration(milliseconds: 1500), - icon: Icon( - Icons.error_outline, - color: Colors.white, - size: 24.sp, - ), - ); - } - } - // 构建语言选择栏 Widget _buildLanguageSelectionBar(bool isDarkMode) { return Container(