Browse Source

feat(translation): 新增翻译长按菜单和删除确认对话框组件

将翻译长按菜单和删除确认对话框逻辑抽离为独立组件
重构翻译视图中的相关代码,提高代码复用性和可维护性
weicu
lxm 1 year ago
parent
commit
d9d135ad10
  1. 172
      lib/modules/translation/views/dialogs/delete_confirm_dialog.dart
  2. 187
      lib/modules/translation/views/dialogs/translation_long_press_menu.dart
  3. 280
      lib/modules/translation/views/translation_view.dart

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

187
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<PopupMenuEntry> _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,
),
);
});
}
}

280
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<TranslationController> {
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<PopupMenuEntry> _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<TranslationController> {
}
// 显示删除所有记录确认对话框
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<TranslationController> {
),
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<TranslationController> {
);
}
// 删除所有翻译历史记录的具体实现
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(

Loading…
Cancel
Save