6 changed files with 601 additions and 131 deletions
@ -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), |
||||
|
), |
||||
|
), |
||||
|
); |
||||
|
} |
||||
|
} |
||||
@ -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, |
||||
|
), |
||||
|
); |
||||
|
}); |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue