3 changed files with 379 additions and 260 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