diff --git a/lib/core/bindings/initial_binding.dart b/lib/core/bindings/initial_binding.dart index ae0534031..38f439e9d 100644 --- a/lib/core/bindings/initial_binding.dart +++ b/lib/core/bindings/initial_binding.dart @@ -1,5 +1,5 @@ -import 'package:voitrans/data/services/pay_service.dart'; -import 'package:voitrans/data/services/qq_music.dart'; +import '/data/services/pay_service.dart'; +import '/data/services/qq_music.dart'; import '../../../data/services/user_portrait.dart'; diff --git a/lib/core/widgets/common_bottom_nav.dart b/lib/core/widgets/common_bottom_nav.dart index 04c9d5b31..c1946f17f 100644 --- a/lib/core/widgets/common_bottom_nav.dart +++ b/lib/core/widgets/common_bottom_nav.dart @@ -17,8 +17,8 @@ class CommonBottomNav extends StatelessWidget { return BottomNavigationBar( currentIndex: currentIndex, onTap: (index) { - // Navigate to the appropriate page based on index - if(index ==currentIndex) return;//如果是当前页面,不跳转 + // 点击当前项不执行跳转 + if (index == currentIndex) return; //如果是当前页面,不跳转 switch (index) { case 0: Get.offAllNamed(Routes.home); diff --git a/lib/modules/translation/views/dialogs/translation_long_press_menu.dart b/lib/modules/translation/views/dialogs/translation_long_press_menu.dart index 7463c6f1f..b1750145e 100644 --- a/lib/modules/translation/views/dialogs/translation_long_press_menu.dart +++ b/lib/modules/translation/views/dialogs/translation_long_press_menu.dart @@ -174,7 +174,7 @@ class TranslationLongPressMenu { 'copySuccess'.tr, // 复制成功 backgroundColor: Colors.blue.withOpacity(0.8), colorText: Colors.white, - snackPosition: SnackPosition.BOTTOM, + snackPosition: SnackPosition.TOP, duration: const Duration(milliseconds: 1200), icon: Icon( Icons.check_circle_outline, diff --git a/lib/modules/translation/views/translation_view.dart b/lib/modules/translation/views/translation_view.dart index 39a68f818..b51cf7fa0 100644 --- a/lib/modules/translation/views/translation_view.dart +++ b/lib/modules/translation/views/translation_view.dart @@ -17,6 +17,7 @@ import '../../common/widgets/language_selector_bar.dart'; // 导入拆分后的组件 import 'widgets/face_to_face_layout.dart'; // 面对面翻译布局 import 'widgets/draggable_floating_button.dart'; // 可拖拽的悬浮按钮 +import 'widgets/translation_item_widget.dart'; // 翻译项组件 import 'dialogs/language_selection_dialog.dart'; // 语言选择对话框 import 'dialogs/translation_mode_selector.dart'; // 翻译模式选择器 import 'dialogs/translation_long_press_menu.dart'; // 长按菜单 @@ -27,6 +28,17 @@ import 'dialogs/translation_long_press_menu.dart'; // 长按菜单 class TranslationView extends GetView { const TranslationView({Key? key}) : super(key: key); + // 本地交互状态:当前显示操作按钮的消息时间戳(不改动控制器) + static final Rx _activeActionTimestamp = Rx(null); + + // 切换当前操作按钮展示的项(再次点击同一项则隐藏) + void _toggleActiveActionItem(TranslationItem item) { + final DateTime ts = item.timestamp; + final DateTime? current = _activeActionTimestamp.value; + _activeActionTimestamp.value = + (current != null && current.isAtSameMomentAs(ts)) ? null : ts; + } + @override Widget build(BuildContext context) { final isDarkMode = Theme.of(context).brightness == Brightness.dark; @@ -137,7 +149,7 @@ class TranslationView extends GetView { size: 20.sp, ), onPressed: () { - // Stop recognition and TTS before navigating back + // 点击返回时,停止所有语音识别和TTS controller.stopAll(); Get.back(); }, @@ -271,10 +283,8 @@ class TranslationView extends GetView { children: [ // 统一通用语言选择栏组件 LanguageSelectorBar( - sourceText: () => - controller.sourceLanguage.value.split(' ')[0], - targetText: () => - controller.targetLanguage.value.split(' ')[0], + sourceText: () => controller.sourceLanguage.value.split(' ')[0], + targetText: () => controller.targetLanguage.value.split(' ')[0], onTapSource: () => LanguageSelectionDialog.show(true, isDarkMode), onTapTarget: () => @@ -286,9 +296,7 @@ class TranslationView extends GetView { ? Icon( Icons.phone_in_talk, size: 20.sp, - color: isDarkMode - ? Colors.white70 - : Colors.grey[600], + color: isDarkMode ? Colors.white70 : Colors.grey[600], ) : null, ), @@ -396,128 +404,18 @@ class TranslationView extends GetView { final historyItem = controller.translationHistory[historyIndex]; // 构建项目widget - return _buildTranslationItemWidget(context, historyItem); + return TranslationItemWidget( + item: historyItem, + activeActionTimestamp: _activeActionTimestamp, + onToggleActionItem: _toggleActiveActionItem, + onLongPressStart: _onLongPressTranslationItem, + onDeleteItemConfirm: _showDeleteItemConfirmDialog, + ); }, ); }); } - Widget _buildTranslationItemWidget( - BuildContext context, TranslationItem item) { - final isDarkMode = Theme.of(context).brightness == Brightness.dark; - - // 定义自适应颜色 - final selfColor = isDarkMode - ? Colors.blue[800]!.withOpacity(0.15) // 深色模式:深蓝+透明度 - : Colors.blue[50]!; // 浅色模式:浅蓝 - - final otherColor = isDarkMode - ? Colors.green[800]!.withOpacity(0.15) // 深色模式:深绿+透明度 - : Colors.green[50]!; // 浅色模式:浅绿 - - return GestureDetector( - onLongPressStart: (details) => _onLongPressTranslationItem( - context, - isDarkMode, - details, - item, - ), - child: Container( - margin: const EdgeInsets.only(bottom: 8), - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: item.isIntermediate - ? Colors.grey.withOpacity(isDarkMode ? 0.15 : 0.1) // 中间结果 - : (item.sourceLanguageCode == controller.sourceLanguageCode.value - ? selfColor // 自己说的话 - : otherColor), // 对方说的话 - borderRadius: BorderRadius.circular(12), - border: Border.all( - color: item.isIntermediate - ? Colors.grey.withOpacity(isDarkMode ? 0.3 : 0.2) - : Colors.transparent, - width: 1, - ), - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // 源文本和删除按钮行 - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - // 源文本 - Expanded( - child: Text( - item.sourceText, - style: TextStyle( - fontSize: 16, - color: Theme.of(context).textTheme.bodyLarge?.color, - ), - ), - ), - // 删除按钮 - 只对完成态记录显示,与源文本平齐 - if (!item.isIntermediate) - GestureDetector( - onTap: () => _showDeleteItemConfirmDialog(item), - child: Container( - padding: EdgeInsets.all(4.w), - child: Icon( - Icons.delete_outline, - size: 18.sp, - 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, - ), - ), - ], - // 语言信息和时间戳 - const SizedBox(height: 4), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Expanded( - child: Text( - '${item.sourceLanguageName.tr} → ${item.targetLanguageName.tr}', - 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, - ), - ), - ), - ], - ), - ], - ), - ), - ); - } - Widget _buildControlBar(bool isDarkMode) { // 使用GlobalKey来获取控制栏的高度 final controlBarKey = GlobalKey(); @@ -781,10 +679,10 @@ class TranslationView extends GetView { mainAxisSize: MainAxisSize.min, children: [ SizedBox( - width: 24, - height: 24, + width: 24.w, + height: 24.h, child: CircularProgressIndicator( - strokeWidth: 2.5, + strokeWidth: 2.5.w, valueColor: const AlwaysStoppedAnimation( AppColors.primary, ), @@ -792,7 +690,7 @@ class TranslationView extends GetView { ), const SizedBox(height: 12), Text( - '正在加载历史记录...', + 'loading'.tr, //加载中 style: TextStyle( fontSize: 14.sp, color: Colors.grey[600], @@ -828,7 +726,7 @@ class TranslationView extends GetView { ), SizedBox(width: 8.w), Text( - '加载失败,点击重试', + 'loadFailedClickRetry'.tr, style: TextStyle( fontSize: 14.sp, color: Colors.red[600], @@ -862,7 +760,7 @@ class TranslationView extends GetView { ), SizedBox(width: 4.w), Text( - '向上滑动查看更多历史', + 'swipeUpForMore'.tr, style: TextStyle( fontSize: 12.sp, color: Colors.grey[600], diff --git a/lib/modules/translation/views/widgets/translation_item_widget.dart b/lib/modules/translation/views/widgets/translation_item_widget.dart new file mode 100644 index 000000000..49ce94472 --- /dev/null +++ b/lib/modules/translation/views/widgets/translation_item_widget.dart @@ -0,0 +1,191 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; + +import '../../controllers/translation_controller.dart'; +import '../../models/translation_models.dart'; +import '../dialogs/translation_long_press_menu.dart'; + +class TranslationItemWidget extends GetView { + // 传入的翻译条目 + final TranslationItem item; + + // 传入用于控制操作按钮显隐的时间戳状态 + final Rx activeActionTimestamp; + + // 点击气泡时切换操作按钮显隐 + final void Function(TranslationItem item) onToggleActionItem; + + // 长按时弹出菜单(需要上下文、暗色模式、手势详情、条目) + final void Function( + BuildContext context, + bool isDarkMode, + LongPressStartDetails details, + TranslationItem item, + ) onLongPressStart; + + // 删除按钮点击时的确认对话框 + final void Function(TranslationItem item) onDeleteItemConfirm; + + const TranslationItemWidget({ + Key? key, + required this.item, + required this.activeActionTimestamp, + required this.onToggleActionItem, + required this.onLongPressStart, + required this.onDeleteItemConfirm, + }) : super(key: key); + + @override + Widget build(BuildContext context) { + final isDarkMode = Theme.of(context).brightness == Brightness.dark; + + // 定义自适应颜色(与原实现保持一致) + final selfColor = + isDarkMode ? Colors.blue[800]!.withOpacity(0.15) : Colors.blue[50]!; + final otherColor = + isDarkMode ? Colors.green[800]!.withOpacity(0.15) : Colors.green[50]!; + + // 判断是否是己方的翻译项(依据控制器当前源语言) + final bool isSelf = + item.sourceLanguageCode == controller.sourceLanguageCode.value; + + return GestureDetector( + onTap: () => onToggleActionItem(item), // 触碰气泡时切换按钮显隐 + onLongPressStart: (details) => + onLongPressStart(context, isDarkMode, details, item), + child: Align( + alignment: item.isIntermediate + ? Alignment.center + : (isSelf ? Alignment.centerLeft : Alignment.centerRight), + child: Container( + margin: EdgeInsets.only(bottom: 8.h), + padding: EdgeInsets.all(12.w), + constraints: BoxConstraints(maxWidth: 0.8.sw, minWidth: 0.35.sw), + decoration: BoxDecoration( + color: item.isIntermediate + ? Colors.grey.withOpacity(isDarkMode ? 0.15 : 0.1) // 中间结果 + : (isSelf ? selfColor : otherColor), // 自己 / 对方 + borderRadius: BorderRadius.circular(12.r), + border: Border.all( + color: item.isIntermediate + ? Colors.grey.withOpacity(isDarkMode ? 0.3 : 0.2) + : Colors.transparent, + width: 1, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // 源文本 + Text( + item.sourceText, + style: TextStyle( + fontSize: 16.sp, + color: Theme.of(context).textTheme.bodyLarge?.color, + ), + ), + SizedBox(height: 8.h), + // 分隔线 + 译文 + Container( + padding: EdgeInsets.only(top: 8.h), + decoration: BoxDecoration( + border: Border( + top: BorderSide( + color: item.isIntermediate + ? (isDarkMode + ? Colors.grey[400]!.withOpacity(0.15) + : Colors.grey[400]!.withOpacity(0.25)) + : (isSelf + ? (isDarkMode + ? Colors.blue[300]!.withOpacity(0.15) + : Colors.blue[300]!.withOpacity(0.25)) + : (isDarkMode + ? Colors.green[300]!.withOpacity(0.15) + : Colors.green[300]!.withOpacity(0.25))), + width: 1, + ), + ), + ), + child: Text( + item.translatedText, + style: TextStyle( + fontSize: 16.sp, + color: Theme.of(context).textTheme.bodyMedium?.color, + fontWeight: FontWeight.w500, + ), + ), + ), + // 操作按钮:复制 / 播放 / 删除(仅非中间态且显隐状态为激活时显示) + Obx(() { + final bool isActive = activeActionTimestamp.value + ?.isAtSameMomentAs(item.timestamp) ?? + false; + if (!item.isIntermediate && isActive) { + return Column( + children: [ + SizedBox(height: 8.h), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + // 复制按钮(复用长按菜单的复制逻辑) + GestureDetector( + onTap: () => + TranslationLongPressMenu.copyTranslationItem( + item), + child: Container( + padding: EdgeInsets.all(4.w), + child: Tooltip( + message: 'copy'.tr, // 翻译键代表的中文文本内容 + child: Icon( + Icons.copy, + size: 20.sp, + color: Theme.of(context).colorScheme.primary, + ), + ), + ), + ), + SizedBox(width: 8.w), + // 播放按钮(调用控制器原有方法) + 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, + ), + ), + ), + SizedBox(width: 8.w), + // 删除按钮(弹出确认) + GestureDetector( + onTap: () => onDeleteItemConfirm(item), + child: Container( + padding: EdgeInsets.all(4.w), + child: Tooltip( + message: 'delete'.tr, // 翻译键代表的中文文本内容 + child: Icon( + Icons.delete_outline, + size: 20.sp, + color: Colors.grey[500], + ), + ), + ), + ), + ], + ) + ], + ); + } else { + return const SizedBox.shrink(); + } + }), + ], + ), + ), + ), + ); + } +}