|
|
|
@ -2,8 +2,10 @@ import 'dart:ui'; |
|
|
|
import 'dart:io'; |
|
|
|
|
|
|
|
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 '../controllers/translation_controller.dart'; |
|
|
|
import '../models/translation_models.dart'; |
|
|
|
import '../views/translation_history_view.dart'; |
|
|
|
@ -414,7 +416,16 @@ class TranslationView extends GetView<TranslationController> { |
|
|
|
|
|
|
|
Widget _buildTranslationItemWidget( |
|
|
|
BuildContext context, TranslationItem item) { |
|
|
|
return Container( |
|
|
|
final isDarkMode = Theme.of(context).brightness == Brightness.dark; |
|
|
|
|
|
|
|
return GestureDetector( |
|
|
|
onLongPressStart: (details) => _onLongPressTranslationItem( |
|
|
|
context, |
|
|
|
isDarkMode, |
|
|
|
details, |
|
|
|
item, |
|
|
|
), |
|
|
|
child: Container( |
|
|
|
margin: const EdgeInsets.only(bottom: 8), |
|
|
|
padding: const EdgeInsets.all(12), |
|
|
|
decoration: BoxDecoration( |
|
|
|
@ -512,6 +523,7 @@ class TranslationView extends GetView<TranslationController> { |
|
|
|
), |
|
|
|
], |
|
|
|
), |
|
|
|
), |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
@ -874,6 +886,168 @@ class TranslationView extends GetView<TranslationController> { |
|
|
|
}); |
|
|
|
} |
|
|
|
|
|
|
|
/// 处理翻译条目长按事件 |
|
|
|
void _onLongPressTranslationItem( |
|
|
|
BuildContext context, |
|
|
|
bool isDarkMode, |
|
|
|
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( |
|
|
|
context: context, |
|
|
|
position: position, |
|
|
|
color: isDarkMode ? AppColors.cardBackground : Colors.white, |
|
|
|
shape: RoundedRectangleBorder( |
|
|
|
borderRadius: BorderRadius.circular(12.r), |
|
|
|
), |
|
|
|
items: _buildTranslationMenuItems(context, isDarkMode, item), |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
/// 构建翻译条目弹出菜单项 |
|
|
|
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; |
|
|
|
@ -1006,6 +1180,159 @@ class TranslationView extends GetView<TranslationController> { |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
// 显示删除所有记录确认对话框 |
|
|
|
void _showDeleteAllConfirmDialog() { |
|
|
|
final isDarkMode = Theme.of(Get.context!).brightness == Brightness.dark; |
|
|
|
|
|
|
|
Get.dialog( |
|
|
|
AlertDialog( |
|
|
|
backgroundColor: isDarkMode ? AppColors.cardBackground : Colors.white, |
|
|
|
title: Text( |
|
|
|
'删除所有翻译历史', |
|
|
|
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, |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
], |
|
|
|
), |
|
|
|
), |
|
|
|
], |
|
|
|
), |
|
|
|
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(); |
|
|
|
_deleteAllTranslationHistory(); |
|
|
|
}, |
|
|
|
child: Text( |
|
|
|
'确认删除', |
|
|
|
style: TextStyle( |
|
|
|
fontSize: 16.sp, |
|
|
|
color: Colors.red, |
|
|
|
fontWeight: FontWeight.bold, |
|
|
|
), |
|
|
|
), |
|
|
|
), |
|
|
|
], |
|
|
|
shape: RoundedRectangleBorder( |
|
|
|
borderRadius: BorderRadius.circular(12.r), |
|
|
|
), |
|
|
|
), |
|
|
|
); |
|
|
|
} |
|
|
|
|
|
|
|
// 删除所有翻译历史记录的具体实现 |
|
|
|
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( |
|
|
|
|