Browse Source

feat(translation): 添加翻译条目长按菜单功能

- 为翻译条目添加长按菜单,支持复制、删除单条和全部记录
- 实现触觉反馈增强用户体验
- 添加确认对话框防止误操作
newdev_shunjiawei
lxm 1 year ago
parent
commit
a0dad4d115
  1. 497
      lib/modules/translation/views/translation_view.dart

497
lib/modules/translation/views/translation_view.dart

@ -2,8 +2,10 @@ import 'dart:ui';
import 'dart:io'; import 'dart:io';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:get/get.dart'; import 'package:get/get.dart';
import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart';
import 'package:haptic_feedback/haptic_feedback.dart';
import '../controllers/translation_controller.dart'; import '../controllers/translation_controller.dart';
import '../models/translation_models.dart'; import '../models/translation_models.dart';
import '../views/translation_history_view.dart'; import '../views/translation_history_view.dart';
@ -414,103 +416,113 @@ class TranslationView extends GetView<TranslationController> {
Widget _buildTranslationItemWidget( Widget _buildTranslationItemWidget(
BuildContext context, TranslationItem item) { BuildContext context, TranslationItem item) {
return Container( final isDarkMode = Theme.of(context).brightness == Brightness.dark;
margin: const EdgeInsets.only(bottom: 8),
padding: const EdgeInsets.all(12), return GestureDetector(
decoration: BoxDecoration( onLongPressStart: (details) => _onLongPressTranslationItem(
color: item.isIntermediate context,
? Colors.grey.withOpacity(0.1) // 中间结果使用浅灰色底色 isDarkMode,
: (item.sourceLanguageCode == controller.sourceLanguageCode.value details,
? Theme.of(context) item,
.colorScheme ),
.primary child: Container(
.withOpacity(0.08) // 源语言匹配时使用主题色底色 margin: const EdgeInsets.only(bottom: 8),
: Theme.of(context) padding: const EdgeInsets.all(12),
.colorScheme decoration: BoxDecoration(
.secondary
.withOpacity(0.08)), // 否则使用次要色底色
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: item.isIntermediate color: item.isIntermediate
? Colors.grey.withOpacity(0.2) ? Colors.grey.withOpacity(0.1) // 中间结果使用浅灰色底色
: Colors.transparent, : (item.sourceLanguageCode == controller.sourceLanguageCode.value
width: 1, ? Theme.of(context)
.colorScheme
.primary
.withOpacity(0.08) // 源语言匹配时使用主题色底色
: Theme.of(context)
.colorScheme
.secondary
.withOpacity(0.08)), // 否则使用次要色底色
borderRadius: BorderRadius.circular(12),
border: Border.all(
color: item.isIntermediate
? Colors.grey.withOpacity(0.2)
: Colors.transparent,
width: 1,
),
), ),
), child: Column(
child: Column( crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, children: [
children: [ // 源文本和删除按钮行
// 源文本和删除按钮行 Row(
Row( crossAxisAlignment: CrossAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start, children: [
children: [ // 源文本
// 源文本 Expanded(
Expanded( child: Text(
child: Text( item.sourceText,
item.sourceText, style: TextStyle(
style: TextStyle( fontSize: 16,
fontSize: 16, color: Theme.of(context).textTheme.bodyLarge?.color,
color: Theme.of(context).textTheme.bodyLarge?.color, ),
), ),
), ),
), // 删除按钮 - 只对完成态记录显示,与源文本平齐
// 删除按钮 - 只对完成态记录显示,与源文本平齐 if (!item.isIntermediate)
if (!item.isIntermediate) GestureDetector(
GestureDetector( onTap: () => _showDeleteItemConfirmDialog(item),
onTap: () => _showDeleteItemConfirmDialog(item), child: Container(
child: Container( padding: EdgeInsets.all(4.w),
padding: EdgeInsets.all(4.w), child: Icon(
child: Icon( Icons.delete_outline,
Icons.delete_outline, size: 18.sp,
size: 18.sp, color: Colors.grey[500],
color: Colors.grey[500], ),
), ),
), ),
), ],
],
),
if (item.translatedText.isNotEmpty) ...[
const SizedBox(height: 8),
// 翻译文本
Text(
item.translatedText,
style: TextStyle(
fontSize: 16,
color: Theme.of(context).textTheme.bodyMedium?.color,
fontWeight: FontWeight.w500,
),
), ),
], if (item.translatedText.isNotEmpty) ...[
// 语言信息和时间戳 const SizedBox(height: 8),
const SizedBox(height: 4), // 翻译文本
Row( Text(
mainAxisAlignment: MainAxisAlignment.spaceBetween, item.translatedText,
children: [ style: TextStyle(
Expanded( fontSize: 16,
child: Text( color: Theme.of(context).textTheme.bodyMedium?.color,
'${item.sourceLanguageName.tr} → ${item.targetLanguageName.tr}', fontWeight: FontWeight.w500,
style: TextStyle(
fontSize: 12,
color: Theme.of(context).textTheme.bodySmall?.color,
),
), ),
), ),
// 播放按钮 ],
if (!item.isIntermediate) // 语言信息和时间戳
GestureDetector( const SizedBox(height: 4),
onTap: () => controller.playTranslationItem(item), Row(
child: Container( mainAxisAlignment: MainAxisAlignment.spaceBetween,
padding: EdgeInsets.all(4.w), children: [
child: Icon( Expanded(
Icons.volume_up, child: Text(
size: 20.sp, '${item.sourceLanguageName.tr} → ${item.targetLanguageName.tr}',
color: Theme.of(context).colorScheme.primary, style: TextStyle(
fontSize: 12,
color: Theme.of(context).textTheme.bodySmall?.color,
), ),
), ),
), ),
], // 播放按钮
), if (!item.isIntermediate)
], GestureDetector(
onTap: () => controller.playTranslationItem(item),
child: Container(
padding: EdgeInsets.all(4.w),
child: Icon(
Icons.volume_up,
size: 20.sp,
color: Theme.of(context).colorScheme.primary,
),
),
),
],
),
],
),
), ),
); );
} }
@ -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) { void _showDeleteItemConfirmDialog(TranslationItem item) {
final isDarkMode = Theme.of(Get.context!).brightness == Brightness.dark; 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) { Widget _buildLanguageSelectionBar(bool isDarkMode) {
return Container( return Container(

Loading…
Cancel
Save