diff --git a/lib/data/services/ota_check_service.dart b/lib/data/services/ota_check_service.dart index 12dad5451..5caa31493 100644 --- a/lib/data/services/ota_check_service.dart +++ b/lib/data/services/ota_check_service.dart @@ -9,49 +9,50 @@ import 'package:flutter_screenutil/flutter_screenutil.dart'; /// 负责管理全局OTA检查状态,确保在应用生命周期内只检查一次 class OtaCheckService extends GetxService { static const String _tag = 'OtaCheckService'; - + // 全局状态管理 static bool _hasCheckedInCurrentSession = false; static bool _isCheckingInProgress = false; - + final _otaCoreService = OtaCoreService(); - + /// 获取服务实例 static OtaCheckService get to => Get.find(); - + @override void onInit() { super.onInit(); Logger.d(_tag, 'OTA检查服务已初始化'); } - + /// 重置检查状态(仅在应用重启时调用) static void resetCheckStatus() { _hasCheckedInCurrentSession = false; _isCheckingInProgress = false; Logger.d(_tag, 'OTA检查状态已重置'); } - + /// 检查是否应该进行OTA检查 bool shouldCheckOta() { return !_hasCheckedInCurrentSession && !_isCheckingInProgress; } - + /// 标记为已检查 void _markAsChecked() { _hasCheckedInCurrentSession = true; _isCheckingInProgress = false; Logger.d(_tag, 'OTA检查状态已标记为完成'); } - + /// 检查当前是否在Home页面 bool _isCurrentlyOnHomePage() { final currentRoute = Get.currentRoute; - final isOnHome = currentRoute == Routes.home || currentRoute == Routes.initial; + final isOnHome = + currentRoute == Routes.home || currentRoute == Routes.initial; Logger.d(_tag, '当前路由: $currentRoute, 是否在Home页面: $isOnHome'); return isOnHome; } - + /// 执行OTA升级检查 /// [isFullyConnected] 设备是否完全连接 /// 返回是否成功执行了检查 @@ -62,53 +63,54 @@ class OtaCheckService extends GetxService { Logger.d(_tag, '跳过OTA检查 - 已检查过或正在检查中'); return false; } - + // 检查设备连接状态 if (!isFullyConnected) { Logger.d(_tag, '设备未完全连接,跳过OTA检查'); return false; } - + // 检查当前页面 if (!_isCurrentlyOnHomePage()) { Logger.d(_tag, '当前不在Home页面,跳过OTA检查'); return false; } - + // 标记为检查中 _isCheckingInProgress = true; Logger.d(_tag, '开始执行OTA固件升级检查...'); - + // 获取当前版本信息 final currentInfo = await _otaCoreService.getCurrentVersionInfo(); final currentVersion = currentInfo['currentVersion'] as String; - + // 获取最新版本信息 final latestInfo = await _otaCoreService.getLatestVersionInfo(); final latestVersion = latestInfo['latestVersion'] as String; - + Logger.d(_tag, '当前版本: $currentVersion, 最新版本: $latestVersion'); - + // 检查是否有更新 - final hasUpdate = _otaCoreService.isNewVersionAvailable(currentVersion, latestVersion); - + final hasUpdate = + _otaCoreService.isNewVersionAvailable(currentVersion, latestVersion); + if (hasUpdate) { Logger.d(_tag, '发现新版本,准备显示升级提醒'); - + // 再次确认当前在Home页面(防止异步执行期间页面切换) if (_isCurrentlyOnHomePage()) { - _showOtaUpgradeDialog(latestVersion, latestInfo['updateDescription'] as String?); + _showOtaUpgradeDialog( + latestVersion, latestInfo['updateDescription'] as String?); } else { Logger.d(_tag, '检查完成时已不在Home页面,取消显示弹窗'); } } else { Logger.d(_tag, '当前已是最新版本'); } - + // 标记为已检查(无论是否有更新都标记为已检查) _markAsChecked(); return true; - } catch (e) { Logger.e(_tag, '检查OTA升级失败: $e'); // 即使检查失败,也标记为已检查,避免重复尝试 @@ -116,7 +118,7 @@ class OtaCheckService extends GetxService { return false; } } - + /// 显示OTA升级提醒对话框 /// [latestVersion] 最新版本号 /// [updateDescription] 更新描述 @@ -126,24 +128,32 @@ class OtaCheckService extends GetxService { Logger.d(_tag, '显示弹窗时发现不在Home页面,取消显示'); return; } - + final isDarkMode = Get.isDarkMode; + Logger.d(_tag, '显示OTA升级提醒对话框'); - + Get.dialog( AlertDialog( title: Row( children: [ Icon( Icons.headset, - color: Colors.blue[600], + color: isDarkMode ? const Color(0xFF0066CC) : Colors.blue[600], size: 24.sp, ), SizedBox(width: 8.w), - Text( - 'ota_newVersionFound'.tr, - style: TextStyle( - color: Colors.blue[700], - fontWeight: FontWeight.w600, + // 使用Flexible确保标题文本能够自适应不同语言长度,避免溢出 + Flexible( + child: Text( + 'ota_newVersionFound'.tr, // 发现新版本 + style: TextStyle( + color: isDarkMode ? Colors.white : Colors.blue[700], + fontWeight: FontWeight.w600, + fontSize: 20.sp, // 设置合适的字体大小 + ), + softWrap: true, // 启用软换行 + overflow: TextOverflow.visible, // 确保文本完全显示 + maxLines: 2, // 最多显示两行 ), ), ], @@ -155,6 +165,7 @@ class OtaCheckService extends GetxService { Text( '${'latestFirmwareVersion'.tr}: $latestVersion', style: TextStyle( + color: isDarkMode ? Colors.grey[400] : Colors.grey[600], fontWeight: FontWeight.w500, fontSize: 16.sp, ), @@ -173,7 +184,9 @@ class OtaCheckService extends GetxService { }, child: Text( 'talkLater'.tr, - style: TextStyle(color: Colors.grey[600]), + style: TextStyle( + color: isDarkMode ? Colors.grey[400] : Colors.grey[600], + ), ), ), // 立即升级 (靠右) @@ -184,11 +197,15 @@ class OtaCheckService extends GetxService { Get.toNamed(Routes.OTA_UPGRADE); }, style: ElevatedButton.styleFrom( - backgroundColor: Colors.blue[600], + backgroundColor: + isDarkMode ? const Color(0xFF0066CC) : Colors.blue[600], foregroundColor: Colors.white, shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(8.sp), ), + side: isDarkMode + ? BorderSide(color: Colors.grey[800]!, width: 1) + : BorderSide.none, ), child: Text( 'ota_updateNow'.tr, @@ -201,12 +218,12 @@ class OtaCheckService extends GetxService { shape: RoundedRectangleBorder( borderRadius: BorderRadius.circular(12.sp), ), - backgroundColor: Colors.white, + backgroundColor: isDarkMode ? const Color(0xFF1A1A1A) : Colors.white, ), barrierDismissible: true, // 允许点击外部关闭 ); } - + /// 获取当前检查状态(用于调试) Map getCheckStatus() { return { diff --git a/lib/modules/agent/controllers/agent_controller.dart b/lib/modules/agent/controllers/agent_controller.dart index 5f6386cce..5c9a5c683 100644 --- a/lib/modules/agent/controllers/agent_controller.dart +++ b/lib/modules/agent/controllers/agent_controller.dart @@ -706,7 +706,7 @@ class AgentController extends GetxController with WidgetsBindingObserver { // messages.refresh(); } - Get.snackbar('error'.tr, '${event.data['message']}'); + // Get.snackbar('error'.tr, '${event.data['message']}'); Logger.e(TAG, '代理服务错误: ${event.data['message']}'); break; @@ -2084,12 +2084,13 @@ class AgentController extends GetxController with WidgetsBindingObserver { return; } } - + // 5. 最后尝试内置高德导航 + _navigationManager.open(start, "$endlongitude,$endlatitude", mode); // 5. 如果都没有安装,提示用户 - print('未找到可用的导航应用'); - final availableApps = - Platform.isIOS ? '百度地图、高德地图、谷歌地图或苹果地图' : '百度地图、高德地图或谷歌地图'; - Get.snackbar('提示', '未找到可用的导航应用,请安装$availableApps'); + // print('未找到可用的导航应用'); + // final availableApps = + // Platform.isIOS ? '百度地图、高德地图、谷歌地图或苹果地图' : '百度地图、高德地图或谷歌地图'; + // Get.snackbar('提示', '未找到可用的导航应用,请安装$availableApps'); } catch (e) { print('自动选择导航失败: $e'); Get.snackbar('错误', '启动导航失败'); diff --git a/lib/modules/translation/views/dialogs/delete_confirm_dialog.dart b/lib/modules/translation/views/dialogs/delete_confirm_dialog.dart new file mode 100644 index 000000000..dbbd2b2fb --- /dev/null +++ b/lib/modules/translation/views/dialogs/delete_confirm_dialog.dart @@ -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), + ), + ), + ); + } +} diff --git a/lib/modules/translation/views/dialogs/translation_long_press_menu.dart b/lib/modules/translation/views/dialogs/translation_long_press_menu.dart new file mode 100644 index 000000000..7463c6f1f --- /dev/null +++ b/lib/modules/translation/views/dialogs/translation_long_press_menu.dart @@ -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 _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, + ), + ); + }); + } +} diff --git a/lib/modules/translation/views/translation_view.dart b/lib/modules/translation/views/translation_view.dart index a5b21aa2a..28888303e 100644 --- a/lib/modules/translation/views/translation_view.dart +++ b/lib/modules/translation/views/translation_view.dart @@ -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'; @@ -16,6 +18,7 @@ import 'widgets/face_to_face_layout.dart'; // 面对面翻译布局 import 'widgets/draggable_floating_button.dart'; // 可拖拽的悬浮按钮 import 'dialogs/language_selection_dialog.dart'; // 语言选择对话框 import 'dialogs/translation_mode_selector.dart'; // 翻译模式选择器 +import 'dialogs/translation_long_press_menu.dart'; // 长按菜单 // import 'package:intl/intl.dart'; @@ -414,103 +417,113 @@ class TranslationView extends GetView { Widget _buildTranslationItemWidget( BuildContext context, TranslationItem item) { - return Container( - margin: const EdgeInsets.only(bottom: 8), - padding: const EdgeInsets.all(12), - decoration: BoxDecoration( - color: item.isIntermediate - ? Colors.grey.withOpacity(0.1) // 中间结果使用浅灰色底色 - : (item.sourceLanguageCode == controller.sourceLanguageCode.value - ? Theme.of(context) - .colorScheme - .primary - .withOpacity(0.08) // 源语言匹配时使用主题色底色 - : Theme.of(context) - .colorScheme - .secondary - .withOpacity(0.08)), // 否则使用次要色底色 - borderRadius: BorderRadius.circular(12), - border: Border.all( + 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( color: item.isIntermediate - ? Colors.grey.withOpacity(0.2) - : Colors.transparent, - width: 1, + ? Colors.grey.withOpacity(0.1) // 中间结果使用浅灰色底色 + : (item.sourceLanguageCode == controller.sourceLanguageCode.value + ? 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( - 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, + 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.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.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.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, + ], + // 语言信息和时间戳 + 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, + ), + ), + ), + ], + ), + ], + ), ), ); } @@ -874,6 +887,24 @@ class TranslationView extends GetView { }); } + /// 处理翻译条目长按事件 + void _onLongPressTranslationItem( + BuildContext context, + bool isDarkMode, + LongPressStartDetails details, + TranslationItem item, + ) { + TranslationLongPressMenu.show( + context: context, + isDarkMode: isDarkMode, + details: details, + item: item, + onCopy: (item) => TranslationLongPressMenu.copyTranslationItem(item), + onDeleteItem: (item) => _deleteTranslationItem(item), + onClearHistory: _showClearHistoryConfirmDialog, + ); + } + // 显示删除单条记录确认对话框 void _showDeleteItemConfirmDialog(TranslationItem item) { final isDarkMode = Theme.of(Get.context!).brightness == Brightness.dark; @@ -1006,6 +1037,62 @@ class TranslationView extends GetView { } } + // 显示删除所有记录确认对话框 + void _showClearHistoryConfirmDialog() { + 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: () { + // 清除历史记录 + controller.clearTranslationHistory(); + Get.back(); + }, + child: Text( + 'confirm'.tr, + style: TextStyle( + fontSize: 16.sp, + color: Colors.red, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12.r), + ), + ), + ); + } + // 构建语言选择栏 Widget _buildLanguageSelectionBar(bool isDarkMode) { return Container( diff --git a/local_plugins/location_service/android/src/main/kotlin/com/yunqiinnovation/location_service/LocationService.kt b/local_plugins/location_service/android/src/main/kotlin/com/yunqiinnovation/location_service/LocationService.kt index b85738b1f..5e051d418 100644 --- a/local_plugins/location_service/android/src/main/kotlin/com/yunqiinnovation/location_service/LocationService.kt +++ b/local_plugins/location_service/android/src/main/kotlin/com/yunqiinnovation/location_service/LocationService.kt @@ -270,13 +270,19 @@ object LocationService { if (!hasForegroundPermission) { return 1 // 前台权限未授予 } - + // 前台权限已授予,再根据前后台状态检查后台权限 if (!isAppInForeground(context)) { - // 应用在后台:Android 10+ 需要检查后台权限 - return 2 // 后台权限未授予 + // 应用在后台:Android 10+ 需要检查后台权限的实际授予状态 + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + val hasBackgroundPermission = hasBackgroundLocationPermission(context) + if (!hasBackgroundPermission) { + return 2 // 后台权限未授予 + } + } + // Android 9 及以下或已授予后台权限 } - + // 所有所需权限均已授予 return 0 }