From fc01d8909f4a6504e43d77f26d6570b8d7167ec2 Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Fri, 19 Sep 2025 14:52:22 +0800 Subject: [PATCH 01/55] =?UTF-8?q?feat(splash):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E9=9A=90=E7=A7=81=E6=94=BF=E7=AD=96=E5=90=8C=E6=84=8F=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E5=8F=8A=E7=95=8C=E9=9D=A2=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在闪屏页添加隐私政策同意功能,包括状态管理、存储和界面组件。用户需要同意隐私政策才能继续使用应用,并可通过复选框和链接查看详细内容。 --- .../splash/controllers/splash_controller.dart | 16 +++ lib/modules/splash/views/splash_view.dart | 115 ++++++++++++++++++ 2 files changed, 131 insertions(+) diff --git a/lib/modules/splash/controllers/splash_controller.dart b/lib/modules/splash/controllers/splash_controller.dart index 4da069bc9..d629117f2 100644 --- a/lib/modules/splash/controllers/splash_controller.dart +++ b/lib/modules/splash/controllers/splash_controller.dart @@ -27,12 +27,20 @@ class SplashController extends GetxController { final _loadingProgress = 0.0.obs; double get loadingProgress => _loadingProgress.value; + // 隐私政策同意状态 - 新增 + final _hasAcceptedPrivacy = false.obs; + RxBool get hasAcceptedPrivacy => _hasAcceptedPrivacy; + // 使用一个反应式变量来决定是否可以导航,替代所有布尔标记 final _canNavigate = false.obs; @override void onInit() { super.onInit(); + // 初始化隐私政策状态 + _hasAcceptedPrivacy.value = + _storage.read('hasAcceptedPrivacy') ?? false; + // 监听 _canNavigate 状态,一旦为 true,立即且仅执行一次导航 once(_canNavigate, (_) => _navigateToTargetPage()); // 启动整个初始化流程 @@ -59,10 +67,17 @@ class SplashController extends GetxController { _canNavigate.value = true; } + /// 切换隐私政策同意状态 - 新增方法 + void togglePrivacyAcceptance() { + _hasAcceptedPrivacy.value = !_hasAcceptedPrivacy.value; + _storage.write('hasAcceptedPrivacy', _hasAcceptedPrivacy.value); + } + /// 检查隐私政策,如果未同意,则显示弹窗并等待用户操作 /// 返回用户是否同意 Future _checkAndRequestPrivacy() async { if (_storage.read('hasAcceptedPrivacy') ?? false) { + _hasAcceptedPrivacy.value = true; return true; } @@ -71,6 +86,7 @@ class SplashController extends GetxController { PrivacyPolicyDialog.show( onAccept: () { _storage.write('hasAcceptedPrivacy', true); + _hasAcceptedPrivacy.value = true; Get.back(); // 关闭弹窗 if (!completer.isCompleted) completer.complete(true); }, diff --git a/lib/modules/splash/views/splash_view.dart b/lib/modules/splash/views/splash_view.dart index 815d11f6f..71457e362 100644 --- a/lib/modules/splash/views/splash_view.dart +++ b/lib/modules/splash/views/splash_view.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import '../controllers/splash_controller.dart'; +import '../../common/webview_page.dart'; class SplashView extends GetView { const SplashView({Key? key}) : super(key: key); @@ -39,6 +40,14 @@ class SplashView extends GetView { ), ), + // 隐私政策组件 - 添加在底部 + Positioned( + bottom: 90.h, + left: 0, + right: 0, + child: _buildTermsAndPrivacy(isDarkMode), + ), + // 版本号 //_buildVersionInfo(isDarkMode), ], @@ -206,6 +215,112 @@ class SplashView extends GetView { ); } + // 用户协议和隐私政策 + Widget _buildTermsAndPrivacy(bool isDarkMode) { + return Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + // 将勾选框和文本包装在 GestureDetector 中 + GestureDetector( + onTap: () => controller.togglePrivacyAcceptance(), + behavior: HitTestBehavior.opaque, // 确保透明区域也能响应点击 + child: Row( + children: [ + SizedBox( + width: 12.w, + height: 16.w, + child: Transform.scale( + scale: 0.7, + child: Obx(() => Checkbox( + value: controller.hasAcceptedPrivacy.value, + onChanged: (value) => controller + .hasAcceptedPrivacy.value = value ?? false, + activeColor: const Color(0xFF3B82F6), + materialTapTargetSize: + MaterialTapTargetSize.shrinkWrap, + visualDensity: VisualDensity.compact, + side: BorderSide( + width: 1.6, + color: Colors.grey[600]!, + ), + )), + ), + ), + SizedBox(width: 4.w), + Text( + 'expressConsent'.tr, // 登录/注册即表示你同意 + style: TextStyle( + fontSize: 13.sp, + color: isDarkMode ? Colors.grey[400] : Colors.grey[700], + ), + ), + ], + ), + ), + ], + ), + //SizedBox(height: 4.h), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + GestureDetector( + onTap: () { + String url = 'https://www.voitrans.net/doc/yhxy.html'; // 用户协议链接 + if (Get.locale?.languageCode != 'zh' || + Get.locale?.countryCode != 'CN') { + url = 'https://www.voitrans.net/doc/yhxy_en.html'; // 用户协议英文链接 + } + Get.to(() => WebViewPage( + url: url, + title: 'userAgreement'.tr, + )); + }, + child: Text( + 'userAgreement'.tr, // 《用户协议》 + style: TextStyle( + fontSize: 12.sp, + color: const Color(0xFF3B82F6), + ), + ), + ), + Text( + 'and'.tr, // 和 + style: TextStyle( + fontSize: 12.sp, + color: isDarkMode ? Colors.grey[500] : Colors.grey[600], + ), + ), + GestureDetector( + onTap: () { + String url = 'https://www.voitrans.net/doc/ysxy.html'; // 隐私政策链接 + + if (Get.locale?.languageCode != 'zh' || + Get.locale?.countryCode != 'CN') { + url = 'https://www.voitrans.net/doc/ysxy_en.html'; // 隐私政策英文链接 + } + + Get.to(() => WebViewPage( + url: url, + title: 'privacyPolicy'.tr, + )); + }, + child: Text( + 'privacyPolicy'.tr, // 《隐私政策》 + style: TextStyle( + fontSize: 12.sp, + color: const Color(0xFF3B82F6), + ), + ), + ), + ], + ), + ], + ); + } + // 版本信息 // 暂时不需要版本号显示,保留代码以备将来使用 // Widget _buildVersionInfo(bool isDarkMode) { From f688de9b582a9b9f4f60e373b42e24c7d30e7ae3 Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Fri, 19 Sep 2025 17:39:52 +0800 Subject: [PATCH 02/55] =?UTF-8?q?feat(=E9=85=8D=E5=AF=B9=E6=8E=A7=E5=88=B6?= =?UTF-8?q?=E5=99=A8):=20=E6=B7=BB=E5=8A=A0=E5=8D=8E=E4=B8=BA=E8=AE=BE?= =?UTF-8?q?=E5=A4=87=E7=89=B9=E6=AE=8A=E5=A4=84=E7=90=86=EF=BC=9A=E5=8D=8E?= =?UTF-8?q?=E4=B8=BA=E8=AE=BE=E5=A4=87=E9=A6=96=E6=AC=A1=E6=A3=80=E6=9F=A5?= =?UTF-8?q?=E6=9D=83=E9=99=90=E8=A2=AB=E6=8B=92=E7=BB=9D=E5=90=8E=E7=BB=AD?= =?UTF-8?q?=E4=B8=8D=E5=86=8D=E8=A7=A6=E5=8F=91=E6=9D=83=E9=99=90=E7=94=B3?= =?UTF-8?q?=E8=AF=B7=EF=BC=8C=E7=9B=B4=E6=8E=A5=E6=8F=90=E7=A4=BA=E6=89=8B?= =?UTF-8?q?=E5=8A=A8=E5=BC=80=E5=90=AF=E6=9D=83=E9=99=90=EF=BC=9B=20?= =?UTF-8?q?=E5=85=B6=E5=AE=83=E5=93=81=E7=89=8C=E8=AE=BE=E5=A4=87=E4=B8=8D?= =?UTF-8?q?=E5=8F=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controllers/pairing_controller.dart | 44 ++++++++++++++++--- 1 file changed, 39 insertions(+), 5 deletions(-) diff --git a/lib/modules/pairing/controllers/pairing_controller.dart b/lib/modules/pairing/controllers/pairing_controller.dart index b3ff77b52..e1bd5f96d 100644 --- a/lib/modules/pairing/controllers/pairing_controller.dart +++ b/lib/modules/pairing/controllers/pairing_controller.dart @@ -1,20 +1,25 @@ +import 'dart:io'; +import 'dart:async'; + import 'package:app_settings/app_settings.dart'; import 'package:voitrans/data/models/appconfig_model.dart'; -import '/data/models/appconfig.dart'; -import '/data/models/user_Info.dart'; -import '/data/services/network/api.dart'; import 'package:flutter/material.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; import 'package:get/get.dart'; -import 'dart:async'; import 'package:permission_handler/permission_handler.dart'; import 'package:url_launcher/url_launcher.dart'; +import 'package:get_storage/get_storage.dart'; +import 'package:device_info_plus/device_info_plus.dart'; + import '../../../routes/app_routes.dart'; import '../../../core/utils/logger.dart'; -import 'dart:io'; +import '/data/models/appconfig.dart'; +import '/data/models/user_Info.dart'; +import '/data/services/network/api.dart'; import '../../../data/services/bluetooth_manager.dart'; import '../../../data/services/ble_manager.dart'; import '../../../core/utils/permission_util.dart'; +import '../../../data/services/version_update_service.dart'; /// 配对状态枚举 - 简化版本 enum PairingState { @@ -48,6 +53,7 @@ enum ErrorType { class PairingController extends GetxController with WidgetsBindingObserver { static const String _tag = 'PairingCtrl'; static PairingController get to => Get.find(); + final _storage = GetStorage(); // ========== 状态管理 ========== final _currentState = PairingState.idle.obs; @@ -96,6 +102,12 @@ class PairingController extends GetxController with WidgetsBindingObserver { // Public getter for view access BluetoothDevice? get currentDevice => _currentClassicDevice; + //华为设备权限检查标记 + bool isHuaweiHasChecked = false; + // 缓存的设备品牌信息 + final versionService = Get.find(); + bool isHuaweiDevice = false; + @override void onInit() { super.onInit(); @@ -106,6 +118,8 @@ class PairingController extends GetxController with WidgetsBindingObserver { WidgetsBinding.instance.addObserver(this); Logger.i(_tag, '应用生命周期监听器注册完成'); + isHuaweiDevice = _isHuaweiDevice(); + // 确保服务已初始化 _bluetoothManager = Get.find(); _bleManager = Get.find(); @@ -113,6 +127,11 @@ class PairingController extends GetxController with WidgetsBindingObserver { // 集中设置所有监听器 _setupAllListeners(); + if (isHuaweiDevice) { + //检查华为设备是否检查权限检查过 + isHuaweiHasChecked = _storage.read('isHuaweiHasChecked') ?? false; + } + // 开始初始状态检查 // 延迟稍长时间,确保BluetoothManager有足够时间获取已连接设备 Future.delayed(const Duration(milliseconds: 300), () { @@ -548,8 +567,20 @@ class PairingController extends GetxController with WidgetsBindingObserver { return _bluetoothManager.bluetoothState != 'off'; } + /// 检查是否为华为设备 + bool _isHuaweiDevice() { + final deviceBrand = versionService.cachedDeviceBrand; + return deviceBrand.isNotEmpty && + (deviceBrand.contains('huawei') || deviceBrand.contains('华为')); + } + /// 检查权限 Future _checkPermissions() async { + //如果是华为设备,且已经检查过,直接返回false + if (isHuaweiDevice && isHuaweiHasChecked) { + return false; + } + // Android权限检查 if (Platform.isAndroid) { Logger.i(_tag, '开始检查Android权限'); @@ -575,6 +606,9 @@ class PairingController extends GetxController with WidgetsBindingObserver { permanentDenialText: 'locationPermanentDenial'.tr, ); + if (isHuaweiDevice) { + _storage.write('isHuaweiHasChecked', true); + } return bluetoothScanGranted && bluetoothConnectGranted && locationGranted; } return true; From e79fde408513a1661dfe4b601eb4c9d14791bfc2 Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Fri, 19 Sep 2025 19:34:30 +0800 Subject: [PATCH 03/55] =?UTF-8?q?=E4=B8=8A=E4=BC=A0qq=E9=9F=B3=E4=B9=90?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- ios/Runner/Info.plist | 3 + .../chat_api/QQMusicSystemFunctionHandler.kt | 87 +++++++------------ .../chat_api/MusicSystemFunctionHandler.swift | 1 + .../QQMusicSystemFunctionHandler.swift | 1 + .../qq_music/QQMusicLoginManager.kt | 70 ++++++++++----- .../qq_music/QQMusicSingleton.kt | 3 +- 6 files changed, 87 insertions(+), 78 deletions(-) diff --git a/ios/Runner/Info.plist b/ios/Runner/Info.plist index 1087023b6..ec7521c06 100644 --- a/ios/Runner/Info.plist +++ b/ios/Runner/Info.plist @@ -91,6 +91,9 @@ google.navigation googlemaps comgooglemaps + baidumap + amapuri + maps LSRequiresIPhoneOS diff --git a/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/QQMusicSystemFunctionHandler.kt b/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/QQMusicSystemFunctionHandler.kt index db3684402..a820c23d3 100644 --- a/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/QQMusicSystemFunctionHandler.kt +++ b/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/QQMusicSystemFunctionHandler.kt @@ -124,7 +124,26 @@ private class QQMusicSearchHandler(private val context: Context?) : FunctionHand qqMusicSingleton.searchSongs(keyword) { searchResult -> val result = if (searchResult.isSuccess) { val songs = searchResult.getOrNull() ?: emptyList() - + var playError: Throwable? = null + if (songs.isNotEmpty()) { + // 从搜索结果中提取歌曲MID列表 + val songMids = songs.mapNotNull { song -> + val songMap = song as Map + songMap["mid"] as? String + }.filter { it.isNotEmpty() } + + if (songMids.isNotEmpty()) { + // 播放搜索到的歌曲列表,从第一首开始播放 + qqMusicSingleton.playSongs(songMids, 0) { playResult -> + Log.d("QQMusicSearchHandler", "播放结果: ${playResult.isSuccess}") + if (!playResult.isSuccess) { + playError = playResult.exceptionOrNull() +// Log.e("QQMusicSearchHandler", "播放失败: ${playError?.message}") + } + } + } + } + // 根据搜索类型处理结果 val processedSongs = when (searchType) { 0 -> { // 歌曲搜索,返回所有结果 @@ -148,7 +167,18 @@ private class QQMusicSearchHandler(private val context: Context?) : FunctionHand val duration = songMap["duration"] ?: 0 "{\"mid\": \"$mid\", \"name\": \"$name\", \"artist\": \"$artist\", \"album\": \"$album\", \"duration\":$duration,\"pic\": \"$pic\"}" } - "{\"success\": true,\"iscard\": true, \"broadcast\": false, \"message\": \"搜索成功,正在播放\", \"data\": [$songsJson]}" + + if (songs.isNotEmpty()) { + if (playError == null){ + "{\"success\": true, \"iscard\": true, \"broadcast\": false, \"message\": \"搜索成功,正在播放\", \"data\": [$songsJson]}" + }else{ + Log.e("QQMusicSearchHandler", "播放失败: ${playError?.message}") + "{\"success\": false, \"iscard\": true, \"broadcast\": true, \"message\": \"${playError?.message}\", \"data\": [$songsJson]}" + } + + } else { + "{\"success\": false, \"message\": \"未找到相关歌曲\"}" + } } else { val error = searchResult.exceptionOrNull() "{\"success\": false, \"message\": \"搜索失败: ${error?.message}\"}" @@ -163,59 +193,6 @@ private class QQMusicSearchHandler(private val context: Context?) : FunctionHand } } } -//private class QQMusicSearchHandler(private val context: Context?) : FunctionHandler { -// override suspend fun handle(arguments: Map): String { -// return try { -// val keyword = arguments["keyword"] as? String ?: return "{\"success\": false, \"message\": \"缺少搜索关键词\"}" -// val searchType = arguments["searchType"] as? Int ?: 0 // 默认搜索歌曲 -// val qqMusicSingleton = QQMusicSingleton.getInstance() -// var result = "" -// -// // 直接调用搜索,内部会自动处理登录检查 -// qqMusicSingleton.searchSongs(keyword) { searchResult -> -// result = if (searchResult.isSuccess) { -// val songs = searchResult.getOrNull() ?: emptyList() -// -// // 根据搜索类型处理结果 -// val processedSongs = when (searchType) { -// 0 -> { // 歌曲搜索,只返回第一个结果 -//// if (songs.isNotEmpty()) listOf(songs.first()) else emptyList() -// songs -// } -// else -> { // 专辑、歌手等其他类型,返回所有结果 -// songs -// } -// } -// -// val songsJson = processedSongs.joinToString(",") { song -> -// val songMap = song as Map -// // 添加调试日志 -// Log.d("QQMusicSearchHandler", "原始数据: $songMap") -// // 修正字段名映射 -// val mid = songMap["mid"] ?: "unknown" -// val name = songMap["name"] ?: "unknown" -// val artist = songMap["singer"] ?: "unknown" -// val album = songMap["album"] ?: "" -// val pic = songMap["pic"] ?: "" -// val duration = songMap["duration"] ?: 0 -// "{\"mid\": \"$mid\", \"name\": \"$name\", \"artist\": \"$artist\", \"album\": \"$album\", \"duration\":$duration,\"pic\": \"$pic\"}" -// } -// "{\"success\": true, \"broadcast\": false, \"message\": \"搜索成功,增在播放\", \"data\": [$songsJson]}" -// } else { -// val error = searchResult.exceptionOrNull() -// "{\"success\": false, \"message\": \"搜索失败: ${error?.message}\"}" -// } -// } -// -// // 等待回调完成 -// delay(5000) -// result.ifEmpty { "{\"success\": false, \"message\": \"搜索超时\"}" } -// } catch (e: Exception) { -// Log.e("QQMusicSearchHandler", "QQ音乐搜索失败", e) -// "{\"success\": false, \"message\": \"搜索异常: ${e.message}\"}" -// } -// } -//} private class QQMusicPlaySongsHandler(private val context: Context?) : FunctionHandler { override suspend fun handle(arguments: Map): String { diff --git a/local_plugins/chat_api/ios/chat_api/Sources/chat_api/MusicSystemFunctionHandler.swift b/local_plugins/chat_api/ios/chat_api/Sources/chat_api/MusicSystemFunctionHandler.swift index c2e2c3409..9d65f4fdd 100644 --- a/local_plugins/chat_api/ios/chat_api/Sources/chat_api/MusicSystemFunctionHandler.swift +++ b/local_plugins/chat_api/ios/chat_api/Sources/chat_api/MusicSystemFunctionHandler.swift @@ -67,6 +67,7 @@ private class MusicSearchHandler: FunctionHandler { let result: [String: Any] = [ "success": true, "broadcast": false, + "iscard": true, "message": "搜索成功,正在播放", "data": songsJson ] diff --git a/local_plugins/chat_api/ios/chat_api/Sources/chat_api/QQMusicSystemFunctionHandler.swift b/local_plugins/chat_api/ios/chat_api/Sources/chat_api/QQMusicSystemFunctionHandler.swift index c8c3144aa..2558ca63b 100644 --- a/local_plugins/chat_api/ios/chat_api/Sources/chat_api/QQMusicSystemFunctionHandler.swift +++ b/local_plugins/chat_api/ios/chat_api/Sources/chat_api/QQMusicSystemFunctionHandler.swift @@ -162,6 +162,7 @@ private class QQMusicSearchHandler: FunctionHandler { result = [ "success": true, "message": "Found \(items.count) songs", + "iscard": true, "broadcast": false, "data": songs ] diff --git a/local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/QQMusicLoginManager.kt b/local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/QQMusicLoginManager.kt index a9474d997..396c6cf76 100644 --- a/local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/QQMusicLoginManager.kt +++ b/local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/QQMusicLoginManager.kt @@ -1,6 +1,7 @@ package com.yunqiinnovation.qq_music import android.app.Activity +import android.app.ActivityManager import android.content.Context import android.os.Bundle import android.os.Handler @@ -18,12 +19,12 @@ import org.json.JSONTokener * 负责处理QQ音乐的登录授权流程 */ class QQMusicLoginManager(private val context: Context) { - + companion object { private const val TAG = "QQMusicLoginManager" private const val BASE_SCHEME = "voitrans://" private const val URI_LOGIN = BASE_SCHEME + "login" - + // 定义错误码常量,替代 LoginExampleActivity 中的常量 const val LOGIN_FAILED = 1 } @@ -32,7 +33,7 @@ class QQMusicLoginManager(private val context: Context) { private var qqMusicApi: IQQMusicApi? = null private lateinit var qqMusicApiWrapper: QQMusicApiWrapper private val handler = Handler(Looper.getMainLooper()) - + /** * 登录结果回调接口 */ @@ -51,57 +52,57 @@ class QQMusicLoginManager(private val context: Context) { */ fun startLogin(callback: LoginCallback) { Log.d(TAG, "开始QQ音乐登录流程") - + // 检查配置 if (Config.OPENID_APPID.isEmpty() || Config.OPENID_APP_PRIVATE_KEY.isEmpty()) { callback.onError("CONFIG_ERROR", "请先配置APPID和私钥") return } - + // 初始化绑定助手 qPlayBindHelper = QPlayBindHelper(context) - + // 开始绑定QQ音乐服务 bindQQMusicService(callback) } - + /** * 绑定QQ音乐服务 */ private fun bindQQMusicService(callback: LoginCallback) { Log.d(TAG, "开始绑定QQ音乐服务") - + qPlayBindHelper.ensureQQMusicBindByStartProcess { success -> if (success && qPlayBindHelper.isBindQQMusic()) { Log.d(TAG, "QQ音乐服务绑定成功") qqMusicApi = qPlayBindHelper.getQQMusicApi()!! qqMusicApiWrapper = QQMusicApiWrapper(qqMusicApi!!) - + // 检查是否已经授权 checkAuthStatus(callback) - + } else { Log.e(TAG, "QQ音乐服务绑定失败") callback.onError("APP_NOT_INSTALLED", "请先安装QQ音乐App才能使用音乐功能") } } } - + /** * 检查授权状态 */ private fun checkAuthStatus(callback: LoginCallback) { Log.d(TAG, "检查授权状态") - + val bundle = Bundle().apply { putInt(Keys.API_PARAM_KEY_SDK_VERSION, CommonCmd.SDK_VERSION) putString(Keys.API_PARAM_KEY_PLATFORM_TYPE, Config.BIND_PLATFORM) } - + try { val result = qqMusicApi?.execute("hi", bundle) val code = result?.getInt(Keys.API_RETURN_KEY_CODE) - + when (code) { ErrorCodes.ERROR_OK -> { // 已授权,直接请求登录信息 @@ -147,21 +148,46 @@ class QQMusicLoginManager(private val context: Context) { */ fun startAuthProcess2(callback: LoginCallback) { Log.d(TAG, "启动QQ音乐授权流程") - + if (isAppInForeground(context)) { + CommonCmd.loginQQMusic( + context, + context.packageName, + BASE_SCHEME + ) +// Log.d(TAG, "当前是前台服务") + } else { + // CommonCmd.loginQQMusic( + // context, + // context.packageName, + // BASE_SCHEME, + // ) + Log.d(TAG, "当前是后台服务") + } // 启动QQ音乐登录 // CommonCmd.startQQMusicProcess(context,context.packageName) - CommonCmd.loginQQMusic( - context, - context.packageName, - BASE_SCHEME - ) - callback.onSuccess() + isBindQQMusicService = true + + Handler(Looper.getMainLooper()).postDelayed({ + callback.onSuccess() + }, 1000) // 延迟500ms + + // 等待用户登录完成后进行身份验证 // handler.postDelayed({ // verifyIdentity(callback) // }, 3000) // 给用户足够的登录时间 } - + private fun isAppInForeground(context: Context): Boolean { + val activityManager = context.getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager + val appProcesses = activityManager.runningAppProcesses ?: return false + val packageName = context.packageName + for (appProcess in appProcesses) { + if (appProcess.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND && appProcess.processName == packageName) { + return true + } + } + return false + } /** * 启动授权流程 */ diff --git a/local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/QQMusicSingleton.kt b/local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/QQMusicSingleton.kt index 5c87e6c94..c69c781cd 100644 --- a/local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/QQMusicSingleton.kt +++ b/local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/QQMusicSingleton.kt @@ -573,7 +573,8 @@ class QQMusicSingleton private constructor() { musicManager?.bindQQMusicApiService() callback(Result.failure(Exception("播放失败: $errorMessage"))) } else if (errorCode == 1030) { - callback(Result.success(Unit)) + callback(Result.failure(Exception("播放失败: $errorMessage"))) +// callback(Result.success(Unit)) }else{ callback(Result.failure(Exception("播放失败: $errorMessage"))) } From 4198c2e67bbf7af7f9972b7d56ce0d82db410c58 Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Fri, 19 Sep 2025 20:46:40 +0800 Subject: [PATCH 04/55] =?UTF-8?q?feat(translation):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E5=B7=A6=E6=BB=91=E6=89=8B=E5=8A=BF=E5=AF=BC=E8=88=AA=E8=87=B3?= =?UTF-8?q?=E5=8E=86=E5=8F=B2=E8=AE=B0=E5=BD=95=E9=A1=B5=E9=9D=A2=20refact?= =?UTF-8?q?or(translation):=20=E7=A7=BB=E9=99=A4=E7=81=AB=E5=B1=B1AI?= =?UTF-8?q?=E6=91=98=E8=A6=81=E7=94=9F=E6=88=90=E7=9B=B8=E5=85=B3=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=20fix(pairing):=20=E4=BF=AE=E6=AD=A3=E5=8D=8E?= =?UTF-8?q?=E4=B8=BA=E8=AE=BE=E5=A4=87=E6=9D=83=E9=99=90=E6=A3=80=E6=9F=A5?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controllers/pairing_controller.dart | 10 +- .../translation_detail_controller.dart | 162 ++++---- .../views/translation_detail_view.dart | 352 +++++++++--------- .../translation/views/translation_view.dart | 45 ++- 4 files changed, 295 insertions(+), 274 deletions(-) diff --git a/lib/modules/pairing/controllers/pairing_controller.dart b/lib/modules/pairing/controllers/pairing_controller.dart index e1bd5f96d..60543dc64 100644 --- a/lib/modules/pairing/controllers/pairing_controller.dart +++ b/lib/modules/pairing/controllers/pairing_controller.dart @@ -576,9 +576,15 @@ class PairingController extends GetxController with WidgetsBindingObserver { /// 检查权限 Future _checkPermissions() async { - //如果是华为设备,且已经检查过,直接返回false + //如果是华为设备,且已经检查过,直接返回权限状态 if (isHuaweiDevice && isHuaweiHasChecked) { - return false; + final bluetoothScanStatus = await Permission.bluetoothScan.status; + final bluetoothConnectStatus = await Permission.bluetoothConnect.status; + final locationStatus = await Permission.locationWhenInUse.status; + + return bluetoothScanStatus.isGranted && + bluetoothConnectStatus.isGranted && + locationStatus.isGranted; } // Android权限检查 diff --git a/lib/modules/translation/controllers/translation_detail_controller.dart b/lib/modules/translation/controllers/translation_detail_controller.dart index fd3239f0d..da34f3ff2 100644 --- a/lib/modules/translation/controllers/translation_detail_controller.dart +++ b/lib/modules/translation/controllers/translation_detail_controller.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import 'package:get/get.dart'; -import '../../../data/services/volcano_ai_service.dart'; +// import '../../../data/services/volcano_ai_service.dart';// 注释掉火山AI服务导入 import '../controllers/translation_controller.dart'; import 'translation_audiohistory_controller.dart'; import '../models/translation_models.dart'; // 添加这行导入 @@ -12,7 +12,7 @@ import '../../../data/services/translation_usage_service.dart'; //详情页管理 class TranslationDetailController extends GetxController { // 依赖注入 - final VolcanoAIService _aiService = Get.find(); + //final VolcanoAIService _aiService = Get.find(); final GetStorage _storage = GetStorage(); late final TranslationUsageService _usageService; @@ -124,89 +124,89 @@ class TranslationDetailController extends GetxController { sessionItems.map((e) => e.translatedText).join('\n\n'); // 使用延长的超时时间重试提取摘要 - Future retryExtractSummaryWithExtendedTimeout(String text) async { - return _extractSummaryInternal(text, _extendedTimeoutDuration); - } + // Future retryExtractSummaryWithExtendedTimeout(String text) async { + // return _extractSummaryInternal(text, _extendedTimeoutDuration); + // } // 提取摘要 - Future extractSummary(String text) async { - return _extractSummaryInternal(text, _defaultTimeoutDuration); - } + // Future extractSummary(String text) async { + // return _extractSummaryInternal(text, _defaultTimeoutDuration); + // } // 内部摘要提取方法,避免代码重复 - Future _extractSummaryInternal( - String text, int timeoutDuration) async { - if (text.isEmpty) { - return '无法生成摘要:文本为空'; - } - - isGeneratingSummary.value = true; - - try { - // 构建提示词 - final messages = [ - {'role': 'user', 'content': '请为以下文本生成一个简洁的摘要,突出重点内容:\n\n$text'}, - ]; - - // 系统提示词 - const systemPrompt = ''' -你是一个专业的文本摘要助手,擅长从会议记录、对话和文本中提取关键信息并生成简洁的摘要。 -请按照以下格式生成摘要: - -1. 首先用1-2句话对整体内容进行简要概括 -2. 然后使用项目符号列表(•)列出关键点或重要信息 -3. 如果是对话内容,请特别标注出讨论的结论或达成的共识 - -请遵循以下原则: -1. 保留原文的核心信息和关键点 -2. 使用简洁、清晰的语言 -3. 保持客观,不添加个人观点 -4. 摘要总长度应该是原文的1/3或更短 -5. 确保列表中的每个要点都简明扼要,突出重点 -'''; - - String result; - try { - // 使用超时限制 - result = await _aiService - .sendMessage( - messages: messages, - systemPrompt: systemPrompt, - ) - .timeout(Duration(seconds: timeoutDuration), onTimeout: () { - throw TimeoutException('请求超时', Duration(seconds: timeoutDuration)); - }); - } on TimeoutException catch (_) { - final timeoutMessage = timeoutDuration == _defaultTimeoutDuration - ? '生成摘要超时,请稍后重试' - : '生成摘要超时,请检查网络连接后重试'; - summaryResult.value = timeoutMessage; - return summaryResult.value; - } - - // 更新结果并保存 - if (result.isNotEmpty) { - summaryResult.value = result; - saveSummary(result); - - // 记录AI摘要API调用统计 - try { - _usageService.recordAiSummaryApiCall(); - } catch (e) { - Logger.error('记录AI摘要统计失败: $e'); - } - } - - return result; - } catch (e) { - final errorMessage = '生成摘要失败: ${e.toString()}'; - Logger.error('TranslationDetailController', errorMessage); - summaryResult.value = errorMessage; - return errorMessage; - } finally { - isGeneratingSummary.value = false; - } - } +// Future _extractSummaryInternal( +// String text, int timeoutDuration) async { +// if (text.isEmpty) { +// return '无法生成摘要:文本为空'; +// } + +// isGeneratingSummary.value = true; + +// try { +// // 构建提示词 +// final messages = [ +// {'role': 'user', 'content': '请为以下文本生成一个简洁的摘要,突出重点内容:\n\n$text'}, +// ]; + +// // 系统提示词 +// const systemPrompt = ''' +// 你是一个专业的文本摘要助手,擅长从会议记录、对话和文本中提取关键信息并生成简洁的摘要。 +// 请按照以下格式生成摘要: + +// 1. 首先用1-2句话对整体内容进行简要概括 +// 2. 然后使用项目符号列表(•)列出关键点或重要信息 +// 3. 如果是对话内容,请特别标注出讨论的结论或达成的共识 + +// 请遵循以下原则: +// 1. 保留原文的核心信息和关键点 +// 2. 使用简洁、清晰的语言 +// 3. 保持客观,不添加个人观点 +// 4. 摘要总长度应该是原文的1/3或更短 +// 5. 确保列表中的每个要点都简明扼要,突出重点 +// '''; + +// String result; +// try { +// // 使用超时限制 +// result = await _aiService +// .sendMessage( +// messages: messages, +// systemPrompt: systemPrompt, +// ) +// .timeout(Duration(seconds: timeoutDuration), onTimeout: () { +// throw TimeoutException('请求超时', Duration(seconds: timeoutDuration)); +// }); +// } on TimeoutException catch (_) { +// final timeoutMessage = timeoutDuration == _defaultTimeoutDuration +// ? '生成摘要超时,请稍后重试' +// : '生成摘要超时,请检查网络连接后重试'; +// summaryResult.value = timeoutMessage; +// return summaryResult.value; +// } + +// // 更新结果并保存 +// if (result.isNotEmpty) { +// summaryResult.value = result; +// saveSummary(result); + +// // 记录AI摘要API调用统计 +// try { +// _usageService.recordAiSummaryApiCall(); +// } catch (e) { +// Logger.error('记录AI摘要统计失败: $e'); +// } +// } + +// return result; +// } catch (e) { +// final errorMessage = '生成摘要失败: ${e.toString()}'; +// Logger.error('TranslationDetailController', errorMessage); +// summaryResult.value = errorMessage; +// return errorMessage; +// } finally { +// isGeneratingSummary.value = false; +// } +// } // 删除单条翻译记录 Future deleteTranslationItem(TranslationItem item) async { diff --git a/lib/modules/translation/views/translation_detail_view.dart b/lib/modules/translation/views/translation_detail_view.dart index e2ec8dfbf..7a10c65da 100644 --- a/lib/modules/translation/views/translation_detail_view.dart +++ b/lib/modules/translation/views/translation_detail_view.dart @@ -142,9 +142,9 @@ class TranslationDetailView extends GetView { if (controller.selectedTabIndex.value == 0) ..._buildOriginalRecords(isDarkMode) else - _buildSummaryWidget(isDarkMode), + //_buildSummaryWidget(isDarkMode), - SizedBox(height: 24.h), + SizedBox(height: 24.h), ], ); }), @@ -323,23 +323,23 @@ class TranslationDetailView extends GetView { } // 构建总结视图 - Widget _buildSummaryWidget(bool isDarkMode) { - final String combinedSourceText = _getCombinedSourceText(); + // Widget _buildSummaryWidget(bool isDarkMode) { + // final String combinedSourceText = _getCombinedSourceText(); - return Column( - children: [ - // 摘要内容容器 - _buildSummaryContainer(isDarkMode), + // return Column( + // children: [ + // // 摘要内容容器 + // _buildSummaryContainer(isDarkMode), - // 提取摘要按钮 - Obx(() => _buildExtractSummaryButton(combinedSourceText, isDarkMode)), + // // 提取摘要按钮 + // Obx(() => _buildExtractSummaryButton(combinedSourceText, isDarkMode)), - // 重新生成按钮 - Obx(() => - _buildRegenerateSummaryButton(combinedSourceText, isDarkMode)), - ], - ); - } + // // 重新生成按钮 + // Obx(() => + // _buildRegenerateSummaryButton(combinedSourceText, isDarkMode)), + // ], + // ); + // } // 构建摘要容器 Widget _buildSummaryContainer(bool isDarkMode) { @@ -484,163 +484,163 @@ class TranslationDetailView extends GetView { } // 构建提取摘要按钮 - Widget _buildExtractSummaryButton( - String combinedSourceText, bool isDarkMode) { - final bool isDisabled = controller.isGeneratingSummary.value || - controller.summaryResult.value.isNotEmpty; - - return Container( - width: double.infinity, - margin: EdgeInsets.only(bottom: 16.h), - child: ElevatedButton( - onPressed: - isDisabled ? null : () => _extractSummary(combinedSourceText), - style: ElevatedButton.styleFrom( - backgroundColor: isDarkMode ? AppColors.primary : Colors.blue, - foregroundColor: Colors.white, - padding: EdgeInsets.symmetric(vertical: 12.h), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16.r), - ), - elevation: 0, - disabledBackgroundColor: controller.isGeneratingSummary.value - ? isDarkMode - ? AppColors.primary.withAlpha(128) - : Colors.blue.withAlpha(128) - : Colors.grey.withAlpha(128), - ), - child: controller.isGeneratingSummary.value - ? _buildGeneratingIndicator(isDarkMode) - : Text( - controller.summaryResult.value.isEmpty - ? 'extractSummary'.tr - : 'summaryGenerated'.tr, // 提取摘要 : 已生成摘要 - style: TextStyle( - fontSize: 16.sp, - fontWeight: FontWeight.w600, - ), - ), - ), - ); - } + // Widget _buildExtractSummaryButton( + // String combinedSourceText, bool isDarkMode) { + // final bool isDisabled = controller.isGeneratingSummary.value || + // controller.summaryResult.value.isNotEmpty; + + // return Container( + // width: double.infinity, + // margin: EdgeInsets.only(bottom: 16.h), + // child: ElevatedButton( + // onPressed: + // isDisabled ? null : () => _extractSummary(combinedSourceText), + // style: ElevatedButton.styleFrom( + // backgroundColor: isDarkMode ? AppColors.primary : Colors.blue, + // foregroundColor: Colors.white, + // padding: EdgeInsets.symmetric(vertical: 12.h), + // shape: RoundedRectangleBorder( + // borderRadius: BorderRadius.circular(16.r), + // ), + // elevation: 0, + // disabledBackgroundColor: controller.isGeneratingSummary.value + // ? isDarkMode + // ? AppColors.primary.withAlpha(128) + // : Colors.blue.withAlpha(128) + // : Colors.grey.withAlpha(128), + // ), + // child: controller.isGeneratingSummary.value + // ? _buildGeneratingIndicator(isDarkMode) + // : Text( + // controller.summaryResult.value.isEmpty + // ? 'extractSummary'.tr + // : 'summaryGenerated'.tr, // 提取摘要 : 已生成摘要 + // style: TextStyle( + // fontSize: 16.sp, + // fontWeight: FontWeight.w600, + // ), + // ), + // ), + // ); + // } // 构建生成中指示器 - Widget _buildGeneratingIndicator(bool isDarkMode) { - return Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - SizedBox( - width: 20.w, - height: 20.h, - child: CircularProgressIndicator( - color: Colors.white, - strokeWidth: 2.0, - ), - ), - SizedBox(width: 12.w), - Text( - 'generating'.tr, // 生成中... - style: TextStyle( - fontSize: 16.sp, - fontWeight: FontWeight.w600, - ), - ), - ], - ); - } + // Widget _buildGeneratingIndicator(bool isDarkMode) { + // return Row( + // mainAxisAlignment: MainAxisAlignment.center, + // children: [ + // SizedBox( + // width: 20.w, + // height: 20.h, + // child: CircularProgressIndicator( + // color: Colors.white, + // strokeWidth: 2.0, + // ), + // ), + // SizedBox(width: 12.w), + // Text( + // 'generating'.tr, // 生成中... + // style: TextStyle( + // fontSize: 16.sp, + // fontWeight: FontWeight.w600, + // ), + // ), + // ], + // ); + // } // 构建重新生成按钮 - Widget _buildRegenerateSummaryButton( - String combinedSourceText, bool isDarkMode) { - if (controller.summaryResult.value.isEmpty || - controller.isGeneratingSummary.value) { - return SizedBox.shrink(); - } - - return Container( - width: double.infinity, - margin: EdgeInsets.only(bottom: 16.h), - child: OutlinedButton( - onPressed: () => _extractSummary(combinedSourceText), - style: OutlinedButton.styleFrom( - foregroundColor: isDarkMode ? AppColors.primary : Colors.blue, - side: BorderSide(color: isDarkMode ? AppColors.primary : Colors.blue), - padding: EdgeInsets.symmetric(vertical: 12.h), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16.r), - ), - ), - child: Text( - 'regenerate'.tr, // 重新生成 - style: TextStyle( - fontSize: 16.sp, - fontWeight: FontWeight.w600, - ), - ), - ), - ); - } + // Widget _buildRegenerateSummaryButton( + // String combinedSourceText, bool isDarkMode) { + // if (controller.summaryResult.value.isEmpty || + // controller.isGeneratingSummary.value) { + // return SizedBox.shrink(); + // } + + // return Container( + // width: double.infinity, + // margin: EdgeInsets.only(bottom: 16.h), + // child: OutlinedButton( + // onPressed: () => _extractSummary(combinedSourceText), + // style: OutlinedButton.styleFrom( + // foregroundColor: isDarkMode ? AppColors.primary : Colors.blue, + // side: BorderSide(color: isDarkMode ? AppColors.primary : Colors.blue), + // padding: EdgeInsets.symmetric(vertical: 12.h), + // shape: RoundedRectangleBorder( + // borderRadius: BorderRadius.circular(16.r), + // ), + // ), + // child: Text( + // 'regenerate'.tr, // 重新生成 + // style: TextStyle( + // fontSize: 16.sp, + // fontWeight: FontWeight.w600, + // ), + // ), + // ), + // ); + // } // 提取摘要方法 - void _extractSummary(String text) async { - final result = await controller.extractSummary(text); + // void _extractSummary(String text) async { + // final result = await controller.extractSummary(text); - // 检查是否超时,如果超时则显示重试选项 - if (result.contains('超时')) { - _showTimeoutDialog(text); - } - } + // // 检查是否超时,如果超时则显示重试选项 + // if (result.contains('超时')) { + // _showTimeoutDialog(text); + // } + // } // 显示超时对话框 - void _showTimeoutDialog(String text) { - Get.dialog( - AlertDialog( - title: Text( - 'requestTimeout'.tr, // 请求超时 - style: TextStyle( - fontSize: 18.sp, - fontWeight: FontWeight.bold, - ), - ), - content: Text( - 'summaryTimeoutMessage'.tr, // 生成摘要请求超时,可能是网络问题或服务器繁忙。 - style: TextStyle( - fontSize: 16.sp, - ), - ), - actions: [ - TextButton( - onPressed: () => Get.back(), - child: Text( - 'cancel'.tr, // 取消 - style: TextStyle( - fontSize: 16.sp, - color: Colors.grey[700], - ), - ), - ), - TextButton( - onPressed: () { - Get.back(); - // 使用延长的超时时间重试 - controller.retryExtractSummaryWithExtendedTimeout(text); - }, - child: Text( - 'retry'.tr, // 重试 - style: TextStyle( - fontSize: 16.sp, - color: Colors.blue, - fontWeight: FontWeight.bold, - ), - ), - ), - ], - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12.r), - ), - ), - ); - } + // void _showTimeoutDialog(String text) { + // Get.dialog( + // AlertDialog( + // title: Text( + // 'requestTimeout'.tr, // 请求超时 + // style: TextStyle( + // fontSize: 18.sp, + // fontWeight: FontWeight.bold, + // ), + // ), + // content: Text( + // 'summaryTimeoutMessage'.tr, // 生成摘要请求超时,可能是网络问题或服务器繁忙。 + // style: TextStyle( + // fontSize: 16.sp, + // ), + // ), + // actions: [ + // TextButton( + // onPressed: () => Get.back(), + // child: Text( + // 'cancel'.tr, // 取消 + // style: TextStyle( + // fontSize: 16.sp, + // color: Colors.grey[700], + // ), + // ), + // ), + // TextButton( + // onPressed: () { + // Get.back(); + // // 使用延长的超时时间重试 + // controller.retryExtractSummaryWithExtendedTimeout(text); + // }, + // child: Text( + // 'retry'.tr, // 重试 + // style: TextStyle( + // fontSize: 16.sp, + // color: Colors.blue, + // fontWeight: FontWeight.bold, + // ), + // ), + // ), + // ], + // shape: RoundedRectangleBorder( + // borderRadius: BorderRadius.circular(12.r), + // ), + // ), + // ); + // } // 显示删除单条记录确认对话框 void _showDeleteItemConfirmDialog(TranslationItem item) { @@ -750,18 +750,18 @@ class TranslationDetailView extends GetView { '${'originalText'.tr}:\n$sourceText\n\n${'translatedText'.tr}:\n$translatedText'; // 原文:译文: } else { // 分享摘要 - if (controller.summaryResult.value.isNotEmpty) { - shareText = - '${'sessionSummary'.tr}:\n${controller.summaryResult.value}'; // 会话摘要: - } else { - Get.snackbar( - 'cannotShare'.tr, // 无法分享 - 'generateSummaryFirst'.tr, // 请先生成摘要 - snackPosition: SnackPosition.BOTTOM, - duration: Duration(seconds: 2), - ); - return; - } + // if (controller.summaryResult.value.isNotEmpty) { + // shareText = + // '${'sessionSummary'.tr}:\n${controller.summaryResult.value}'; // 会话摘要: + // } else { + // Get.snackbar( + // 'cannotShare'.tr, // 无法分享 + // 'generateSummaryFirst'.tr, // 请先生成摘要 + // snackPosition: SnackPosition.BOTTOM, + // duration: Duration(seconds: 2), + // ); + // return; + // } } // 调用系统分享 diff --git a/lib/modules/translation/views/translation_view.dart b/lib/modules/translation/views/translation_view.dart index 69abbd234..fc506df20 100644 --- a/lib/modules/translation/views/translation_view.dart +++ b/lib/modules/translation/views/translation_view.dart @@ -343,16 +343,31 @@ class TranslationView extends GetView { } Widget _buildBody(bool isDarkMode) { - return Column( - children: [ - Expanded( - child: _buildTranslationContent(isDarkMode), - ), - _buildControlBar(isDarkMode), - ], + return GestureDetector( + onPanUpdate: (details) { + // 检测左滑手势 + if (details.delta.dx < -10) { + // 左滑距离超过10像素时触发 + _handleSwipeLeft(); + } + }, + child: Column( + children: [ + Expanded( + child: _buildTranslationContent(isDarkMode), + ), + _buildControlBar(isDarkMode), + ], + ), ); } + // 处理左滑手势 + void _handleSwipeLeft() async { + await controller.stopAll(); + Get.toNamed(Routes.translationHistory); + } + Widget _buildTranslationContent(bool isDarkMode) { return Obx(() { // 其他模式使用原有的列表布局 @@ -969,14 +984,14 @@ class TranslationView extends GetView { controller.deleteTranslationItem(item.sessionId, item.timestamp); // 显示删除成功提示 - Get.snackbar( - 'success'.tr, // deleteSuccess - '已删除该翻译记录', // deleteTranslationSuccess - snackPosition: SnackPosition.BOTTOM, - backgroundColor: Get.isDarkMode ? Colors.grey[800] : Colors.grey[200], - colorText: Get.isDarkMode ? Colors.white : Colors.black, - duration: const Duration(milliseconds: 800), - ); + // Get.snackbar( + // 'success'.tr, // deleteSuccess + // '已删除该翻译记录', // deleteTranslationSuccess + // snackPosition: SnackPosition.BOTTOM, + // backgroundColor: Get.isDarkMode ? Colors.grey[800] : Colors.grey[200], + // colorText: Get.isDarkMode ? Colors.white : Colors.black, + // duration: const Duration(milliseconds: 800), + // ); } catch (e) { // 显示删除失败提示 Get.snackbar( From 28b4416fd3a61f387f4f75f3290f4b3cdd729774 Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Fri, 19 Sep 2025 20:52:36 +0800 Subject: [PATCH 05/55] =?UTF-8?q?=E4=B8=8A=E4=BC=A0app=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- android/app/build.gradle.kts | 3 +- android/app/src/main/AndroidManifest.xml | 4 +- .../yunqiinnovation/deepsound/MainActivity.kt | 135 +++++++++++++++- .../deepsound/MyApplication.kt | 144 ++++++++++++++++++ lib/main.dart | 140 +++++++++++------ .../splash/controllers/splash_controller.dart | 15 ++ .../agent_service/AgentService.kt | 6 +- .../agent_service/AgentServiceImpl.swift | 11 +- .../qq_music/QQMusicLoginManager.kt | 12 +- 9 files changed, 408 insertions(+), 62 deletions(-) create mode 100644 android/app/src/main/kotlin/com/yunqiinnovation/deepsound/MyApplication.kt diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 0b53e4a8f..6f29c8c95 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -61,7 +61,8 @@ android { manifestPlaceholders.putAll( mapOf( "redirectSchemeName" to "spotify-sdk", - "redirectHostName" to "auth" + "redirectHostName" to "auth", + "applicationName" to "com.saitong.voitrans.MyApp" // 新增:声明自定义 Application ) ) // TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html). diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index e1ca0f551..24d99722b 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -2,6 +2,7 @@ xmlns:tools="http://schemas.android.com/tools"> + @@ -84,8 +85,7 @@ + Log.d(TAG, "🔗 处理待处理的深链接") + handleDeepLink(intent) + pendingDeepLink = null + } + }, 1000) // 延迟1秒确保Flutter完全准备好 + + Log.d(TAG, "✅ configureFlutterEngine 完成") + } + + // 处理深链接的统一方法 + private fun handleDeepLink(intent: Intent?) { + intent?.let { + if (it.action == Intent.ACTION_VIEW && it.data != null) { + val uri = it.data + Log.d(TAG, "🔗 处理深链接: $uri") + + try { + Log.d(TAG, "🔗 开始发送深链接到 Flutter") + + // 通过 MethodChannel 将深链接信息传递给 Flutter + flutterEngine?.dartExecutor?.binaryMessenger?.let { messenger -> + Log.d(TAG, "🔗 获取到 BinaryMessenger") + + val channel = MethodChannel( + messenger, + "com.saitong.voitrans/deeplink" + ) + + val arguments = mapOf( + "url" to uri.toString(), + "scheme" to uri?.scheme, + "host" to uri?.host, + "path" to uri?.path, + "query" to uri?.query, + "queryParameters" to uri?.queryParameterNames?.associateWith { + uri.getQueryParameter(it) + } + ) + + Log.d(TAG, "🔗 准备发送参数: $arguments") + + channel.invokeMethod("handleDeepLink", arguments) + Log.d(TAG, "✅ 深链接信息已发送到 Flutter") + } ?: run { + Log.w(TAG, "❌ BinaryMessenger 为空,无法发送深链接") + } + } catch (e: Exception) { + Log.e(TAG, "❌ 处理深链接时出错", e) + } + } else { + Log.d(TAG, "🔗 Intent 不是深链接或数据为空") + } + } } override fun onDestroy() { @@ -112,5 +237,5 @@ class MainActivity: FlutterActivity() { // 反射方法可能不存在,忽略 } } -} +} diff --git a/android/app/src/main/kotlin/com/yunqiinnovation/deepsound/MyApplication.kt b/android/app/src/main/kotlin/com/yunqiinnovation/deepsound/MyApplication.kt new file mode 100644 index 000000000..17e5ee3b0 --- /dev/null +++ b/android/app/src/main/kotlin/com/yunqiinnovation/deepsound/MyApplication.kt @@ -0,0 +1,144 @@ +package com.saitong.voitrans + +import android.app.ActivityManager +import android.content.Context +import android.util.Log +import io.flutter.app.FlutterApplication +import io.flutter.embedding.engine.FlutterEngine +import io.flutter.embedding.engine.FlutterEngineCache +import io.flutter.embedding.engine.dart.DartExecutor +import io.flutter.plugins.GeneratedPluginRegistrant + +// Application.kt +class MyApp : FlutterApplication() { + + companion object { + private const val TAG = "MyApp" + private const val ENGINE_ID = "main_engine" + + // 标记 Engine 是否已经执行过 Dart 入口点 + @Volatile + private var isDartEntrypointExecuted = false + + // 记录应用启动时间,用于判断是否为新启动 + @Volatile + private var appStartTime = 0L + } + + override fun onCreate() { + super.onCreate() + appStartTime = System.currentTimeMillis() + Log.d(TAG, "MyApp onCreate - 进程启动") + + val cache = FlutterEngineCache.getInstance() + val existingEngine = cache.get(ENGINE_ID) + + if (existingEngine == null) { + Log.d(TAG, "创建新的 FlutterEngine") + createAndCacheEngine(cache) + } else { + Log.d(TAG, "FlutterEngine 已存在,检查状态") + // 检查现有 Engine 的状态 + try { + val isExecuting = existingEngine.dartExecutor.isExecutingDart + Log.d(TAG, "Engine Dart执行状态: $isExecuting") + + if (!isExecuting && isMainProcess()) { + Log.d(TAG, "Engine 存在但Dart未执行,重新执行") + existingEngine.dartExecutor.executeDartEntrypoint( + DartExecutor.DartEntrypoint.createDefault() + ) + isDartEntrypointExecuted = true + } + } catch (e: Exception) { + Log.e(TAG, "检查或重新执行 Dart 入口点失败", e) + // 如果失败,重新创建 Engine + cache.remove(ENGINE_ID) + createAndCacheEngine(cache) + } + } + } + + private fun createAndCacheEngine(cache: FlutterEngineCache) { + // 在主进程中总是创建并执行Dart入口点 + if (isMainProcess()) { + Log.d(TAG, "✅ 在主进程创建 FlutterEngine 并执行 Dart") + val flutterEngine = FlutterEngine(this) + + // 注册插件(只在这里注册一次) + Log.d(TAG, "📦 注册 Flutter 插件") + GeneratedPluginRegistrant.registerWith(flutterEngine) + + try { + Log.d(TAG, "🚀 开始执行 Dart 入口点") + flutterEngine.dartExecutor.executeDartEntrypoint( + DartExecutor.DartEntrypoint.createDefault() + ) + isDartEntrypointExecuted = true + cache.put(ENGINE_ID, flutterEngine) + Log.d(TAG, "✅ FlutterEngine 创建并缓存成功") + } catch (e: Exception) { + Log.e(TAG, "❌ 执行 Dart 入口点失败", e) + // 如果执行失败,仍然缓存 Engine,但标记为未执行 + isDartEntrypointExecuted = false + cache.put(ENGINE_ID, flutterEngine) + } + } else { + Log.d(TAG, "⚠️ 在非主进程,仅创建 FlutterEngine") + // 非主进程只创建 Engine,不执行 Dart 入口点 + val flutterEngine = FlutterEngine(this) + cache.put(ENGINE_ID, flutterEngine) + } + } + + private fun isMainProcess(): Boolean { + val am = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager + val processes = am.runningAppProcesses ?: return true + val mainProcessName = packageName + val myPid = android.os.Process.myPid() + + for (process in processes) { + if (process.pid == myPid) { + return process.processName == mainProcessName + } + } + return true + } + + private fun isAppInForeground(): Boolean { + val am = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager + val processes = am.runningAppProcesses ?: return false + val pkg = packageName + for (p in processes) { + if (p.processName == pkg && + p.importance == ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND) { + return true + } + } + return false + } + + private fun isAppBeingLaunched(): Boolean { + // 改进的启动检测逻辑 + val currentTime = System.currentTimeMillis() + val isRecentStart = (currentTime - appStartTime) < 3000 // 3秒内认为是新启动 + + if (isRecentStart) { + Log.d(TAG, "检测到应用新启动") + return true + } + + // 备用检测方法(兼容旧版本) + return try { + val am = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager + val tasks = am.getRunningTasks(1) + val result = tasks.isNotEmpty() && tasks[0].topActivity?.packageName == packageName + Log.d(TAG, "通过 RunningTasks 检测启动状态: $result") + result + } catch (e: Exception) { + Log.w(TAG, "无法获取 RunningTasks,可能缺少权限", e) + // 如果无法获取任务信息,默认认为是启动状态 + true + } + } +} diff --git a/lib/main.dart b/lib/main.dart index 6cfa3d668..5a0941bdf 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,37 +1,104 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; -import 'package:flutter_easyloading/flutter_easyloading.dart'; -import 'package:flutter_intro/flutter_intro.dart'; -import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:get/get.dart'; -import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:get_storage/get_storage.dart'; +import 'package:flutter_dotenv/flutter_dotenv.dart'; import 'package:flutter_screenutil/flutter_screenutil.dart'; -import 'core/bindings/initial_binding.dart'; + import 'routes/app_pages.dart'; import 'routes/app_routes.dart'; import 'core/translations/app_translations.dart'; import 'core/themes/app_theme.dart'; import 'core/themes/theme_service.dart'; -import '../../data/services/login_service.dart'; +import 'core/bindings/initial_binding.dart'; +import 'data/services/login_service.dart'; +import 'dart:ui' as ui; +import 'modules/splash/controllers/splash_controller.dart'; void main() async { - // 确保Flutter绑定初始化 WidgetsFlutterBinding.ensureInitialized(); - await SystemChrome.setPreferredOrientations([DeviceOrientation.portraitUp]); - - // 初始化存储 - 在创建应用之前初始化 + // 初始化存储 await GetStorage.init(); - // 初始化环境变量 - await dotenv.load(); + // 加载环境变量 + await dotenv.load(fileName: ".env"); + print("环境变量加载成功"); + + // 初始化登录服务 + Get.put(LoginService()); + + // 延迟初始化深链接处理器,确保Flutter完全准备好 + WidgetsBinding.instance.addPostFrameCallback((_) { + Future.delayed(const Duration(milliseconds: 1500), () { + _initDeepLinkHandler(); + }); + }); + + runApp(MainApp()); +} + +// 初始化深链接处理器 +void _initDeepLinkHandler() { + print("🔗 初始化深链接处理器"); + + const platform = MethodChannel('com.saitong.voitrans/deeplink'); + + platform.setMethodCallHandler((call) async { + print("🔗 Flutter 收到深链接调用: ${call.method}"); + + if (call.method == 'handleDeepLink') { + final arguments = call.arguments as Map; + print("🔗 深链接参数: $arguments"); - // 初始化登录服务(包含Firebase初始化) - LoginService(); + final url = arguments['url'] as String?; + if (url != null) { + print("🔗 处理深链接 URL: $url"); + await _handleDeepLinkNavigation(url); + } + } + }); - // 运行应用 - runApp(const MainApp()); + print("✅ 深链接处理器初始化完成"); +} + +// 处理深链接导航 +Future _handleDeepLinkNavigation(String url) async { + print("🔗 开始处理深链接导航: $url"); + + try { + final uri = Uri.parse(url); + print("🔗 解析URI: scheme=${uri.scheme}, host=${uri.host}, path=${uri.path}"); + + if (uri.scheme == 'deepvoice' && uri.host == 'qqmusic') { + print("🔗 检测到QQ音乐登录回调"); + + // 等待SplashController注册 + int retryCount = 0; + const maxRetries = 10; + + while (retryCount < maxRetries) { + try { + if (Get.isRegistered()) { + print("🔗 找到SplashController,处理QQ音乐登录"); + final controller = Get.find(); + controller.handleQQMusicLogin(); + return; + } + } catch (e) { + print("🔗 获取SplashController失败: $e"); + } + + retryCount++; + print("🔗 等待SplashController注册 ($retryCount/$maxRetries)"); + await Future.delayed(const Duration(milliseconds: 500)); + } + + print("❌ 超时等待SplashController注册"); + } + } catch (e) { + print("❌ 处理深链接时出错: $e"); + } } class MainApp extends StatelessWidget { @@ -39,46 +106,27 @@ class MainApp extends StatelessWidget { @override Widget build(BuildContext context) { - // 获取保存的主题设置 - final themeMode = ThemeService().getThemeMode(); + // 在应用构建时立即初始化深链接处理 + _initDeepLinkHandler(); + // 使用 ScreenUtilInit 包装整个应用 return ScreenUtilInit( - designSize: const Size(375, 812), + designSize: const Size(375, 812), // 设计稿尺寸,根据你的设计稿调整 minTextAdapt: true, splitScreenMode: true, - builder: (_, child) { + builder: (context, child) { return GetMaterialApp( - title: 'Voitrans', + title: 'VoiTrans', theme: AppTheme.lightTheme, darkTheme: AppTheme.darkTheme, - themeMode: themeMode, // 使用提前获取的主题模式 - initialBinding: InitialBinding(), + themeMode: ThemeService().getThemeMode(), initialRoute: Routes.splash, getPages: AppPages.pages, - debugShowCheckedModeBanner: false, + initialBinding: InitialBinding(), translations: AppTranslations(), - localizationsDelegates: const [ - GlobalMaterialLocalizations.delegate, - GlobalWidgetsLocalizations.delegate, - GlobalCupertinoLocalizations.delegate, - ], - locale: AppTranslations.deviceLocale(), - fallbackLocale: AppTranslations.getSmartFallbackLocale(), - supportedLocales: AppTranslations.supportedLocales, - builder: EasyLoading.init( - builder: (BuildContext context, Widget? widget) { - EasyLoading.instance.userInteractions = false; - return MediaQuery( - data: MediaQuery.of(context).copyWith( - textScaler: TextScaler.noScaling, - ), - child: Intro( - padding: EdgeInsets.zero, - child: widget!, - ), - ); - }, - ), + locale: Get.deviceLocale, + fallbackLocale: const Locale('en', 'US'), + debugShowCheckedModeBanner: false, ); }, ); diff --git a/lib/modules/splash/controllers/splash_controller.dart b/lib/modules/splash/controllers/splash_controller.dart index 4da069bc9..1ac931498 100644 --- a/lib/modules/splash/controllers/splash_controller.dart +++ b/lib/modules/splash/controllers/splash_controller.dart @@ -289,6 +289,21 @@ class SplashController extends GetxController { return false; } } + + /// 处理QQ音乐登录回调 + void handleQQMusicLogin() { + try { + Logger.i(_tag, '处理QQ音乐登录回调'); + + // 这里可以添加具体的QQ音乐登录处理逻辑 + // 例如:验证登录状态、更新用户信息、导航到相应页面等 + + // 如果需要导航到特定页面,可以使用: + // Get.toNamed(Routes.qqmusic); + } catch (e) { + Logger.e(_tag, '处理QQ音乐登录回调失败: $e'); + } + } } /// 显示无网络连接对话框并等待用户操作(阻塞版本) diff --git a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt b/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt index 99e0ed8e5..c4cab4819 100644 --- a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt +++ b/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt @@ -1956,7 +1956,7 @@ object AgentService : CoroutineScope { ) } if (playlist.isNotEmpty()) { - processQQMusicPlayList(playlist) +// processQQMusicPlayList(playlist) } else { Log.w(TAG, "qqmusic_searchandplay_songs 中没有有效的音乐条目") } @@ -1969,6 +1969,10 @@ object AgentService : CoroutineScope { val start = cardNavigation.optString("start", "") val end = cardNavigation.optString("end", "") processNavigation(start, end); + }else if (toolname == "map_weather") { //导航服务 + iscard = true + }else if (toolname == "maps_weather") { //导航服务 + iscard = true } return mapOf("meta" to metaStr, "broadcast" to broadcast, "notreply" to notreply,"iscard" to iscard); } diff --git a/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift b/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift index b294b6a05..b13d17233 100644 --- a/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift +++ b/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift @@ -834,6 +834,7 @@ internal func autoHandleFunctionCallResult(toolName: String, functionCallResult: private func handleJSONDictionary(_ jsonDict: [String: Any], toolName: String) -> [String: Any] { var broadcast = true var notreply = false + var iscard = false var metadata: [String: Any] = [:] metadata[toolName] = jsonDict @@ -843,7 +844,11 @@ private func handleJSONDictionary(_ jsonDict: [String: Any], toolName: String) - if let _notreply = jsonDict["notreply"] as? Bool { notreply = _notreply } + if let _iscard = jsonDict["iscard"] as? Bool { + iscard = _iscard + } metadata["notreply"] = notreply + metadata["iscard"] = iscard // 处理卡片数据 // if let cardMusic = jsonDict["card_music"] as? [String: Any] { // processMusicCard(cardMusic) @@ -854,8 +859,12 @@ private func handleJSONDictionary(_ jsonDict: [String: Any], toolName: String) - processNavigationCard(cardNavigation) } else if let cardQQMusiclist = jsonDict["data"] as? [[String: Any]], toolName == "qqmusic_searchandplay_songs" { processQQMusicPlayList(cardQQMusiclist) + } else if toolName == "maps_weather" { + iscard = true + }else if toolName == "map_weather" { + iscard = true } - + metadata["iscard"] = iscard let metaStr: String if let jsonData = try? JSONSerialization.data(withJSONObject: metadata, options: []), let jsonString = String(data: jsonData, encoding: .utf8) { diff --git a/local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/QQMusicLoginManager.kt b/local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/QQMusicLoginManager.kt index 396c6cf76..077fdf10c 100644 --- a/local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/QQMusicLoginManager.kt +++ b/local_plugins/qq_music/android/src/main/kotlin/com/yunqiinnovation/qq_music/QQMusicLoginManager.kt @@ -108,7 +108,7 @@ class QQMusicLoginManager(private val context: Context) { // 已授权,直接请求登录信息 Log.d(TAG, "已授权,直接获取登录信息") // isBindQQMusicService = true - startAuthProcess2(callback) + startAuthProcess(callback) } ErrorCodes.ERROR_API_NO_PERMISSION -> { // 需要授权 @@ -156,11 +156,11 @@ class QQMusicLoginManager(private val context: Context) { ) // Log.d(TAG, "当前是前台服务") } else { - // CommonCmd.loginQQMusic( - // context, - // context.packageName, - // BASE_SCHEME, - // ) + CommonCmd.loginQQMusic( + context, + context.packageName, + BASE_SCHEME, + ) Log.d(TAG, "当前是后台服务") } // 启动QQ音乐登录 From 633a404d442f58bd39e90598bd44006ec724855c Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Fri, 19 Sep 2025 21:13:14 +0800 Subject: [PATCH 06/55] =?UTF-8?q?fix(ota):=20=E5=B0=86=E5=9B=BA=E4=BB=B6?= =?UTF-8?q?=E5=8D=87=E7=BA=A7=E7=94=B5=E9=87=8F=E8=A6=81=E6=B1=82=E4=BB=8E?= =?UTF-8?q?30%=E8=B0=83=E6=95=B4=E4=B8=BA20%?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 更新所有语言翻译文件和相关逻辑,将固件升级的最低电量要求从30%降低到20%,以提升用户体验。同时优化了升级完成后的页面跳转逻辑。 --- lib/core/translations/language/bg_bg.dart | 2 +- lib/core/translations/language/cs_cz.dart | 2 +- lib/core/translations/language/da_dk.dart | 2 +- lib/core/translations/language/de_de.dart | 2 +- lib/core/translations/language/el_gr.dart | 2 +- lib/core/translations/language/en_us.dart | 2 +- lib/core/translations/language/es_es.dart | 2 +- lib/core/translations/language/et_ee.dart | 2 +- lib/core/translations/language/fi_fi.dart | 2 +- lib/core/translations/language/fr_fr.dart | 2 +- lib/core/translations/language/hi_in.dart | 2 +- lib/core/translations/language/hr_hr.dart | 2 +- lib/core/translations/language/hu_hu.dart | 2 +- lib/core/translations/language/id_id.dart | 2 +- lib/core/translations/language/is_is.dart | 2 +- lib/core/translations/language/it_it.dart | 2 +- lib/core/translations/language/ja_jp.dart | 2 +- lib/core/translations/language/ko_kr.dart | 2 +- lib/core/translations/language/lt_lt.dart | 2 +- lib/core/translations/language/lv_lv.dart | 2 +- lib/core/translations/language/ms_my.dart | 2 +- lib/core/translations/language/nb_no.dart | 2 +- lib/core/translations/language/nl_nl.dart | 2 +- lib/core/translations/language/pl_pl.dart | 2 +- lib/core/translations/language/pt_pt.dart | 2 +- lib/core/translations/language/ro_ro.dart | 2 +- lib/core/translations/language/ru_ru.dart | 2 +- lib/core/translations/language/sk_sk.dart | 2 +- lib/core/translations/language/sl_si.dart | 2 +- lib/core/translations/language/sr_rs.dart | 2 +- lib/core/translations/language/sv_se.dart | 2 +- lib/core/translations/language/th_th.dart | 2 +- lib/core/translations/language/tl_ph.dart | 2 +- lib/core/translations/language/tr_tr.dart | 2 +- lib/core/translations/language/uk_ua.dart | 2 +- lib/core/translations/language/vi_vn.dart | 2 +- lib/core/translations/language/zh_cn.dart | 2 +- lib/core/translations/language/zh_hk.dart | 2 +- .../ota_upgrade/controllers/ota_core_contropller.dart | 6 +++--- lib/modules/ota_upgrade/views/ota_upgrade_popup_view.dart | 2 +- lib/modules/ota_upgrade/views/ota_upgrade_view.dart | 2 +- 41 files changed, 43 insertions(+), 43 deletions(-) diff --git a/lib/core/translations/language/bg_bg.dart b/lib/core/translations/language/bg_bg.dart index 2b5bc3d8c..f79e90317 100644 --- a/lib/core/translations/language/bg_bg.dart +++ b/lib/core/translations/language/bg_bg.dart @@ -824,7 +824,7 @@ const Map bgBG = { "charging": "Зареждане", "updateContent": "Съдържание на актуализацията", "removeEarbudsEnsureBattery": - "Извадете двете слушалки и се уверете, че батерията на двете е над 30%", + "Извадете двете слушалки и се уверете, че батерията на двете е над 20%", // 音乐列表页面 "noMusic": "Няма музика", // 暂无音乐 diff --git a/lib/core/translations/language/cs_cz.dart b/lib/core/translations/language/cs_cz.dart index bf07f076a..7d0ea76e1 100644 --- a/lib/core/translations/language/cs_cz.dart +++ b/lib/core/translations/language/cs_cz.dart @@ -810,7 +810,7 @@ const Map csCZ = { "charging": "Nabíjení", "updateContent": "Obsah aktualizace", "removeEarbudsEnsureBattery": - "Vyjměte obě sluchátka a ujistěte se, že baterie obou je nad 30%", + "Vyjměte obě sluchátka a ujistěte se, že baterie obou je nad 20%", // 音乐列表页面 "noMusic": "Žádná hudba", // 暂无音乐 diff --git a/lib/core/translations/language/da_dk.dart b/lib/core/translations/language/da_dk.dart index d85f68c6f..991a774d5 100644 --- a/lib/core/translations/language/da_dk.dart +++ b/lib/core/translations/language/da_dk.dart @@ -809,7 +809,7 @@ const Map daDK = { "charging": "Oplader", "updateContent": "Opdateringsindhold", "removeEarbudsEnsureBattery": - "Tag begge øretelefoner ud og sørg for at batteriet på begge er over 30%", + "Tag begge øretelefoner ud og sørg for at batteriet på begge er over 20%", // 音乐列表页面 "noMusic": "Ingen musik", // 暂无音乐 diff --git a/lib/core/translations/language/de_de.dart b/lib/core/translations/language/de_de.dart index b8f925e9c..4712dabb5 100644 --- a/lib/core/translations/language/de_de.dart +++ b/lib/core/translations/language/de_de.dart @@ -829,7 +829,7 @@ const Map deDE = { "charging": "Laden", "updateContent": "Update-Inhalt", "removeEarbudsEnsureBattery": - "Entfernen Sie beide Ohrhörer und stellen Sie sicher, dass der Batteriestand über 30% liegt", + "Entfernen Sie beide Ohrhörer und stellen Sie sicher, dass der Batteriestand über 20% liegt", // 音乐列表页面 "noMusic": "Keine Musik", // 暂无音乐 diff --git a/lib/core/translations/language/el_gr.dart b/lib/core/translations/language/el_gr.dart index ce0862444..d91db41a0 100644 --- a/lib/core/translations/language/el_gr.dart +++ b/lib/core/translations/language/el_gr.dart @@ -819,7 +819,7 @@ const Map elGR = { "charging": "Φόρτιση", "updateContent": "Περιεχόμενο ενημέρωσης", "removeEarbudsEnsureBattery": - "Αφαιρέστε και τα δύο ακουστικά και βεβαιωθείτε ότι η μπαταρία και των δύο είναι πάνω από 30%", + "Αφαιρέστε και τα δύο ακουστικά και βεβαιωθείτε ότι η μπαταρία και των δύο είναι πάνω από 20%", // 音乐列表页面 "noMusic": "Δεν υπάρχει μουσική", // 暂无音乐 diff --git a/lib/core/translations/language/en_us.dart b/lib/core/translations/language/en_us.dart index eae0c4a3b..e84caa5af 100644 --- a/lib/core/translations/language/en_us.dart +++ b/lib/core/translations/language/en_us.dart @@ -1137,7 +1137,7 @@ const Map enUS = { "charging": "Charging", "updateContent": "Update Content", "removeEarbudsEnsureBattery": - "Remove both earbuds and ensure battery level is above 30%", + "Remove both earbuds and ensure battery level is above 20%", // 音乐列表页面 "noMusic": "No Music", // 暂无音乐 diff --git a/lib/core/translations/language/es_es.dart b/lib/core/translations/language/es_es.dart index cbfa8bd21..b5eae099d 100644 --- a/lib/core/translations/language/es_es.dart +++ b/lib/core/translations/language/es_es.dart @@ -823,7 +823,7 @@ const Map esES = { "charging": "Cargando", "updateContent": "Contenido de actualización", "removeEarbudsEnsureBattery": - "Retira ambos auriculares y asegúrate de que la batería de ambos esté por encima del 30%", + "Retira ambos auriculares y asegúrate de que la batería de ambos esté por encima del 20%", // 音乐列表页面 "noMusic": "Sin música", // 暂无音乐 diff --git a/lib/core/translations/language/et_ee.dart b/lib/core/translations/language/et_ee.dart index a203f58d0..5b351c875 100644 --- a/lib/core/translations/language/et_ee.dart +++ b/lib/core/translations/language/et_ee.dart @@ -798,7 +798,7 @@ const Map etEE = { "charging": "Laadimine", "updateContent": "Uuenduse sisu", "removeEarbudsEnsureBattery": - "Võta mõlemad kõrvaklapid välja ja veendu, et mõlema aku on üle 30%", + "Võta mõlemad kõrvaklapid välja ja veendu, et mõlema aku on üle 20%", // 音乐列表页面 "noMusic": "Muusikat pole", // 暂无音乐 diff --git a/lib/core/translations/language/fi_fi.dart b/lib/core/translations/language/fi_fi.dart index ad5f39b5e..9daf6d0ec 100644 --- a/lib/core/translations/language/fi_fi.dart +++ b/lib/core/translations/language/fi_fi.dart @@ -793,7 +793,7 @@ const Map fiFI = { "charging": "Ladataan", "updateContent": "Päivityksen sisältö", "removeEarbudsEnsureBattery": - "Poista molemmat kuulokkeet ja varmista, että molempien akun varaus on yli 30%", + "Poista molemmat kuulokkeet ja varmista, että molempien akun varaus on yli 20%", // 音乐列表页面 "noMusic": "Ei musiikkia", diff --git a/lib/core/translations/language/fr_fr.dart b/lib/core/translations/language/fr_fr.dart index 271d2abfc..c9ef9fc69 100644 --- a/lib/core/translations/language/fr_fr.dart +++ b/lib/core/translations/language/fr_fr.dart @@ -832,7 +832,7 @@ const Map frFR = { "charging": "En charge", "updateContent": "Contenu de la mise à jour", "removeEarbudsEnsureBattery": - "Retirez les deux écouteurs et assurez-vous que le niveau de batterie est supérieur à 30%", + "Retirez les deux écouteurs et assurez-vous que le niveau de batterie est supérieur à 20%", // 音乐列表页面 "noMusic": "Aucune musique", // 暂无音乐 diff --git a/lib/core/translations/language/hi_in.dart b/lib/core/translations/language/hi_in.dart index 0629cb064..267ddc911 100644 --- a/lib/core/translations/language/hi_in.dart +++ b/lib/core/translations/language/hi_in.dart @@ -797,7 +797,7 @@ const Map hiIN = { "charging": "चार्ज हो रहा है", "updateContent": "अपडेट सामग्री", "removeEarbudsEnsureBattery": - "दोनों ईयरबड निकालें, और सुनिश्चित करें कि दोनों की बैटरी 30% से ऊपर है", + "दोनों ईयरबड निकालें, और सुनिश्चित करें कि दोनों की बैटरी 20% से ऊपर है", // 音乐列表页面 "noMusic": "कोई संगीत नहीं", diff --git a/lib/core/translations/language/hr_hr.dart b/lib/core/translations/language/hr_hr.dart index f38f4bab3..fcc7845a1 100644 --- a/lib/core/translations/language/hr_hr.dart +++ b/lib/core/translations/language/hr_hr.dart @@ -803,7 +803,7 @@ const Map hrHR = { "charging": "Punjenje", "updateContent": "Sadržaj ažuriranja", "removeEarbudsEnsureBattery": - "Izvadite obje slušalice i provjerite je li baterija obje iznad 30%", + "Izvadite obje slušalice i provjerite je li baterija obje iznad 20%", // 音乐列表页面 "noMusic": "Nema glazbe", diff --git a/lib/core/translations/language/hu_hu.dart b/lib/core/translations/language/hu_hu.dart index ebb221fe4..28a15ba39 100644 --- a/lib/core/translations/language/hu_hu.dart +++ b/lib/core/translations/language/hu_hu.dart @@ -802,7 +802,7 @@ const Map huHU = { "charging": "Töltés", "updateContent": "Frissítés tartalma", "removeEarbudsEnsureBattery": - "Vegye ki mindkét fülhallgatót, és győződjön meg róla, hogy mindkettő akkumulátora 30% felett van", + "Vegye ki mindkét fülhallgatót, és győződjön meg róla, hogy mindkettő akkumulátora 20% felett van", // 音乐列表页面 "noMusic": "Nincs zene", diff --git a/lib/core/translations/language/id_id.dart b/lib/core/translations/language/id_id.dart index 0c1b36a8d..01f704e62 100644 --- a/lib/core/translations/language/id_id.dart +++ b/lib/core/translations/language/id_id.dart @@ -795,7 +795,7 @@ const Map idID = { "charging": "Mengisi daya", "updateContent": "Konten Pembaruan", "removeEarbudsEnsureBattery": - "Keluarkan kedua earbud, dan pastikan baterai keduanya di atas 30%", + "Keluarkan kedua earbud, dan pastikan baterai keduanya di atas 20%", // 音乐列表页面 "noMusic": "Tidak Ada Musik", diff --git a/lib/core/translations/language/is_is.dart b/lib/core/translations/language/is_is.dart index 15cc47ed5..98fd4f1b8 100644 --- a/lib/core/translations/language/is_is.dart +++ b/lib/core/translations/language/is_is.dart @@ -796,7 +796,7 @@ const Map isIS = { "charging": "Hleður", "updateContent": "Innihald uppfærslu", "removeEarbudsEnsureBattery": - "Taktu báðar heyrnartólin úr og gakktu úr skugga um að rafhlaðan á báðum sé yfir 30%", + "Taktu báðar heyrnartólin úr og gakktu úr skugga um að rafhlaðan á báðum sé yfir 20%", // 音乐列表页面 "noMusic": "Engin tónlist", diff --git a/lib/core/translations/language/it_it.dart b/lib/core/translations/language/it_it.dart index bbe8e7350..322e4ecc9 100644 --- a/lib/core/translations/language/it_it.dart +++ b/lib/core/translations/language/it_it.dart @@ -818,7 +818,7 @@ const Map itIT = { "charging": "In carica", "updateContent": "Contenuto dell'aggiornamento", "removeEarbudsEnsureBattery": - "Rimuovi entrambi gli auricolari e assicurati che la batteria di entrambi sia superiore al 30%", + "Rimuovi entrambi gli auricolari e assicurati che la batteria di entrambi sia superiore al 20%", // 音乐列表页面 "noMusic": "Nessuna musica", diff --git a/lib/core/translations/language/ja_jp.dart b/lib/core/translations/language/ja_jp.dart index b2d761375..ef96b5852 100644 --- a/lib/core/translations/language/ja_jp.dart +++ b/lib/core/translations/language/ja_jp.dart @@ -746,7 +746,7 @@ const Map jaJP = { "doNotDisconnect": "切断しないでください", "charging": "充電中", "updateContent": "更新内容", - "removeEarbudsEnsureBattery": "両方のイヤホンを取り出し、バッテリーレベルが30%以上であることを確認してください", + "removeEarbudsEnsureBattery": "両方のイヤホンを取り出し、バッテリーレベルが20%以上であることを確認してください", // 音乐列表页面 "noMusic": "音楽がありません", // 暂无音乐 diff --git a/lib/core/translations/language/ko_kr.dart b/lib/core/translations/language/ko_kr.dart index 422119468..e2b06bbcc 100644 --- a/lib/core/translations/language/ko_kr.dart +++ b/lib/core/translations/language/ko_kr.dart @@ -733,7 +733,7 @@ const Map koKR = { "doNotDisconnect": "연결을 끊지 마세요", "charging": "충전 중", "updateContent": "업데이트 내용", - "removeEarbudsEnsureBattery": "양쪽 이어버드를 꺼내고 배터리가 모두 30% 이상인지 확인하세요", + "removeEarbudsEnsureBattery": "양쪽 이어버드를 꺼내고 배터리가 모두 20% 이상인지 확인하세요", // 音乐列表页面 "noMusic": "음악 없음", diff --git a/lib/core/translations/language/lt_lt.dart b/lib/core/translations/language/lt_lt.dart index 023da1192..273ebb334 100644 --- a/lib/core/translations/language/lt_lt.dart +++ b/lib/core/translations/language/lt_lt.dart @@ -804,7 +804,7 @@ const Map ltLT = { "charging": "Kraunama", "updateContent": "Atnaujinimo turinys", "removeEarbudsEnsureBattery": - "Išimkite abi ausines ir įsitikinkite, kad abiejų baterijos lygis yra didesnis nei 30%", + "Išimkite abi ausines ir įsitikinkite, kad abiejų baterijos lygis yra didesnis nei 20%", // 音乐列表页面 "noMusic": "Nėra muzikos", diff --git a/lib/core/translations/language/lv_lv.dart b/lib/core/translations/language/lv_lv.dart index 5e1a507b2..e6fcd0027 100644 --- a/lib/core/translations/language/lv_lv.dart +++ b/lib/core/translations/language/lv_lv.dart @@ -805,7 +805,7 @@ const Map lvLV = { "charging": "Uzlādējas", "updateContent": "Atjauninājuma saturs", "removeEarbudsEnsureBattery": - "Izņemiet abas austiņas un pārliecinieties, ka abu baterijas līmenis ir virs 30%", + "Izņemiet abas austiņas un pārliecinieties, ka abu baterijas līmenis ir virs 20%", // 音乐列表页面 "noMusic": "Nav mūzikas", diff --git a/lib/core/translations/language/ms_my.dart b/lib/core/translations/language/ms_my.dart index c2c7aab10..025682afc 100644 --- a/lib/core/translations/language/ms_my.dart +++ b/lib/core/translations/language/ms_my.dart @@ -795,7 +795,7 @@ const Map msMY = { "charging": "Mengecas", "updateContent": "Kandungan Kemas Kini", "removeEarbudsEnsureBattery": - "Keluarkan kedua-dua fon telinga, dan pastikan bateri kedua-duanya melebihi 30%", + "Keluarkan kedua-dua fon telinga, dan pastikan bateri kedua-duanya melebihi 20%", // 音乐列表页面 "noMusic": "Tiada Muzik", diff --git a/lib/core/translations/language/nb_no.dart b/lib/core/translations/language/nb_no.dart index 52d40026e..4bb18acf4 100644 --- a/lib/core/translations/language/nb_no.dart +++ b/lib/core/translations/language/nb_no.dart @@ -794,7 +794,7 @@ const Map nbNO = { "charging": "Lader", "updateContent": "Oppdateringsinnhold", "removeEarbudsEnsureBattery": - "Ta ut begge øreproppene og sørg for at batterinivået på begge er over 30 %", + "Ta ut begge øreproppene og sørg for at batterinivået på begge er over 20 %", // 音乐列表页面 "noMusic": "Ingen musikk", diff --git a/lib/core/translations/language/nl_nl.dart b/lib/core/translations/language/nl_nl.dart index 1c837d898..e4649a292 100644 --- a/lib/core/translations/language/nl_nl.dart +++ b/lib/core/translations/language/nl_nl.dart @@ -804,7 +804,7 @@ const Map nlNL = { "charging": "Opladen", "updateContent": "Update-inhoud", "removeEarbudsEnsureBattery": - "Haal beide oortjes eruit en zorg ervoor dat de batterij van beide meer dan 30% is", + "Haal beide oortjes eruit en zorg ervoor dat de batterij van beide meer dan 20% is", // 音乐列表页面 "noMusic": "Geen muziek", diff --git a/lib/core/translations/language/pl_pl.dart b/lib/core/translations/language/pl_pl.dart index 803edcff6..0223cb0d3 100644 --- a/lib/core/translations/language/pl_pl.dart +++ b/lib/core/translations/language/pl_pl.dart @@ -810,7 +810,7 @@ const Map plPL = { "charging": "Ładowanie", "updateContent": "Treść aktualizacji", "removeEarbudsEnsureBattery": - "Wyjmij obie słuchawki i upewnij się, że poziom naładowania baterii obu wynosi ponad 30%", + "Wyjmij obie słuchawki i upewnij się, że poziom naładowania baterii obu wynosi ponad 20%", // 音乐列表页面 "noMusic": "Brak muzyki", diff --git a/lib/core/translations/language/pt_pt.dart b/lib/core/translations/language/pt_pt.dart index de3d1c49f..9319f2dae 100644 --- a/lib/core/translations/language/pt_pt.dart +++ b/lib/core/translations/language/pt_pt.dart @@ -812,7 +812,7 @@ const Map ptPT = { "charging": "A carregar", "updateContent": "Conteúdo da Atualização", "removeEarbudsEnsureBattery": - "Retire ambos os auriculares e certifique-se de que a bateria de ambos está acima de 30%", + "Retire ambos os auriculares e certifique-se de que a bateria de ambos está acima de 20%", // 音乐列表页面 "noMusic": "Sem Música", diff --git a/lib/core/translations/language/ro_ro.dart b/lib/core/translations/language/ro_ro.dart index b25fcf410..05c07e1d7 100644 --- a/lib/core/translations/language/ro_ro.dart +++ b/lib/core/translations/language/ro_ro.dart @@ -813,7 +813,7 @@ const Map roRO = { "charging": "Se încarcă", "updateContent": "Conținut actualizare", "removeEarbudsEnsureBattery": - "Scoateți ambele căști și asigurați-vă că bateria ambelor este peste 30%", + "Scoateți ambele căști și asigurați-vă că bateria ambelor este peste 20%", // 音乐列表页面 "noMusic": "Fără muzică", diff --git a/lib/core/translations/language/ru_ru.dart b/lib/core/translations/language/ru_ru.dart index c96599c05..a08bb5532 100644 --- a/lib/core/translations/language/ru_ru.dart +++ b/lib/core/translations/language/ru_ru.dart @@ -805,7 +805,7 @@ const Map ruRU = { "charging": "Зарядка", "updateContent": "Содержание обновления", "removeEarbudsEnsureBattery": - "Извлеките оба наушника и убедитесь, что уровень заряда батареи в обоих превышает 30%", + "Извлеките оба наушника и убедитесь, что уровень заряда батареи в обоих превышает 20%", // 音乐列表页面 "noMusic": "Нет музыки", diff --git a/lib/core/translations/language/sk_sk.dart b/lib/core/translations/language/sk_sk.dart index 3fecab7a4..2c40591de 100644 --- a/lib/core/translations/language/sk_sk.dart +++ b/lib/core/translations/language/sk_sk.dart @@ -799,7 +799,7 @@ const Map skSK = { "charging": "Nabíja sa", "updateContent": "Obsah aktualizácie", "removeEarbudsEnsureBattery": - "Vyberte obe slúchadlá a uistite sa, že batéria oboch je nad 30%", + "Vyberte obe slúchadlá a uistite sa, že batéria oboch je nad 20%", // 音乐列表页面 "noMusic": "Žiadna hudba", diff --git a/lib/core/translations/language/sl_si.dart b/lib/core/translations/language/sl_si.dart index 07e3b0e8a..8a45e9437 100644 --- a/lib/core/translations/language/sl_si.dart +++ b/lib/core/translations/language/sl_si.dart @@ -804,7 +804,7 @@ const Map slSI = { "charging": "Polnjenje", "updateContent": "Vsebina posodobitve", "removeEarbudsEnsureBattery": - "Odstranite obe slušalki in se prepričajte, da je baterija obeh nad 30%", + "Odstranite obe slušalki in se prepričajte, da je baterija obeh nad 20%", // 音乐列表页面 "noMusic": "Ni glasbe", diff --git a/lib/core/translations/language/sr_rs.dart b/lib/core/translations/language/sr_rs.dart index 62b7440f5..e99a7021a 100644 --- a/lib/core/translations/language/sr_rs.dart +++ b/lib/core/translations/language/sr_rs.dart @@ -798,7 +798,7 @@ const Map srRS = { "charging": "Punjenje", "updateContent": "Sadržaj ažuriranja", "removeEarbudsEnsureBattery": - "Izvadite obe slušalice i uverite se da je baterija obe iznad 30%", + "Izvadite obe slušalice i uverite se da je baterija obe iznad 20%", // 音乐列表页面 "noMusic": "Nema muzike", diff --git a/lib/core/translations/language/sv_se.dart b/lib/core/translations/language/sv_se.dart index 6c2b4b319..78067bcf9 100644 --- a/lib/core/translations/language/sv_se.dart +++ b/lib/core/translations/language/sv_se.dart @@ -794,7 +794,7 @@ const Map svSE = { "charging": "Laddar", "updateContent": "Uppdateringsinnehåll", "removeEarbudsEnsureBattery": - "Ta ut båda hörlurarna och se till att batterinivån är över 30 % på båda", + "Ta ut båda hörlurarna och se till att batterinivån är över 20 % på båda", // 音乐列表页面 "noMusic": "Ingen musik", diff --git a/lib/core/translations/language/th_th.dart b/lib/core/translations/language/th_th.dart index f037318e9..9868d122d 100644 --- a/lib/core/translations/language/th_th.dart +++ b/lib/core/translations/language/th_th.dart @@ -780,7 +780,7 @@ const Map thTH = { "charging": "กำลังชาร์จ", "updateContent": "เนื้อหาการอัปเดต", "removeEarbudsEnsureBattery": - "ถอดหูฟังทั้งสองข้างออก และตรวจสอบให้แน่ใจว่าแบตเตอรี่ทั้งสองข้างมีพลังงานมากกว่า 30%", + "ถอดหูฟังทั้งสองข้างออก และตรวจสอบให้แน่ใจว่าแบตเตอรี่ทั้งสองข้างมีพลังงานมากกว่า 20%", // 音乐列表页面 "noMusic": "ไม่มีเพลง", diff --git a/lib/core/translations/language/tl_ph.dart b/lib/core/translations/language/tl_ph.dart index 62dc7fb8b..1e7d0b891 100644 --- a/lib/core/translations/language/tl_ph.dart +++ b/lib/core/translations/language/tl_ph.dart @@ -816,7 +816,7 @@ const Map tlPH = { "charging": "Nagcha-charge", "updateContent": "Nilalaman ng Update", "removeEarbudsEnsureBattery": - "Alisin ang parehong earbuds, at tiyaking parehong may 30% o higit pang baterya", + "Alisin ang parehong earbuds, at tiyaking parehong may 20% o higit pang baterya", // 音乐列表页面 "noMusic": "Walang Musika", diff --git a/lib/core/translations/language/tr_tr.dart b/lib/core/translations/language/tr_tr.dart index b2d537d51..ebfd1c3d7 100644 --- a/lib/core/translations/language/tr_tr.dart +++ b/lib/core/translations/language/tr_tr.dart @@ -796,7 +796,7 @@ const Map trTR = { "charging": "Şarj oluyor", "updateContent": "Güncelleme İçeriği", "removeEarbudsEnsureBattery": - "Her iki kulaklığı da çıkarın ve pil seviyelerinin %30'un üzerinde olduğundan emin olun", + "Her iki kulaklığı da çıkarın ve pil seviyelerinin %20'un üzerinde olduğundan emin olun", // 音乐列表页面 "noMusic": "Müzik Yok", diff --git a/lib/core/translations/language/uk_ua.dart b/lib/core/translations/language/uk_ua.dart index 95ea53215..8d9d12e24 100644 --- a/lib/core/translations/language/uk_ua.dart +++ b/lib/core/translations/language/uk_ua.dart @@ -804,7 +804,7 @@ const Map ukUA = { "charging": "Зарядка", "updateContent": "Зміст оновлення", "removeEarbudsEnsureBattery": - "Вийміть обидва навушники та переконайтеся, що заряд батареї в обох перевищує 30%", + "Вийміть обидва навушники та переконайтеся, що заряд батареї в обох перевищує 20%", // 音乐列表页面 "noMusic": "Немає музики", diff --git a/lib/core/translations/language/vi_vn.dart b/lib/core/translations/language/vi_vn.dart index bb79fd9bb..350988495 100644 --- a/lib/core/translations/language/vi_vn.dart +++ b/lib/core/translations/language/vi_vn.dart @@ -788,7 +788,7 @@ const Map viVN = { "charging": "Đang sạc", "updateContent": "Nội dung cập nhật", "removeEarbudsEnsureBattery": - "Lấy cả hai tai nghe ra và đảm bảo pin của cả hai đều trên 30%", + "Lấy cả hai tai nghe ra và đảm bảo pin của cả hai đều trên 20%", // 音乐列表页面 "noMusic": "Không có nhạc", diff --git a/lib/core/translations/language/zh_cn.dart b/lib/core/translations/language/zh_cn.dart index fc0a2439c..aa5d27a9b 100644 --- a/lib/core/translations/language/zh_cn.dart +++ b/lib/core/translations/language/zh_cn.dart @@ -1036,7 +1036,7 @@ const Map zhCN = { "doNotDisconnect": "请勿断开连接", "charging": "充电中", "updateContent": "更新内容", - "removeEarbudsEnsureBattery": "取出双耳机,并保证电量均为30%以上的状态", + "removeEarbudsEnsureBattery": "取出双耳机,并保证电量均为20%以上的状态", // 音乐列表页面 "noMusic": "暂无音乐", // 暂无音乐 diff --git a/lib/core/translations/language/zh_hk.dart b/lib/core/translations/language/zh_hk.dart index aa83a2427..3be17e2f9 100644 --- a/lib/core/translations/language/zh_hk.dart +++ b/lib/core/translations/language/zh_hk.dart @@ -734,7 +734,7 @@ const Map zhHK = { "doNotDisconnect": "請勿斷開連接", "charging": "充電中", "updateContent": "更新內容", - "removeEarbudsEnsureBattery": "取出雙耳機,並保證電量均為30%以上的狀態", + "removeEarbudsEnsureBattery": "取出雙耳機,並保證電量均為20%以上的狀態", // 音樂列表頁面 "noMusic": "暫無音樂", diff --git a/lib/modules/ota_upgrade/controllers/ota_core_contropller.dart b/lib/modules/ota_upgrade/controllers/ota_core_contropller.dart index bef30cae9..03ec1be87 100644 --- a/lib/modules/ota_upgrade/controllers/ota_core_contropller.dart +++ b/lib/modules/ota_upgrade/controllers/ota_core_contropller.dart @@ -188,8 +188,8 @@ class OtaCoreService { final leftBattery = deviceInfo.leftEarBattery; final rightBattery = deviceInfo.rightEarBattery; - // 检查左右耳电量是否都大于30% - if (leftBattery <= 30 || rightBattery <= 30) { + // 检查左右耳电量是否都大于20% + if (leftBattery <= 20 || rightBattery <= 20) { _showLowBatteryDialog(leftBattery, rightBattery); return false; } @@ -206,7 +206,7 @@ class OtaCoreService { /// [rightBattery] 右耳电量 void _showLowBatteryDialog(int leftBattery, int rightBattery) { String message = '设备电量不足,无法进行固件升级\n'; - message += '升级要求:左右耳电量均需大于30%\n'; + message += '升级要求:左右耳电量均需大于20%\n'; message += '当前电量:左耳 ${leftBattery}%,右耳 ${rightBattery}%\n'; message += '请充电后再试'; diff --git a/lib/modules/ota_upgrade/views/ota_upgrade_popup_view.dart b/lib/modules/ota_upgrade/views/ota_upgrade_popup_view.dart index 66ef745f5..c117987db 100644 --- a/lib/modules/ota_upgrade/views/ota_upgrade_popup_view.dart +++ b/lib/modules/ota_upgrade/views/ota_upgrade_popup_view.dart @@ -80,7 +80,7 @@ class OtaUpgradePopUp { ElevatedButton( onPressed: () { Get.back(); - Get.toNamed(Routes.pairing); + Get.offAllNamed(Routes.pairing); }, style: ElevatedButton.styleFrom( backgroundColor: Colors.green[600], diff --git a/lib/modules/ota_upgrade/views/ota_upgrade_view.dart b/lib/modules/ota_upgrade/views/ota_upgrade_view.dart index 0597a37b5..18dc7485f 100644 --- a/lib/modules/ota_upgrade/views/ota_upgrade_view.dart +++ b/lib/modules/ota_upgrade/views/ota_upgrade_view.dart @@ -925,7 +925,7 @@ class OtaUpgradeView extends GetView { SizedBox(width: 12.w), Expanded( child: Text( - 'removeEarbudsEnsureBattery'.tr, //取出双耳机,并保证电量均为30%以上的状态 + 'removeEarbudsEnsureBattery'.tr, //取出双耳机,并保证电量均为20%以上的状态 style: TextStyle( fontSize: 13.sp, color: isDarkMode From 5706f21724bd9afac01969bfda565947785cf443 Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Sat, 20 Sep 2025 12:19:53 +0800 Subject: [PATCH 07/55] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E6=96=B0=E6=89=8B?= =?UTF-8?q?=E5=92=8C=E5=A4=9A=E8=AF=AD=E8=A8=80=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/main.dart | 85 +++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 65 insertions(+), 20 deletions(-) diff --git a/lib/main.dart b/lib/main.dart index 5a0941bdf..b558250f6 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -1,5 +1,8 @@ import 'package:flutter/material.dart'; import 'package:flutter/services.dart'; +import 'package:flutter_easyloading/flutter_easyloading.dart'; +import 'package:flutter_intro/flutter_intro.dart'; +import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:get/get.dart'; import 'package:get_storage/get_storage.dart'; import 'package:flutter_dotenv/flutter_dotenv.dart'; @@ -108,27 +111,69 @@ class MainApp extends StatelessWidget { Widget build(BuildContext context) { // 在应用构建时立即初始化深链接处理 _initDeepLinkHandler(); +// 获取保存的主题设置 + final themeMode = ThemeService().getThemeMode(); - // 使用 ScreenUtilInit 包装整个应用 return ScreenUtilInit( - designSize: const Size(375, 812), // 设计稿尺寸,根据你的设计稿调整 - minTextAdapt: true, - splitScreenMode: true, - builder: (context, child) { - return GetMaterialApp( - title: 'VoiTrans', - theme: AppTheme.lightTheme, - darkTheme: AppTheme.darkTheme, - themeMode: ThemeService().getThemeMode(), - initialRoute: Routes.splash, - getPages: AppPages.pages, - initialBinding: InitialBinding(), - translations: AppTranslations(), - locale: Get.deviceLocale, - fallbackLocale: const Locale('en', 'US'), - debugShowCheckedModeBanner: false, - ); - }, - ); + designSize: const Size(375, 812), + minTextAdapt: true, + splitScreenMode: true, + builder: (_, child) { + return GetMaterialApp( + title: 'Voitrans', + theme: AppTheme.lightTheme, + darkTheme: AppTheme.darkTheme, + themeMode: themeMode, // 使用提前获取的主题模式 + initialBinding: InitialBinding(), + initialRoute: Routes.splash, + getPages: AppPages.pages, + debugShowCheckedModeBanner: false, + translations: AppTranslations(), + localizationsDelegates: const [ + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + locale: AppTranslations.deviceLocale(), + fallbackLocale: AppTranslations.getSmartFallbackLocale(), + supportedLocales: AppTranslations.supportedLocales, + builder: EasyLoading.init( + builder: (BuildContext context, Widget? widget) { + EasyLoading.instance.userInteractions = false; + return MediaQuery( + data: MediaQuery.of(context).copyWith( + textScaler: TextScaler.noScaling, + ), + child: Intro( + padding: EdgeInsets.zero, + child: widget!, + ), + ); + }, + ), + ); + }); + + // // 使用 ScreenUtilInit 包装整个应用 + // return ScreenUtilInit( + // designSize: const Size(375, 812), // 设计稿尺寸,根据你的设计稿调整 + // minTextAdapt: true, + // splitScreenMode: true, + // builder: (context, child) { + // return GetMaterialApp( + // title: 'VoiTrans', + // theme: AppTheme.lightTheme, + // darkTheme: AppTheme.darkTheme, + // themeMode: ThemeService().getThemeMode(), + // initialRoute: Routes.splash, + // getPages: AppPages.pages, + // initialBinding: InitialBinding(), + // translations: AppTranslations(), + // locale: Get.deviceLocale, + // fallbackLocale: const Locale('en', 'US'), + // debugShowCheckedModeBanner: false, + // ); + // }, + // ); } } From 9b42e9f4006ab8cf92750155d1ab3ceefdc02ebf Mon Sep 17 00:00:00 2001 From: fdp <1286779656@qq.com> Date: Sat, 20 Sep 2025 13:22:21 +0800 Subject: [PATCH 08/55] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E5=BD=95=E9=9F=B3?= =?UTF-8?q?=E5=AE=8C=E5=90=8E=EF=BC=8C=E6=92=AD=E6=94=BE=E5=A4=96=E6=94=BE?= =?UTF-8?q?=E9=97=AE=E9=A2=98=E3=80=82=E4=BF=AE=E5=A4=8D=E5=BD=95=E9=9F=B3?= =?UTF-8?q?=E4=BB=8E=E8=80=B3=E6=9C=BA=E5=BD=95=E9=9F=B3=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Sources/tools/MicrophoneCapture.swift | 26 +++++++++++++++++++ .../Sources/tools/SimpleAudioReceiver.swift | 16 +++++------- 2 files changed, 33 insertions(+), 9 deletions(-) diff --git a/local_plugins/azure_speech/ios/azure_speech/Sources/tools/MicrophoneCapture.swift b/local_plugins/azure_speech/ios/azure_speech/Sources/tools/MicrophoneCapture.swift index 1d14da379..2bd895836 100644 --- a/local_plugins/azure_speech/ios/azure_speech/Sources/tools/MicrophoneCapture.swift +++ b/local_plugins/azure_speech/ios/azure_speech/Sources/tools/MicrophoneCapture.swift @@ -444,6 +444,32 @@ public class MicrophoneCapture: NSObject { // 安全地处理音频会话 guard let audioSession = audioSession else { return } + do { + try audioSession.setActive(false) + + if self.hasBluetoothDevices { + print("耳机") + // 设置音频会话参数 + try audioSession.setCategory(.playback, + mode: .videoChat, + options: [ + .allowBluetoothA2DP, + .allowBluetooth + ]) // 添加音频优先级控制 + } else { + print("手机") + try audioSession.setCategory(.playback, + mode: .videoChat, + options: [ + .mixWithOthers, + .defaultToSpeaker]) + } + + try audioSession.overrideOutputAudioPort(.none) + try audioSession.setActive(true) + } catch { + print("音频会话配置失败: \(error.localizedDescription)") + } } diff --git a/local_plugins/azure_speech/ios/azure_speech/Sources/tools/SimpleAudioReceiver.swift b/local_plugins/azure_speech/ios/azure_speech/Sources/tools/SimpleAudioReceiver.swift index b37cd56f4..8d3c4f774 100644 --- a/local_plugins/azure_speech/ios/azure_speech/Sources/tools/SimpleAudioReceiver.swift +++ b/local_plugins/azure_speech/ios/azure_speech/Sources/tools/SimpleAudioReceiver.swift @@ -192,15 +192,13 @@ public class SimpleAudioReceiver: NSObject { do { // 确保正在写入状态 guard self._isWriting else { return } - // 设置音频会话参数 - try audioSession.setCategory(.playAndRecord, - mode: .voiceChat, - options: [.defaultToSpeaker, - .mixWithOthers, - .defaultToSpeaker, - .allowBluetoothA2DP, - .allowBluetooth]) - try audioSession.overrideOutputAudioPort(.none) + // 设置音频会话参数 + try audioSession.setCategory(.playAndRecord, + mode: .videoChat, + options: [.allowBluetoothA2DP, // 允许蓝牙耳机,不占用hfp链路 + .mixWithOthers, + .defaultToSpeaker]) // 添加音频优先级控制 + try audioSession.setActive(true) try micCapture.startCapture() try micCapture.audioDataHandler = { data in From 3abdbabcdbaa6bb3a12d8b6c6642028b6dce9841 Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Sat, 20 Sep 2025 15:11:59 +0800 Subject: [PATCH 09/55] =?UTF-8?q?refactor(translation):=20=E7=A7=BB?= =?UTF-8?q?=E9=99=A4=E5=B7=A6=E6=BB=91=E6=89=8B=E5=8A=BF=E5=B9=B6=E7=AE=80?= =?UTF-8?q?=E5=8C=96=E7=BF=BB=E8=AF=91=E6=A8=A1=E5=BC=8F=E9=80=89=E6=8B=A9?= =?UTF-8?q?=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 移除不再使用的左滑手势检测代码,简化翻译视图结构 修改翻译模式选择器,不再自动开始识别 --- .../dialogs/translation_mode_selector.dart | 13 +++--- .../translation/views/translation_view.dart | 45 ++++++++++--------- 2 files changed, 30 insertions(+), 28 deletions(-) diff --git a/lib/modules/translation/views/dialogs/translation_mode_selector.dart b/lib/modules/translation/views/dialogs/translation_mode_selector.dart index 75386980c..9a5b94489 100644 --- a/lib/modules/translation/views/dialogs/translation_mode_selector.dart +++ b/lib/modules/translation/views/dialogs/translation_mode_selector.dart @@ -10,7 +10,7 @@ class TranslationModeSelector { /// 显示翻译模式选择器 static void show(bool isDarkMode) { final controller = Get.find(); - + Get.bottomSheet( BackdropFilter( filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0), @@ -22,7 +22,8 @@ class TranslationModeSelector { } // 构建翻译模式选择器 - 参考会议模块样式 - static Widget _buildTranslationModeSelector(TranslationController controller) { + static Widget _buildTranslationModeSelector( + TranslationController controller) { return Builder( builder: (BuildContext context) { return SafeArea( @@ -39,7 +40,7 @@ class TranslationModeSelector { Get.back(); await controller.stopAll(); await controller.changeTranslationMode('simultaneous'); - await controller.startRecognition(); + //await controller.startRecognition(); }, ), _buildModeItem( @@ -52,7 +53,7 @@ class TranslationModeSelector { Get.back(); await controller.stopAll(); await controller.changeTranslationMode('faceToFace'); - await controller.startRecognition(); + //await controller.startRecognition(); }, ), _buildModeItem( @@ -66,7 +67,7 @@ class TranslationModeSelector { Get.back(); await controller.stopAll(); await controller.changeTranslationMode('audioVideo'); - await controller.startRecognition(); + //await controller.startRecognition(); } : null, ), @@ -81,7 +82,7 @@ class TranslationModeSelector { Get.back(); await controller.stopAll(); await controller.changeTranslationMode('call'); - await controller.startRecognition(); + //await controller.startRecognition(); } : null, ), diff --git a/lib/modules/translation/views/translation_view.dart b/lib/modules/translation/views/translation_view.dart index fc506df20..a5b21aa2a 100644 --- a/lib/modules/translation/views/translation_view.dart +++ b/lib/modules/translation/views/translation_view.dart @@ -343,30 +343,32 @@ class TranslationView extends GetView { } Widget _buildBody(bool isDarkMode) { - return GestureDetector( - onPanUpdate: (details) { - // 检测左滑手势 - if (details.delta.dx < -10) { - // 左滑距离超过10像素时触发 - _handleSwipeLeft(); - } - }, - child: Column( - children: [ - Expanded( - child: _buildTranslationContent(isDarkMode), - ), - _buildControlBar(isDarkMode), - ], - ), + return + // GestureDetector( + // onPanUpdate: (details) { + // // 检测左滑手势 + // if (details.delta.dx < -10) { + // // 左滑距离超过10像素时触发 + // _handleSwipeLeft(); + // } + // }, + // child: + Column( + children: [ + Expanded( + child: _buildTranslationContent(isDarkMode), + ), + _buildControlBar(isDarkMode), + ], ); + // ); } // 处理左滑手势 - void _handleSwipeLeft() async { - await controller.stopAll(); - Get.toNamed(Routes.translationHistory); - } + // void _handleSwipeLeft() async { + // await controller.stopAll(); + // Get.toNamed(Routes.translationHistory); + // } Widget _buildTranslationContent(bool isDarkMode) { return Obx(() { @@ -584,8 +586,7 @@ class TranslationView extends GetView { if (isRecognizing) { controller.stopAll(); } else { - // 弹出翻译模式选择器 - TranslationModeSelector.show(isDarkMode); + controller.startRecognition(); } }, child: Container( From a0dad4d1158402f954eebdfd424cfbc282421d6c Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Sat, 20 Sep 2025 15:48:44 +0800 Subject: [PATCH 10/55] =?UTF-8?q?feat(translation):=20=E6=B7=BB=E5=8A=A0?= =?UTF-8?q?=E7=BF=BB=E8=AF=91=E6=9D=A1=E7=9B=AE=E9=95=BF=E6=8C=89=E8=8F=9C?= =?UTF-8?q?=E5=8D=95=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 为翻译条目添加长按菜单,支持复制、删除单条和全部记录 - 实现触觉反馈增强用户体验 - 添加确认对话框防止误操作 --- .../translation/views/translation_view.dart | 497 +++++++++++++++--- 1 file changed, 412 insertions(+), 85 deletions(-) diff --git a/lib/modules/translation/views/translation_view.dart b/lib/modules/translation/views/translation_view.dart index a5b21aa2a..6b9ed4eab 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'; @@ -414,103 +416,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 +886,168 @@ class TranslationView extends GetView { }); } + /// 处理翻译条目长按事件 + 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 _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 { } } + // 显示删除所有记录确认对话框 + 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( From 1726148a0dbac3ab0421a4771f06ea7b7bae110e Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Sat, 20 Sep 2025 16:14:09 +0800 Subject: [PATCH 11/55] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E7=94=BB=E5=83=8F=E9=80=BB=E8=BE=91=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../chat_api/ChatApiService.kt | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/ChatApiService.kt b/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/ChatApiService.kt index 83e3a3522..d4a792234 100644 --- a/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/ChatApiService.kt +++ b/local_plugins/chat_api/android/src/main/kotlin/com/yunqiinnovation/chat_api/ChatApiService.kt @@ -364,7 +364,7 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor toolCalls.clear() // 检查是否包含图片,决定使用哪个模型 val currentModel = if (containsImage(messages)) visionModel else model - Log.d(TAG, "liwei-------------------------开始AI 对话 0") + // Log.d(TAG, "liwei-------------------------开始AI 对话 0") currentStreamJob = launch { try { // 转换消息格式 @@ -374,22 +374,22 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor Log.e("ChatApiService", "转换消息格式失败: ${e.message}", e) throw e } - Log.d(TAG, "liwei-------------------------开始AI 对话 1") + // Log.d(TAG, "liwei-------------------------开始AI 对话 1") // 直接获取工具列表 val tools = getOpenAiTools() - Log.d(TAG, "liwei-------------------------开始AI 对话 2") + // Log.d(TAG, "liwei-------------------------开始AI 对话 2") // 构建请求 if (currentModel.isEmpty()) { Log.e("ChatApiService", "模型名称为空") throw IllegalArgumentException("模型名称不能为空") } - Log.d(TAG, "liwei-------------------------开始AI 对话 3") + // Log.d(TAG, "liwei-------------------------开始AI 对话 3") if (chatMessages.isEmpty()) { Log.e("ChatApiService", "消息列表为空") throw IllegalArgumentException("消息列表不能为空") } // Log.e("ChatApiService", "AI解析工具列表: ${tools}") - Log.d(TAG, "liwei-------------------------开始AI 对话 4") + // Log.d(TAG, "liwei-------------------------开始AI 对话 4") val chatsFlow = try { val chatCompletionRequest = ChatCompletionRequest( model = ModelId(currentModel), @@ -398,17 +398,17 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor temperature = 0.7, tools = if (tools.isNotEmpty()) tools else null ) - Log.d(TAG, "liwei-------------------------开始AI 对话 5") + // Log.d(TAG, "liwei-------------------------开始AI 对话 5") if (openAI == null) { Log.e("ChatApiService", "openAI对象为null") throw IllegalStateException("OpenAI客户端未初始化") } - Log.e("ChatApiService", "liwei---------,发送AI请求 model:${currentModel} messages:${chatMessages}") + // Log.e("ChatApiService", "liwei---------,发送AI请求 model:${currentModel} messages:${chatMessages}") // 添加超时控制,10秒超时 val flow = withTimeout(10.seconds) { openAI!!.chatCompletions(chatCompletionRequest) } - Log.d(TAG, "liwei-------------------------开始AI 对话 6") + // Log.d(TAG, "liwei-------------------------开始AI 对话 6") flow } catch (e: Exception) { Log.e("ChatApiService", "创建ChatCompletionRequest或调用chatCompletions失败: ${e.message}", e) @@ -425,7 +425,7 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor } var replyContext = ""; - Log.d(TAG, "liwei-------------------------开始AI 对话 7") + // Log.d(TAG, "liwei-------------------------开始AI 对话 7") chatsFlow.collect { result -> // val usage = result.usage // if (usage != null){ //发现流式不返回这歌数据 @@ -436,19 +436,19 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor // throw CancellationException("Session cancelled") return@collect } - Log.d(TAG, "liwei-------------------------开始AI 对话 7-1") + // Log.d(TAG, "liwei-------------------------开始AI 对话 7-1") val choice = result.choices.firstOrNull() if (choice == null) { Log.w("ChatApiService", "[Session: $sessionid] choice为null") return@collect } - Log.d(TAG, "liwei-------------------------开始AI 对话 7-2") + // Log.d(TAG, "liwei-------------------------开始AI 对话 7-2") val delta = choice.delta if (delta == null) { Log.w("ChatApiService", "[Session: $sessionid] delta为null") return@collect } - Log.d(TAG, "liwei-------------------------开始AI 对话 7-3") + // Log.d(TAG, "liwei-------------------------开始AI 对话 7-3") if (delta.toolCalls == null){ // 处理普通文本内容 delta.content?.let { content -> @@ -461,7 +461,7 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor } } } - Log.d(TAG, "liwei-------------------------开始AI 对话 7-4") + // Log.d(TAG, "liwei-------------------------开始AI 对话 7-4") // 收集工具调用信息 delta.toolCalls?.forEach { toolCall -> try { @@ -513,10 +513,10 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor val promptTokens = estimatePromptTokens(chatMessages); //计算上传token val completionTokens = roughTokenCount(replyContext); //计算上传token getSessionCallback(sessionid)?.onUsage(sessionid,promptTokens,completionTokens,promptTokens+completionTokens) - Log.d(TAG, "liwei-------------------------开始AI 对话 8") + // Log.d(TAG, "liwei-------------------------开始AI 对话 8") // 完成处理 if (sessionid == currSessionId) { - Log.d(TAG, "liwei-------------------------开始AI 对话 9") + // Log.d(TAG, "liwei-------------------------开始AI 对话 9") // 检查是否有工具调用需要处理 val hasToolCalls = processToolCalls(sessionid) if (!hasToolCalls) { @@ -528,7 +528,7 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor clearCurrentSession() } } - Log.d(TAG, "liwei-------------------------开始AI 对话 10") + // Log.d(TAG, "liwei-------------------------开始AI 对话 10") } catch (e: CancellationException) { // 会话被取消,不需要处理 Log.d(TAG, "Session $sessionid cancelled") @@ -652,7 +652,7 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor mapOf("context" to "Tool not found: $functionName") } if (sessionid == currSessionId) { - if (functionName != "set_user_profile_field"){ +// if (functionName != "set_user_profile_field"){ // 处理结果 try { if(!isError){ @@ -674,14 +674,14 @@ class ChatApiService(private val context: android.content.Context? = null) : Cor functionCall = functionCall, functionResult = gson.toJson(result) ) - }else{ - try { - getSessionCallback(sessionid)?.onComplete(sessionid) - } catch (ex: Exception) { - Log.e(TAG, "onComplete回调异常: ${ex.message}", ex) - } - clearCurrentSession() - } +// }else{ +// try { +// getSessionCallback(sessionid)?.onComplete(sessionid) +// } catch (ex: Exception) { +// Log.e(TAG, "onComplete回调异常: ${ex.message}", ex) +// } +// clearCurrentSession() +// } } } } catch (e: Exception) { From ef4a26e95a22e70a475fe7a1b85ea8158eaf000e Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Sat, 20 Sep 2025 16:15:30 +0800 Subject: [PATCH 12/55] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E8=81=8A=E5=A4=A9?= =?UTF-8?q?=E5=AF=BC=E8=88=AA=E7=95=8C=E9=9D=A2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../ios/chat_api/Sources/chat_api/ChatApiService.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/local_plugins/chat_api/ios/chat_api/Sources/chat_api/ChatApiService.swift b/local_plugins/chat_api/ios/chat_api/Sources/chat_api/ChatApiService.swift index 341279185..87b87fff3 100644 --- a/local_plugins/chat_api/ios/chat_api/Sources/chat_api/ChatApiService.swift +++ b/local_plugins/chat_api/ios/chat_api/Sources/chat_api/ChatApiService.swift @@ -520,7 +520,7 @@ private func processToolCalls(_ sessionId:String) async -> Bool { } if sessionId == self.currSessionId { - if (firstToolCall.name != "set_user_profile_field"){ +// if (firstToolCall.name != "set_user_profile_field"){ // 处理结果 if (!isError){ self.getSessionCallback(sessionId)?.onFunctionCallResult( @@ -541,10 +541,10 @@ private func processToolCalls(_ sessionId:String) async -> Bool { functionCall: functionCall, functionResult: self.jsonToString(result) ?? "{}" ) - }else{ - self.getSessionCallback(sessionId)?.onComplete(sessionId) - self.clearCurrentSession() - } +// }else{ +// self.getSessionCallback(sessionId)?.onComplete(sessionId) +// self.clearCurrentSession() +// } } } } catch { From 058681bce7f60a032399ee26c86c6adf850acfaf Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Sat, 20 Sep 2025 17:07:39 +0800 Subject: [PATCH 13/55] =?UTF-8?q?feat(translation):=20=E6=96=B0=E5=A2=9E?= =?UTF-8?q?=E7=BF=BB=E8=AF=91=E9=95=BF=E6=8C=89=E8=8F=9C=E5=8D=95=E5=92=8C?= =?UTF-8?q?=E5=88=A0=E9=99=A4=E7=A1=AE=E8=AE=A4=E5=AF=B9=E8=AF=9D=E6=A1=86?= =?UTF-8?q?=E7=BB=84=E4=BB=B6?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将翻译长按菜单和删除确认对话框逻辑抽离为独立组件 重构翻译视图中的相关代码,提高代码复用性和可维护性 --- .../views/dialogs/delete_confirm_dialog.dart | 172 +++++++++++ .../dialogs/translation_long_press_menu.dart | 187 ++++++++++++ .../translation/views/translation_view.dart | 280 ++---------------- 3 files changed, 379 insertions(+), 260 deletions(-) create mode 100644 lib/modules/translation/views/dialogs/delete_confirm_dialog.dart create mode 100644 lib/modules/translation/views/dialogs/translation_long_press_menu.dart 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 6b9ed4eab..28888303e 100644 --- a/lib/modules/translation/views/translation_view.dart +++ b/lib/modules/translation/views/translation_view.dart @@ -18,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'; @@ -893,161 +894,17 @@ class TranslationView extends GetView { 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( + TranslationLongPressMenu.show( context: context, - position: position, - color: isDarkMode ? AppColors.cardBackground : Colors.white, - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(12.r), - ), - items: _buildTranslationMenuItems(context, isDarkMode, item), + isDarkMode: isDarkMode, + details: details, + item: item, + onCopy: (item) => TranslationLongPressMenu.copyTranslationItem(item), + onDeleteItem: (item) => _deleteTranslationItem(item), + onClearHistory: _showClearHistoryConfirmDialog, ); } - /// 构建翻译条目弹出菜单项 - List _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; @@ -1181,86 +1038,26 @@ class TranslationView extends GetView { } // 显示删除所有记录确认对话框 - void _showDeleteAllConfirmDialog() { - final isDarkMode = Theme.of(Get.context!).brightness == Brightness.dark; + 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: 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, - ), - ), - ), - ], - ), - ), - ], + content: Text( + 'clearHistoryConfirm'.tr, + style: TextStyle( + fontSize: 16.sp, + color: isDarkMode ? Colors.grey[300] : Colors.black87, + ), ), actions: [ TextButton( @@ -1275,11 +1072,12 @@ class TranslationView extends GetView { ), TextButton( onPressed: () { + // 清除历史记录 + controller.clearTranslationHistory(); Get.back(); - _deleteAllTranslationHistory(); }, child: Text( - '确认删除', + 'confirm'.tr, style: TextStyle( fontSize: 16.sp, color: Colors.red, @@ -1295,44 +1093,6 @@ class TranslationView extends GetView { ); } - // 删除所有翻译历史记录的具体实现 - 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( From f572a924811b067e7053f8b5f13255e1c674ca66 Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Sat, 20 Sep 2025 17:43:55 +0800 Subject: [PATCH 14/55] =?UTF-8?q?feat(ota=5Fcheck=5Fservice):=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E6=9A=97=E9=BB=91=E6=A8=A1=E5=BC=8F=E6=94=AF=E6=8C=81?= =?UTF-8?q?=E5=B9=B6=E6=94=B9=E8=BF=9B=E5=AF=B9=E8=AF=9D=E6=A1=86=E6=A0=B7?= =?UTF-8?q?=E5=BC=8F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 为OTA检查服务的升级对话框添加暗黑模式适配,改进文本显示和样式配置。包括: - 根据当前主题模式动态调整颜色 - 优化标题文本布局防止溢出 - 增强按钮边框和背景色适配 --- lib/data/services/ota_check_service.dart | 89 ++++++++++++++---------- 1 file changed, 53 insertions(+), 36 deletions(-) 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 { From c0f0c6434b05c9e7f72166a13a34a0d1fc086e94 Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Sat, 20 Sep 2025 18:20:41 +0800 Subject: [PATCH 15/55] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E5=AF=BC=E8=88=AA?= =?UTF-8?q?=E5=AE=B9=E9=94=99=E5=A4=84=E7=90=86=E5=92=8C=E9=94=99=E8=AF=AF?= =?UTF-8?q?=E6=B6=88=E9=99=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/modules/agent/controllers/agent_controller.dart | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) 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('错误', '启动导航失败'); From 8246400db7c1343ae02db329f4b2189c8f9580db Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Sat, 20 Sep 2025 19:31:22 +0800 Subject: [PATCH 16/55] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E5=AE=9A=E4=BD=8D?= =?UTF-8?q?=E6=9D=83=E9=99=90=E6=A3=80=E6=B5=8B=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../location_service/LocationService.kt | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) 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 } From af1f510864fffa521ff49339e5ac692365fad385 Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Sat, 20 Sep 2025 19:36:07 +0800 Subject: [PATCH 17/55] =?UTF-8?q?refactor(settings):=20=E4=BC=98=E5=8C=96?= =?UTF-8?q?=E8=AF=AD=E8=A8=80=E8=AE=BE=E7=BD=AE=E9=80=BB=E8=BE=91=EF=BC=8C?= =?UTF-8?q?=E7=9B=B4=E6=8E=A5=E4=BD=BF=E7=94=A8Get.deviceLocale=EF=BC=8C?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20fix(permissions):=20=E7=A7=BB=E9=99=A4?= =?UTF-8?q?=E6=9D=83=E9=99=90=E6=88=90=E5=8A=9F=E6=97=B6=E7=9A=84=E6=B6=88?= =?UTF-8?q?=E6=81=AF=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit chore(splash): 注释掉隐私协议和用户协议的点击事件 --- lib/core/translations/app_translations.dart | 4 +- .../controllers/permissions_controller.dart | 2 +- .../controllers/settings_controller.dart | 8 +++- lib/modules/splash/views/splash_view.dart | 46 +++++++++---------- 4 files changed, 33 insertions(+), 27 deletions(-) diff --git a/lib/core/translations/app_translations.dart b/lib/core/translations/app_translations.dart index cf89b513a..1d8c86957 100644 --- a/lib/core/translations/app_translations.dart +++ b/lib/core/translations/app_translations.dart @@ -186,11 +186,11 @@ class AppTranslations extends Translations { return fallbackLocale; } - return _getMatchedLocale(systemLocale); + return getMatchedLocale(systemLocale); } /// 智能匹配系统语言到支持的语言 - static Locale _getMatchedLocale(Locale systemLocale) { + static Locale getMatchedLocale(Locale systemLocale) { String languageCode = systemLocale.languageCode.toLowerCase(); String? countryCode = systemLocale.countryCode?.toUpperCase(); String? scriptCode = systemLocale.scriptCode; diff --git a/lib/modules/permissions/controllers/permissions_controller.dart b/lib/modules/permissions/controllers/permissions_controller.dart index 95013946f..fb69e9b1d 100644 --- a/lib/modules/permissions/controllers/permissions_controller.dart +++ b/lib/modules/permissions/controllers/permissions_controller.dart @@ -184,7 +184,7 @@ class PermissionsController extends GetxController { ); if (hasPermission) { - _showSuccessMessage(); + // _showSuccessMessage(); if (Platform.isIOS && permission == Permission.calendarFullAccess) { if (_isCalendarFullAccess == null) { _storage.write('record_permission_calendar', true); diff --git a/lib/modules/settings/controllers/settings_controller.dart b/lib/modules/settings/controllers/settings_controller.dart index 2508f928d..2011eccfa 100644 --- a/lib/modules/settings/controllers/settings_controller.dart +++ b/lib/modules/settings/controllers/settings_controller.dart @@ -301,9 +301,11 @@ class SettingsController extends GetxController { } } + /// 设置语言 Future setLanguage(LanguageModel data) async { try { Locale? targetLocale; + // print('lxm--设置语言: ${data.value}'); if (data.value != 'system') { // 用户手动选择的语言 @@ -311,7 +313,11 @@ class SettingsController extends GetxController { targetLocale = Locale(languageList[0], languageList[1]); } else { // 跟随系统语言,使用智能匹配 - targetLocale = AppTranslations.deviceLocale(); + targetLocale = Get.deviceLocale; + if (targetLocale != null) { + // 智能匹配系统语言到支持的语言 + targetLocale = AppTranslations.getMatchedLocale(targetLocale); + } } if (targetLocale != null) { diff --git a/lib/modules/splash/views/splash_view.dart b/lib/modules/splash/views/splash_view.dart index 71457e362..175606b35 100644 --- a/lib/modules/splash/views/splash_view.dart +++ b/lib/modules/splash/views/splash_view.dart @@ -225,7 +225,7 @@ class SplashView extends GetView { children: [ // 将勾选框和文本包装在 GestureDetector 中 GestureDetector( - onTap: () => controller.togglePrivacyAcceptance(), + //onTap: () => controller.togglePrivacyAcceptance(), behavior: HitTestBehavior.opaque, // 确保透明区域也能响应点击 child: Row( children: [ @@ -267,17 +267,17 @@ class SplashView extends GetView { mainAxisAlignment: MainAxisAlignment.center, children: [ GestureDetector( - onTap: () { - String url = 'https://www.voitrans.net/doc/yhxy.html'; // 用户协议链接 - if (Get.locale?.languageCode != 'zh' || - Get.locale?.countryCode != 'CN') { - url = 'https://www.voitrans.net/doc/yhxy_en.html'; // 用户协议英文链接 - } - Get.to(() => WebViewPage( - url: url, - title: 'userAgreement'.tr, - )); - }, + // onTap: () { + // String url = 'https://www.voitrans.net/doc/yhxy.html'; // 用户协议链接 + // if (Get.locale?.languageCode != 'zh' || + // Get.locale?.countryCode != 'CN') { + // url = 'https://www.voitrans.net/doc/yhxy_en.html'; // 用户协议英文链接 + // } + // Get.to(() => WebViewPage( + // url: url, + // title: 'userAgreement'.tr, + // )); + // }, child: Text( 'userAgreement'.tr, // 《用户协议》 style: TextStyle( @@ -294,19 +294,19 @@ class SplashView extends GetView { ), ), GestureDetector( - onTap: () { - String url = 'https://www.voitrans.net/doc/ysxy.html'; // 隐私政策链接 + // onTap: () { + // String url = 'https://www.voitrans.net/doc/ysxy.html'; // 隐私政策链接 - if (Get.locale?.languageCode != 'zh' || - Get.locale?.countryCode != 'CN') { - url = 'https://www.voitrans.net/doc/ysxy_en.html'; // 隐私政策英文链接 - } + // if (Get.locale?.languageCode != 'zh' || + // Get.locale?.countryCode != 'CN') { + // url = 'https://www.voitrans.net/doc/ysxy_en.html'; // 隐私政策英文链接 + // } - Get.to(() => WebViewPage( - url: url, - title: 'privacyPolicy'.tr, - )); - }, + // Get.to(() => WebViewPage( + // url: url, + // title: 'privacyPolicy'.tr, + // )); + // }, child: Text( 'privacyPolicy'.tr, // 《隐私政策》 style: TextStyle( From 1fca554bdaa20889f4860f276c8563a004545a23 Mon Sep 17 00:00:00 2001 From: fdp <1286779656@qq.com> Date: Sat, 20 Sep 2025 19:43:43 +0800 Subject: [PATCH 18/55] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E7=BF=BB=E8=AF=91?= =?UTF-8?q?=E5=BD=95=E9=9F=B3=E4=B8=8D=E4=BA=86=EF=BC=8C=E5=BC=82=E5=B8=B8?= =?UTF-8?q?=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../azure_speech/AzureAsrHelper.kt | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt b/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt index 8fc25365a..630abbdf3 100644 --- a/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt +++ b/local_plugins/azure_speech/android/src/main/kotlin/com/yunqiinnovation/azure_speech/AzureAsrHelper.kt @@ -547,19 +547,20 @@ class AzureAsrHelper(private val context: Context) { Log.e(tag, "启动录音前台服务失败: ${e.message}") } } - - // Initialize if not already done - if (!audioStream.isInitialized()) { - audioStream.initAudioRecord() - } - audioStream.setupStream() if (audioStream.recordfile == null) { audioStream.recordfile = RecordFile() } - + if (!audioStream.isContinuousRecognitionActive) { // 启动音频处理 Log.i(tag, "开启录音:audioSourceType${audioSourceType}") + // Initialize if not already done + if (!audioStream.isInitialized()) { + audioStream.initAudioRecord() + } + audioStream.setupStream() + + audioStream.startAudioRecord( when (audioSourceType) { AudioSourceType.MICROPHONE -> SimpleAudioReceiver.AudioSourceType.MICROPHONE AudioSourceType.EXTERNAL -> SimpleAudioReceiver.AudioSourceType.EXTERNAL @@ -635,7 +636,7 @@ class AzureAsrHelper(private val context: Context) { } Log.i(tag, "关闭录音:") audioStream.recordfile ?: return - + if (!audioStream.isContinuousRecognitionActive) { audioStream.stopMicrophoneCapture() From f25f6746b83649af9645a9f430c0557bc8aa68e5 Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Sat, 20 Sep 2025 20:08:33 +0800 Subject: [PATCH 19/55] =?UTF-8?q?fix(bluetooth=5Fmanager):=20=E5=9C=A8?= =?UTF-8?q?=E9=85=8D=E5=AF=B9=E9=A1=B5=E9=9D=A2=E6=97=B6=E4=B8=8D=E8=B7=B3?= =?UTF-8?q?=E8=BD=AC=E8=87=B3=E9=85=8D=E5=AF=B9=E8=B7=AF=E7=94=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 添加对当前路由是否为配对页面的检查,防止在已处于配对页面时重复跳转 --- lib/data/services/bluetooth_manager.dart | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/data/services/bluetooth_manager.dart b/lib/data/services/bluetooth_manager.dart index 08fff314c..ee28c94fa 100644 --- a/lib/data/services/bluetooth_manager.dart +++ b/lib/data/services/bluetooth_manager.dart @@ -76,7 +76,9 @@ class BluetoothManager extends GetxService { currentDeviceRx.value = newDevice; // 检查当前是否在OTA升级界面,如果是则不跳转 - if (!_isInOtaUpgradePage() && !_bleManager.isConnected) { + if (!_isInOtaUpgradePage() && + !_bleManager.isConnected && + Get.currentRoute != Routes.pairing) { Get.toNamed(Routes.pairing, arguments: newDevice); } else { Logger.info('当前在OTA升级界面,跳过配对页面跳转'); From a32080d5b831ee443eff3ed140dae4f29c8e426e Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Sat, 20 Sep 2025 20:24:57 +0800 Subject: [PATCH 20/55] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E5=AF=BC=E8=88=AA?= =?UTF-8?q?=E9=80=BB=E8=BE=91=E4=BF=AE=E5=A4=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../agent/controllers/agent_controller.dart | 13 +- .../agent_service/AgentService.kt | 276 ++++++++++++++++-- 2 files changed, 268 insertions(+), 21 deletions(-) diff --git a/lib/modules/agent/controllers/agent_controller.dart b/lib/modules/agent/controllers/agent_controller.dart index 5c9a5c683..2852e4f69 100644 --- a/lib/modules/agent/controllers/agent_controller.dart +++ b/lib/modules/agent/controllers/agent_controller.dart @@ -810,6 +810,7 @@ class AgentController extends GetxController with WidgetsBindingObserver { if (maps_route_navigation is Map) { final card_navigation = maps_route_navigation["card_navigation"]; + final isinternal = metaResult["isinternal"] ?? false; if (card_navigation is Map) { //导航 final start = card_navigation["start"] as String; @@ -826,9 +827,15 @@ class AgentController extends GetxController with WidgetsBindingObserver { Future.delayed(Duration(seconds: 2), () { // Logger.i(TAG, 'liwei-------- 打开导航页面: $start $end '); // 2秒后执行的代码 - - openNavigation(false, start, destination, end_latitude, - end_longitude, ""); + final navigationMode = + AppConfig.env('APP_NAVIGATION_MODE') ?? + 'external_auto'; + if (navigationMode == 'internal_gaode' || isinternal) { + _navigationManager.open( + start, "$end_longitude,$end_latitude", "driving"); + } + // openNavigation(false, start, destination, end_latitude, + // end_longitude, ""); }); } } diff --git a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt b/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt index c4cab4819..702ee4098 100644 --- a/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt +++ b/local_plugins/agent_service/android/src/main/kotlin/com/yunqiinnovation/agent_service/AgentService.kt @@ -1884,7 +1884,7 @@ object AgentService : CoroutineScope { metadata[toolname] = meta // 现在可以赋值 metadata["notreply"] = notreply // 现在可以赋值 - val metaStr = JSONObject(metadata as Map<*, *>).toString() + // Log.d(TAG, "mcp调用结果: $metaStr") if (meta.has("card_music")) { //音乐卡片 val cardMusic = meta.getJSONObject("card_music") @@ -1967,13 +1967,24 @@ object AgentService : CoroutineScope { }else if (meta.has("card_navigation")) { //导航服务 val cardNavigation = meta.getJSONObject("card_navigation") val start = cardNavigation.optString("start", "") - val end = cardNavigation.optString("end", "") - processNavigation(start, end); + val destination = cardNavigation.optString("destination", "") + val end_longitude = cardNavigation.optString("end_longitude", "") + val end_latitude = cardNavigation.optString("end_latitude", "") + // final start = card_navigation["start"] as String; + // final destination = + // card_navigation["destination"] as String; + // final end_longitude = + // card_navigation["end_longitude"] as String; + // final end_latitude = + // card_navigation["end_latitude"] as String; + var isinternal = processNavigation(start, destination,end_latitude,end_longitude,"driving"); + metadata["isinternal"] = isinternal // 是否是内部导航 }else if (toolname == "map_weather") { //导航服务 iscard = true }else if (toolname == "maps_weather") { //导航服务 iscard = true } + val metaStr = JSONObject(metadata as Map<*, *>).toString() return mapOf("meta" to metaStr, "broadcast" to broadcast, "notreply" to notreply,"iscard" to iscard); } } catch (e: Exception) { @@ -2055,25 +2066,254 @@ object AgentService : CoroutineScope { } /** * 处理导航 - * 将ai返回的音乐连接 在后台启动播放 - * @param musicUrl 图片文件路径 - * @param name 歌曲名称,默认为空 - * @param singer 歌手,默认为false + * 启动导航服务到指定目的地 + * @param start 起点地址 + * @param destination 目的地地址 + * @param endlatitude 目的地纬度 + * @param endlongitude 目的地经度 + * @param mode 导航模式:driving(驾车)、transit(公交)、walking(步行)、cycling(骑行) * @return 是否成功开始处理 */ - fun processNavigation(start: String, end: String) { - if (navigationMode == "internal_gaode"){ - // 如果 playlist 中有有效的歌曲,开始播放 - if (!start.isNullOrEmpty() && !end.isNullOrEmpty()) { - NavigationServiceHelper.startNavigation( - context, - "start", - startpos = start, - endpos = end - ) + fun processNavigation(start: String, destination: String, + endlatitude: String, endlongitude: String, mode: String):Boolean { + Log.d(TAG, "processNavigation: $start -> $destination ($endlongitude,$endlatitude,), mode: $mode") + + if (destination.isEmpty()) { + Log.i(TAG, "启动导航服务失败:目的地为空") + return false + } + + // 获取导航模式配置,默认为自动选择 + val configNavigationMode = navigationMode.ifEmpty { "external_auto" } + Log.d(TAG, "导航模式配置: $configNavigationMode") + + when (configNavigationMode) { + "internal_gaode" -> { + // 使用内置高德导航 + navigateWithInternalGaode(start, destination, endlatitude, endlongitude, mode) + return true + } + "external_baidu" -> { + // 使用外部百度地图 + navigateWithBaiduMap(destination, endlatitude, endlongitude, mode) + return false + } + "external_gaode" -> { + // 使用外部高德地图 + navigateWithGaodeMap(destination, endlatitude, endlongitude, mode) + return false + } + "external_google" -> { + // 使用外部谷歌地图 + navigateWithGoogleMap(destination, mode) + return false + } + else -> { + // 自动选择导航应用,优先级:百度 > 高德 > 谷歌 > 内置高德 + return autoSelectNavigation(start, destination, endlatitude, endlongitude, mode) + } + } + } + + /** + * 使用内置高德导航 + */ + private fun navigateWithInternalGaode(start: String, destination: String, + endlatitude: String, endlongitude: String, mode: String) { + try { + val endpos = if (endlatitude.isNotEmpty() && endlongitude.isNotEmpty()) { + "$endlongitude,$endlatitude" } else { - Log.i(TAG, "启动导航服务失败") + destination } + + NavigationServiceHelper.startNavigation( + context, + "start", + startpos = start, + endpos = "$endlongitude,$endlatitude" + ) + Log.i(TAG, "启动内置高德导航服务成功") + } catch (e: Exception) { + Log.e(TAG, "启动内置高德导航失败: ${e.message}", e) + } + } + + /** + * 使用百度地图导航 + */ + private fun navigateWithBaiduMap(destination: String, endlatitude: String, + endlongitude: String, mode: String) { + try { + val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0) + val src = packageInfo.packageName + + // 百度地图导航模式映射 + val baiduMode = when (mode) { + "transit" -> "transit" + "walking" -> "walking" + "cycling" -> "riding" + else -> "driving" + } + + // 构建目的地参数 + val destinationParam = if (endlatitude.isNotEmpty() && endlongitude.isNotEmpty()) { + "name:$destination|latlng:$endlatitude,$endlongitude" + } else { + destination + } + + val uri = android.net.Uri.parse("baidumap://map/direction?" + + "destination=$destinationParam&" + + "coord_type=gcj02&" + + "mode=$baiduMode&" + + "src=$src") + + val intent = Intent(Intent.ACTION_VIEW, uri).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK + } + + if (isAppInstalled("com.baidu.BaiduMap")) { + context.startActivity(intent) + Log.i(TAG, "已启动百度地图导航") + } else { + Log.w(TAG, "百度地图未安装") + throw Exception("百度地图未安装") + } + } catch (e: Exception) { + Log.e(TAG, "启动百度地图失败: ${e.message}", e) + // 回退到其他导航方式 + navigateWithGaodeMap(destination, endlatitude, endlongitude, mode) + } + } + + /** + * 使用高德地图导航 + */ + private fun navigateWithGaodeMap(destination: String, endlatitude: String, + endlongitude: String, mode: String) { + try { + val packageInfo = context.packageManager.getPackageInfo(context.packageName, 0) + val src = packageInfo.packageName + + // 高德地图导航模式映射 + val gaodeMode = when (mode) { + "transit" -> "1" + "walking" -> "2" + "cycling" -> "3" + else -> "0" + } + + val uriBuilder = StringBuilder("androidamap://route/plan?") + if (endlatitude.isNotEmpty() && endlongitude.isNotEmpty()) { + uriBuilder.append("dlat=$endlatitude&") + uriBuilder.append("dlon=$endlongitude&") + } + uriBuilder.append("dname=$destination&") + uriBuilder.append("dev=0&") + uriBuilder.append("t=$gaodeMode&") + uriBuilder.append("sourceApplication=$src") + + val uri = android.net.Uri.parse(uriBuilder.toString()) + val intent = Intent(Intent.ACTION_VIEW, uri).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK + } + + if (isAppInstalled("com.autonavi.minimap")) { + context.startActivity(intent) + Log.i(TAG, "已启动高德地图导航") + } else { + Log.w(TAG, "高德地图未安装") + throw Exception("高德地图未安装") + } + } catch (e: Exception) { + Log.e(TAG, "启动高德地图失败: ${e.message}", e) + // 回退到其他导航方式 + navigateWithGoogleMap(destination, mode) + } + } + + /** + * 使用谷歌地图导航 + */ + private fun navigateWithGoogleMap(destination: String, mode: String) { + try { + // 谷歌地图导航模式映射 + val googleMode = when (mode) { + "transit" -> "transit" + "walking" -> "walking" + "cycling" -> "bicycling" + else -> "driving" + } + + // 尝试使用Google Maps应用 + val appUri = android.net.Uri.parse("comgooglemaps://?saddr=Current+Location&daddr=$destination&directionsmode=$googleMode") + val appIntent = Intent(Intent.ACTION_VIEW, appUri).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK + } + + if (isAppInstalled("com.google.android.apps.maps")) { + context.startActivity(appIntent) + Log.i(TAG, "已启动谷歌地图导航") + } else { + // 回退到网页版 + val webUri = android.net.Uri.parse("https://www.google.com/maps/dir/?api=1&origin=Current+Location&destination=$destination&travelmode=$googleMode") + val webIntent = Intent(Intent.ACTION_VIEW, webUri).apply { + flags = Intent.FLAG_ACTIVITY_NEW_TASK + } + context.startActivity(webIntent) + Log.i(TAG, "已启动谷歌地图网页版导航") + } + } catch (e: Exception) { + Log.e(TAG, "启动谷歌地图失败: ${e.message}", e) + // 最后回退到内置导航 + navigateWithInternalGaode("", destination, "", "", mode) + } + } + + /** + * 自动选择导航应用 + */ + private fun autoSelectNavigation(start: String, destination: String, + endlatitude: String, endlongitude: String, mode: String):Boolean { + Log.d(TAG, "自动选择导航应用") + + // 1. 优先尝试百度地图 + if (isAppInstalled("com.baidu.BaiduMap")) { + Log.d(TAG, "自动选择:使用百度地图") + navigateWithBaiduMap(destination, endlatitude, endlongitude, mode) + return false + } + + // 2. 其次尝试高德地图 + if (isAppInstalled("com.autonavi.minimap")) { + Log.d(TAG, "自动选择:使用高德地图") + navigateWithGaodeMap(destination, endlatitude, endlongitude, mode) + return false + } + + // 3. 然后尝试谷歌地图 + if (isAppInstalled("com.google.android.apps.maps")) { + Log.d(TAG, "自动选择:使用谷歌地图") + navigateWithGoogleMap(destination, mode) + return false + } + + // 4. 最后使用内置高德导航 + Log.d(TAG, "自动选择:使用内置高德导航") + navigateWithInternalGaode(start, destination, endlatitude, endlongitude, mode) + return true + } + + /** + * 检查应用是否已安装 + */ + private fun isAppInstalled(packageName: String): Boolean { + return try { + context.packageManager.getPackageInfo(packageName, 0) + true + } catch (e: Exception) { + false } } From db8e3b49427730e8be713fe2c5a7617045d8e5e7 Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Sat, 20 Sep 2025 20:38:20 +0800 Subject: [PATCH 21/55] =?UTF-8?q?fix(=E6=9D=83=E9=99=90):=20=E4=BC=98?= =?UTF-8?q?=E5=8C=96Android=E5=AD=98=E5=82=A8=E6=9D=83=E9=99=90=E8=AF=B7?= =?UTF-8?q?=E6=B1=82=E9=80=BB=E8=BE=91=EF=BC=8C=E9=80=82=E9=85=8DAndroid?= =?UTF-8?q?=2011+=E7=89=88=E6=9C=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修改存储权限请求逻辑,针对Android 11及以上版本不再请求存储权限 使用DeviceInfoPlugin获取Android版本信息进行判断 将权限提示文本改为使用国际化翻译 --- .../translation_list_controller.dart | 46 +++++++++++++------ 1 file changed, 33 insertions(+), 13 deletions(-) diff --git a/lib/modules/translation/controllers/translation_list_controller.dart b/lib/modules/translation/controllers/translation_list_controller.dart index 45181b869..55539706c 100644 --- a/lib/modules/translation/controllers/translation_list_controller.dart +++ b/lib/modules/translation/controllers/translation_list_controller.dart @@ -1,5 +1,6 @@ import 'dart:io'; import 'package:audio_waveforms/audio_waveforms.dart'; +import 'package:device_info_plus/device_info_plus.dart'; import 'package:flutter_easyloading/flutter_easyloading.dart'; import 'package:get/get.dart'; import 'package:just_audio/just_audio.dart'; @@ -93,24 +94,43 @@ class TranslationListController extends GetxController { // 请求存储权限 void _requestStoragePermission() async { - // final BuildContext context = Get.overlayContext!; - - PermissionUtil.instance - .requestPermission( - // context: context, - permissionType: Permission.storage, - permissionName: "存储", - explanationText: "我们需要访问存储权限,以便您可以选择并上传音频文件。", - permanentDenialText: "检测到您已永久拒绝存储权限。如需继续使用此功能,请前往应用设置中手动开启存储权限。", - ) - .then((granted) { + // 只在Android 10及以下版本请求存储权限 + if (Platform.isAndroid) { + try { + final deviceInfo = DeviceInfoPlugin(); + final androidInfo = await deviceInfo.androidInfo; + final sdkVersion = androidInfo.version.sdkInt; + + // Android 11+ (API 30+) 不需要存储权限 + if (sdkVersion >= 30) { + Logger.d("Permission", "Android 11+版本,无需请求存储权限"); + //return true; // 直接返回true,表示可以使用存储 + } + } catch (e) { + Logger.e("Permission", "获取Android版本信息失败: $e"); + // 如果获取版本失败,为了兼容性还是请求权限 + } + } + + try { + final bool granted = await PermissionUtil.instance.requestPermission( + permissionType: Permission.storage, + permissionName: 'storagePermission'.tr, + explanationText: 'storagePermissionExplanation'.tr, + permanentDenialText: 'storagePermissionPermanentDenial'.tr, + ); + if (granted) { Logger.d("Permission", "用户授予了存储权限"); + //return true; } else { Logger.d("Permission", "用户未授予存储权限"); - // 可以提示用户无法进行文件操作 + //return false; } - }); + } catch (e) { + Logger.e("Permission", "请求存储权限时出错: $e"); + //return false; + } } // 添加方法:获取音频时长 From c74f18ab2b5bbfc945a679ac2984e2bf064935eb Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Sat, 20 Sep 2025 21:11:59 +0800 Subject: [PATCH 22/55] =?UTF-8?q?=E4=BC=98=E5=8C=96iOS=E5=8D=A1=E7=89=87?= =?UTF-8?q?=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../agent_service/AgentServiceImpl.swift | 317 +++++++++++++++++- 1 file changed, 303 insertions(+), 14 deletions(-) diff --git a/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift b/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift index 6283446dc..6b2415e07 100644 --- a/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift +++ b/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift @@ -1,4 +1,5 @@ import Foundation +import UIKit import AVFoundation import ble_service import location_service @@ -856,7 +857,8 @@ private func handleJSONDictionary(_ jsonDict: [String: Any], toolName: String) - if let cardMusiclist = jsonDict["data"] as? [[String: Any]], toolName == "music_searchandplay_songs" { processMusicListCard(cardMusiclist) } else if let cardNavigation = jsonDict["card_navigation"] as? [String: Any] { - processNavigationCard(cardNavigation) + let isinternal = processNavigationCard(cardNavigation) + metadata["isinternal"] = isinternal } else if let cardQQMusiclist = jsonDict["data"] as? [[String: Any]], toolName == "qqmusic_searchandplay_songs" { processQQMusicPlayList(cardQQMusiclist) } else if toolName == "maps_weather" { @@ -982,11 +984,293 @@ private func jsonToString(_ json: [String: Any]) -> String? { } // 处理导航卡片 - private func processNavigationCard(_ cardNavigation: [String: Any]) { + private func processNavigationCard(_ cardNavigation: [String: Any]) -> Bool { let start = cardNavigation["start"] as? String ?? "" - let end = cardNavigation["end"] as? String ?? "" - // 实际处理导航逻辑 - print("启动导航: 从 \(start) 到 \(end)") + let destination = cardNavigation["end"] as? String ?? "" + let endlatitude = cardNavigation["endlatitude"] as? String ?? "" + let endlongitude = cardNavigation["endlongitude"] as? String ?? "" + let mode = cardNavigation["mode"] as? String ?? "driving" + + print("启动导航: 从 \(start) 到 \(destination), 坐标: \(endlatitude),\(endlongitude), 模式: \(mode)") + + // 根据导航模式选择不同的导航方式 + switch mode { + case "internal_gaode": + // 使用内置高德导航 + // startInternalNavigation(start: start, destination: destination, + // latitude: endlatitude, longitude: endlongitude, mode: mode) + return true + case "external_baidu": + // 使用外部百度地图 + openBaiduMap(destination: destination, latitude: endlatitude, + longitude: endlongitude, mode: mode) + return false + case "external_gaode": + // 使用外部高德地图 + openGaodeMap(destination: destination, latitude: endlatitude, + longitude: endlongitude, mode: mode) + return false + case "external_google": + // 使用外部谷歌地图 + openGoogleMap(destination: destination, mode: mode) + return false + default: + // 默认使用自动选择 + return autoSelectNavigation(start: start, destination: destination, + latitude: endlatitude, longitude: endlongitude, mode: mode) + } + } + + // 启动内置高德导航 + private func startInternalNavigation(start: String, destination: String, + latitude: String, longitude: String, mode: String) { + // 发送导航事件通知 + NotificationCenter.default.post(name: .navigationdEvent, object: nil, userInfo: [ + "start": start, + "destination": destination, + "latitude": latitude, + "longitude": longitude, + "mode": mode + ]) + print("启动内置导航: 从 \(start) 到 \(destination)") + } + + // 打开百度地图 + private func openBaiduMap(destination: String, latitude: String, longitude: String, mode: String) { + guard let bundleId = Bundle.main.bundleIdentifier else { + print("无法获取应用包名") + return + } + + let src = "ios.\(bundleId)" + + // 百度地图导航模式映射 + let baiduMode: String + switch mode { + case "transit": + baiduMode = "transit" // 公交 + case "walking": + baiduMode = "walking" // 步行 + case "cycling": + baiduMode = "riding" // 骑行 + case "driving": + fallthrough + default: + baiduMode = "driving" // 驾车 + } + + var components = URLComponents() + components.scheme = "baidumap" + components.host = "map" + components.path = "/direction" + components.queryItems = [ + URLQueryItem(name: "destination", value: "name:\(destination)|latlng:\(latitude),\(longitude)"), + URLQueryItem(name: "coord_type", value: "gcj02"), + URLQueryItem(name: "mode", value: baiduMode), + URLQueryItem(name: "src", value: src) + ] + + guard let url = components.url else { + print("构建百度地图URL失败") + return + } + + print("百度导航链接: \(url.absoluteString)") + + if UIApplication.shared.canOpenURL(url) { + UIApplication.shared.open(url, options: [:]) { success in + if success { + print("成功打开百度地图") + } else { + print("打开百度地图失败") + } + } + } else { + print("百度地图未安装") + // 可以在这里添加提示用户安装百度地图的逻辑 + } + } + + // 打开高德地图 + private func openGaodeMap(destination: String, latitude: String, longitude: String, mode: String) { + guard let bundleId = Bundle.main.bundleIdentifier else { + print("无法获取应用包名") + return + } + + let src = "ios.\(bundleId)" + + // 导航模式映射 + let gaodeMode: String + switch mode { + case "transit": + gaodeMode = "1" // 公交 + case "walking": + gaodeMode = "2" // 步行 + case "cycling": + gaodeMode = "3" // 骑行 + case "driving": + fallthrough + default: + gaodeMode = "0" // 驾车 + } + + var components = URLComponents() + components.scheme = "amapuri" + components.host = "route" + components.path = "/plan" + components.queryItems = [ + URLQueryItem(name: "dlat", value: latitude), + URLQueryItem(name: "dlon", value: longitude), + URLQueryItem(name: "dname", value: destination), + URLQueryItem(name: "dev", value: "0"), // 0: gcj02 + URLQueryItem(name: "t", value: gaodeMode), + URLQueryItem(name: "sourceApplication", value: src) + ] + + guard let url = components.url else { + print("构建高德地图URL失败") + return + } + + print("高德导航链接: \(url.absoluteString)") + + if UIApplication.shared.canOpenURL(url) { + UIApplication.shared.open(url, options: [:]) { success in + if success { + print("成功打开高德地图") + } else { + print("打开高德地图失败") + } + } + } else { + print("高德地图未安装") + // 可以在这里添加提示用户安装高德地图的逻辑 + } + } + + // 打开谷歌地图 + private func openGoogleMap(destination: String, mode: String) { + // 方案1: 尝试使用Google Maps应用的URL scheme + var components = URLComponents() + components.scheme = "comgooglemaps" + components.queryItems = [ + URLQueryItem(name: "saddr", value: "Current Location"), + URLQueryItem(name: "daddr", value: destination), + URLQueryItem(name: "directionsmode", value: mode) + ] + + if let appUrl = components.url, UIApplication.shared.canOpenURL(appUrl) { + UIApplication.shared.open(appUrl, options: [:]) { success in + if success { + print("成功打开谷歌地图应用") + } else { + print("打开谷歌地图应用失败") + } + } + return + } + + // 方案2: 使用网页版谷歌地图 + let webUrlString = "https://www.google.com/maps/dir/?api=1&origin=Current+Location&destination=\(destination)&travelmode=\(mode)" + if let webUrl = URL(string: webUrlString) { + UIApplication.shared.open(webUrl, options: [:]) { success in + if success { + print("成功打开谷歌地图网页版") + } else { + print("打开谷歌地图网页版失败") + } + } + } + } + + // 打开苹果地图 + private func openAppleMap(destination: String, mode: String) { + var components = URLComponents() + components.scheme = "maps" + components.queryItems = [ + URLQueryItem(name: "daddr", value: destination), + URLQueryItem(name: "dirflg", value: mode == "driving" ? "d" : "r") // d=驾车, r=公交 + ] + + guard let url = components.url else { + print("构建苹果地图URL失败") + return + } + + if UIApplication.shared.canOpenURL(url) { + UIApplication.shared.open(url, options: [:]) { success in + if success { + print("成功打开苹果地图") + } else { + print("打开苹果地图失败") + } + } + } else { + print("苹果地图不可用") + } + } + + // 自动选择导航应用 + private func autoSelectNavigation(start: String, destination: String, + latitude: String, longitude: String, mode: String) -> Bool { + // 1. 优先尝试百度地图 + if canOpenBaiduMap() { + print("自动选择:使用百度地图") + openBaiduMap(destination: destination, latitude: latitude, longitude: longitude, mode: mode) + return false + } + + // 2. 其次尝试高德地图 + if canOpenGaodeMap() { + print("自动选择:使用高德地图") + openGaodeMap(destination: destination, latitude: latitude, longitude: longitude, mode: mode) + return false + } + + // 3. 然后尝试谷歌地图 + if canOpenGoogleMap() { + print("自动选择:使用谷歌地图") + openGoogleMap(destination: destination, mode: mode) + return false + } + + // 4. 尝试苹果地图 + if canOpenAppleMap() { + print("自动选择:使用苹果地图") + openAppleMap(destination: destination, mode: mode) + return false + } + + // 5. 最后尝试内置高德导航 + print("自动选择:使用内置导航") + // startInternalNavigation(start: start, destination: destination, + // latitude: latitude, longitude: longitude, mode: mode) + return true + } + + // 检查是否可以打开百度地图 + private func canOpenBaiduMap() -> Bool { + guard let url = URL(string: "baidumap://") else { return false } + return UIApplication.shared.canOpenURL(url) + } + + // 检查是否可以打开高德地图 + private func canOpenGaodeMap() -> Bool { + guard let url = URL(string: "amapuri://") else { return false } + return UIApplication.shared.canOpenURL(url) + } + + // 检查是否可以打开谷歌地图 + private func canOpenGoogleMap() -> Bool { + guard let url = URL(string: "comgooglemaps://") else { return false } + return UIApplication.shared.canOpenURL(url) + } + + // 检查是否可以打开苹果地图 + private func canOpenAppleMap() -> Bool { + guard let url = URL(string: "maps://") else { return false } + return UIApplication.shared.canOpenURL(url) } // 后台播放单曲 @@ -1294,6 +1578,7 @@ class ChatApiStreamCallback: StreamCallback { private var responseBuilder = "" private var metadata = "" private var userMetadata:[String: Any] = [:] + private var iscard = false; private var reply = true; //是否回复 private var broadcast = true //是否播发 @@ -1443,15 +1728,19 @@ class ChatApiStreamCallback: StreamCallback { metadata = resultmap["meta"] as? String ?? "" broadcast = resultmap["broadcast"] as? Bool ?? true reply = !(resultmap["notreply"] as? Bool ?? true) - agentService.sendEvent(name: "function_call_result", data: [ - "function_call": functionCall, - "result": functionCallResult, - "meta":metadata, - ]) - os_log("收到函数调用结果! broadcast: %{public}@ reply: %{public}@ %{metadata}@", - log: agentService.logger, - type: .info, - "\(broadcast)", "\(reply)", "\(metadata)") + iscard = !(resultmap["iscard"] as? Bool ?? false) + if (iscard){ + agentService.sendEvent(name: "function_call_result", data: [ + "function_call": functionCall, + "result": functionCallResult, + "meta":metadata, + ]) + os_log("收到函数调用结果! broadcast: %{public}@ reply: %{public}@ %{metadata}@", + log: agentService.logger, + type: .info, + "\(broadcast)", "\(reply)", "\(metadata)") + } + }catch{ print("liwei--------------- AI Call onFunctionCallResult 异常: \(error)") } From 843933f45e05c81467a96346e384284f7211258d Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Sat, 20 Sep 2025 21:43:06 +0800 Subject: [PATCH 23/55] =?UTF-8?q?refactor(translation):=20=E4=BC=98?= =?UTF-8?q?=E5=8C=96=E6=9D=83=E9=99=90=E6=A3=80=E6=9F=A5=E9=80=BB=E8=BE=91?= =?UTF-8?q?=E5=92=8C=E6=82=AC=E6=B5=AE=E6=8C=89=E9=92=AE=E6=8B=96=E6=8B=BD?= =?UTF-8?q?=E5=A4=84=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修复Android 11+版本存储权限检查直接返回的问题 将悬浮按钮拖拽逻辑提取为独立方法并增加安全区域检查 在调试模式下显示opus解码测试选项 --- lib/modules/settings/views/settings_view.dart | 37 +++++++++++-------- .../translation_list_controller.dart | 2 +- .../widgets/draggable_floating_button.dart | 36 +++++++++++------- 3 files changed, 45 insertions(+), 30 deletions(-) diff --git a/lib/modules/settings/views/settings_view.dart b/lib/modules/settings/views/settings_view.dart index 42b9daa3d..d36801018 100644 --- a/lib/modules/settings/views/settings_view.dart +++ b/lib/modules/settings/views/settings_view.dart @@ -515,22 +515,27 @@ class SettingsView extends GetView { // ? Colors.white.withOpacity(0.1) // : Colors.grey[200]), // //BLE测试 - // _buildSimpleNavigationSetting( - // title: 'opus解码测试', - // subtitle: '测试opus解码', - // icon: Icons.bluetooth_searching, - // iconBgColor: isDarkMode - // ? Colors.green[900]!.withOpacity(0.3) - // : Colors.green[100]!, - // iconColor: - // isDarkMode ? Colors.green[300]! : Colors.green[600]!, - // titleColor: isDarkMode ? Colors.white : null, - // subtitleColor: isDarkMode ? Colors.white70 : null, - // onTap: () { - // Get.toNamed(Routes.opusTest); - // }, - // isDarkMode: isDarkMode, - // ), + + Obx(() => controller.showDebugInfo.value + ? _buildSimpleNavigationSetting( + title: 'opus解码测试', + subtitle: '测试opus解码', + icon: Icons.bluetooth_searching, + iconBgColor: isDarkMode + ? Colors.green[900]!.withOpacity(0.3) + : Colors.green[100]!, + iconColor: isDarkMode + ? Colors.green[300]! + : Colors.green[600]!, + titleColor: isDarkMode ? Colors.white : null, + subtitleColor: isDarkMode ? Colors.white70 : null, + onTap: () { + Get.toNamed(Routes.opusTest); + }, + isDarkMode: isDarkMode, + ) + : const SizedBox.shrink()), + // Divider( // height: 1, // color: isDarkMode diff --git a/lib/modules/translation/controllers/translation_list_controller.dart b/lib/modules/translation/controllers/translation_list_controller.dart index 55539706c..2d4bffc19 100644 --- a/lib/modules/translation/controllers/translation_list_controller.dart +++ b/lib/modules/translation/controllers/translation_list_controller.dart @@ -104,7 +104,7 @@ class TranslationListController extends GetxController { // Android 11+ (API 30+) 不需要存储权限 if (sdkVersion >= 30) { Logger.d("Permission", "Android 11+版本,无需请求存储权限"); - //return true; // 直接返回true,表示可以使用存储 + return; // 直接返回,表示可以使用存储 } } catch (e) { Logger.e("Permission", "获取Android版本信息失败: $e"); diff --git a/lib/modules/translation/views/widgets/draggable_floating_button.dart b/lib/modules/translation/views/widgets/draggable_floating_button.dart index 98b25adb3..80fa508f0 100644 --- a/lib/modules/translation/views/widgets/draggable_floating_button.dart +++ b/lib/modules/translation/views/widgets/draggable_floating_button.dart @@ -55,19 +55,7 @@ class DraggableFloatingButton extends StatelessWidget { ), ), onDragEnd: (details) { - // 计算新位置(相对于屏幕右下角) - final screenSize = MediaQuery.of(Get.context!).size; - final newRight = screenSize.width - details.offset.dx - 56.w; - final newBottom = screenSize.height - details.offset.dy - 56.w; - - // 边界检查,确保按钮不会超出屏幕 - final clampedRight = newRight.clamp(0.0, screenSize.width - 56.w); - final clampedBottom = - newBottom.clamp(0.0, screenSize.height - 56.w); - - // 保存新位置 - controller.saveFloatingButtonPosition( - Offset(clampedRight, clampedBottom)); + _handleDragEnd(context, details); }, child: GestureDetector( onTap: () => _showTranslationModeSelector(isDarkMode), @@ -96,6 +84,28 @@ class DraggableFloatingButton extends StatelessWidget { )); } + /// 处理拖拽结束事件 + void _handleDragEnd(BuildContext context, DraggableDetails details) { + final screenSize = MediaQuery.of(context).size; + final appBarHeight = Scaffold.of(context).appBarMaxHeight ?? kToolbarHeight; + final statusBarHeight = MediaQuery.of(context).padding.top; + final safeAreaTop = statusBarHeight + appBarHeight; + + // 计算新位置(相对于屏幕右下角) + final newRight = screenSize.width - details.offset.dx - 56.w; + final newBottom = screenSize.height - details.offset.dy - 56.w; + + // 边界检查,确保按钮不会超出屏幕或被AppBar遮盖 + final clampedRight = newRight.clamp(0.0, screenSize.width - 56.w); + final clampedBottom = newBottom.clamp( + 0.0, // 底部边界 + screenSize.height - safeAreaTop - 56.w, // 顶部边界(避免被AppBar遮盖) + ); + + // 保存新位置 + controller.saveFloatingButtonPosition(Offset(clampedRight, clampedBottom)); + } + /// 显示翻译模式选择器 void _showTranslationModeSelector(bool isDarkMode) { TranslationModeSelector.show(isDarkMode); From 4cd3987f105d601041df082960ec684253e6cfce Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Mon, 22 Sep 2025 14:45:46 +0800 Subject: [PATCH 24/55] =?UTF-8?q?style(ui):=20=E4=BC=98=E5=8C=96=E4=B8=BB?= =?UTF-8?q?=E9=A2=98=E9=A2=9C=E8=89=B2=E9=80=82=E9=85=8D=E5=92=8C=E4=BB=A3?= =?UTF-8?q?=E7=A0=81=E5=8F=AF=E8=AF=BB=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 调整翻译视图和个人资料页面的颜色方案,使其更好地适配深色/浅色模式 统一颜色变量定义,提高代码可维护性 --- lib/modules/profile/views/profile_view.dart | 19 +++++++-------- .../translation/views/translation_view.dart | 23 +++++++++++-------- 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/lib/modules/profile/views/profile_view.dart b/lib/modules/profile/views/profile_view.dart index 4dbf73dd3..1e9d8a268 100644 --- a/lib/modules/profile/views/profile_view.dart +++ b/lib/modules/profile/views/profile_view.dart @@ -750,11 +750,11 @@ class EditProfileView extends GetView { title: 'clean'.tr, // 清理缓存 icon: Icons.cleaning_services, iconBgColor: isDarkMode - ? Colors.red[900]!.withOpacity(0.3) - : Colors.red[50]!, - iconColor: isDarkMode ? Colors.red[300]! : Colors.red[600]!, - titleColor: - isDarkMode ? Colors.red[300]! : Colors.red[600]!, + ? Colors.blue[900]!.withOpacity(0.3) + : Colors.blue[50]!, + iconColor: + isDarkMode ? Colors.blue[300]! : Colors.blue[600]!, + titleColor: isDarkMode ? Colors.white : Colors.black87, onTap: () { // 退出登录确认对话框 Get.dialog( @@ -816,11 +816,12 @@ class EditProfileView extends GetView { title: 'logout'.tr, // 退出登录 icon: Icons.logout, iconBgColor: isDarkMode - ? Colors.red[900]!.withOpacity(0.3) - : Colors.red[50]!, - iconColor: isDarkMode ? Colors.red[300]! : Colors.red[600]!, + ? Colors.orange[900]!.withOpacity(0.3) + : Colors.orange[50]!, + iconColor: + isDarkMode ? Colors.orange[300]! : Colors.orange[600]!, titleColor: - isDarkMode ? Colors.red[300]! : Colors.red[600]!, + isDarkMode ? Colors.orange[300]! : Colors.orange[600]!, onTap: () { // 退出登录确认对话框 Get.dialog( diff --git a/lib/modules/translation/views/translation_view.dart b/lib/modules/translation/views/translation_view.dart index 28888303e..e3bc1fc0c 100644 --- a/lib/modules/translation/views/translation_view.dart +++ b/lib/modules/translation/views/translation_view.dart @@ -419,6 +419,15 @@ class TranslationView extends GetView { 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, @@ -431,20 +440,14 @@ class TranslationView extends GetView { padding: const EdgeInsets.all(12), decoration: BoxDecoration( color: item.isIntermediate - ? Colors.grey.withOpacity(0.1) // 中间结果使用浅灰色底色 + ? Colors.grey.withOpacity(isDarkMode ? 0.15 : 0.1) // 中间结果 : (item.sourceLanguageCode == controller.sourceLanguageCode.value - ? Theme.of(context) - .colorScheme - .primary - .withOpacity(0.08) // 源语言匹配时使用主题色底色 - : Theme.of(context) - .colorScheme - .secondary - .withOpacity(0.08)), // 否则使用次要色底色 + ? selfColor // 自己说的话 + : otherColor), // 对方说的话 borderRadius: BorderRadius.circular(12), border: Border.all( color: item.isIntermediate - ? Colors.grey.withOpacity(0.2) + ? Colors.grey.withOpacity(isDarkMode ? 0.3 : 0.2) : Colors.transparent, width: 1, ), From 6841a2a7c60fcb635ea7a9323163ffbf2d1891b8 Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Mon, 22 Sep 2025 15:12:51 +0800 Subject: [PATCH 25/55] =?UTF-8?q?feat(translation):=20=E4=B8=BA=E7=BF=BB?= =?UTF-8?q?=E8=AF=91=E6=A8=A1=E5=BC=8F=E9=80=89=E6=8B=A9=E5=99=A8=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0=E9=94=9A=E7=82=B9=E5=8A=A8=E7=94=BB=E6=95=88=E6=9E=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 修改翻译模式选择器的显示方式,从底部弹窗改为从按钮位置展开的动画效果 计算拖拽按钮位置作为锚点,实现更自然的用户交互体验 --- .../dialogs/translation_mode_selector.dart | 57 ++++++++++++++++--- .../translation/views/translation_view.dart | 11 +++- .../widgets/draggable_floating_button.dart | 9 ++- 3 files changed, 67 insertions(+), 10 deletions(-) diff --git a/lib/modules/translation/views/dialogs/translation_mode_selector.dart b/lib/modules/translation/views/dialogs/translation_mode_selector.dart index 9a5b94489..c9d4ed3c7 100644 --- a/lib/modules/translation/views/dialogs/translation_mode_selector.dart +++ b/lib/modules/translation/views/dialogs/translation_mode_selector.dart @@ -8,16 +8,57 @@ import '../../controllers/translation_controller.dart'; /// 翻译模式选择对话框 class TranslationModeSelector { /// 显示翻译模式选择器 - static void show(bool isDarkMode) { + static void show(bool isDarkMode, {Offset? anchor}) { final controller = Get.find(); - Get.bottomSheet( - BackdropFilter( - filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0), - child: _buildTranslationModeSelector(controller), - ), - isScrollControlled: true, - backgroundColor: Colors.transparent, + // 计算缩放对齐点(-1..1)。如果未提供 anchor,则从底部中间展开 + Alignment alignment = Alignment.bottomCenter; + if (anchor != null) { + final Size screenSize = MediaQuery.of(Get.context!).size; + final double ax = (anchor.dx / screenSize.width) * 2 - 1; + final double ay = (anchor.dy / screenSize.height) * 2 - 1; + alignment = Alignment(ax, ay); + } + + Get.generalDialog( + barrierLabel: 'translation_mode_selector', + barrierDismissible: true, + barrierColor: Colors.black.withOpacity(0.35), + transitionDuration: const Duration(milliseconds: 250), + pageBuilder: (context, animation, secondaryAnimation) { + return const SizedBox.shrink(); + }, + transitionBuilder: (context, animation, secondaryAnimation, child) { + final curved = CurvedAnimation( + parent: animation, + curve: Curves.easeOutCubic, + reverseCurve: Curves.easeInCubic); + return Stack( + children: [ + // 模糊背景 + Positioned.fill( + child: BackdropFilter( + filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0), + child: Container(color: Colors.transparent), + ), + ), + // 将缩放应用在一个全屏容器上,以便 alignment 以屏幕为参考 + ScaleTransition( + alignment: alignment, + scale: Tween(begin: 0.0, end: 1.0).animate(curved), + child: FadeTransition( + opacity: curved, + child: SizedBox.expand( + child: Align( + alignment: Alignment.bottomCenter, + child: _buildTranslationModeSelector(controller), + ), + ), + ), + ), + ], + ); + }, ); } diff --git a/lib/modules/translation/views/translation_view.dart b/lib/modules/translation/views/translation_view.dart index e3bc1fc0c..a9a792433 100644 --- a/lib/modules/translation/views/translation_view.dart +++ b/lib/modules/translation/views/translation_view.dart @@ -144,7 +144,16 @@ class TranslationView extends GetView { title: Obx(() => GestureDetector( onTap: () { // 点击标题时弹出翻译模式选择器 - TranslationModeSelector.show(isDarkMode); + // 从拖拽按钮位置作为锚点打开 + final pos = controller.floatingButtonPosition.value; + final Size screen = MediaQuery.of(Get.context!).size; + // 已保存的是距右、下的偏移,需要换算为屏幕坐标中的中心点 + final double buttonSize = 56.w; + final double left = screen.width - pos.dx - buttonSize; + final double top = screen.height - pos.dy - buttonSize; + final Offset anchor = + Offset(left + buttonSize / 2, top + buttonSize / 2); + TranslationModeSelector.show(isDarkMode, anchor: anchor); }, child: Text( controller.currentModeTitle.value.tr, diff --git a/lib/modules/translation/views/widgets/draggable_floating_button.dart b/lib/modules/translation/views/widgets/draggable_floating_button.dart index 80fa508f0..bef3dbef1 100644 --- a/lib/modules/translation/views/widgets/draggable_floating_button.dart +++ b/lib/modules/translation/views/widgets/draggable_floating_button.dart @@ -108,6 +108,13 @@ class DraggableFloatingButton extends StatelessWidget { /// 显示翻译模式选择器 void _showTranslationModeSelector(bool isDarkMode) { - TranslationModeSelector.show(isDarkMode); + // 使用保存的位置换算按钮中心点(与标题点击一致) + final pos = controller.floatingButtonPosition.value; // 距右、下的偏移 + final Size screen = MediaQuery.of(Get.context!).size; + final double buttonSize = 56.w; + final double left = screen.width - pos.dx - buttonSize; + final double top = screen.height - pos.dy - buttonSize; + final Offset anchor = Offset(left + buttonSize / 2, top + buttonSize / 2); + TranslationModeSelector.show(isDarkMode, anchor: anchor); } } From e0d5fabf3bf71121f829c5233783d3685a8b5a86 Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Mon, 22 Sep 2025 16:54:31 +0800 Subject: [PATCH 26/55] =?UTF-8?q?refactor(translation):=20=E9=87=8D?= =?UTF-8?q?=E6=9E=84=E7=BF=BB=E8=AF=91=E6=A8=A1=E5=BC=8F=E9=80=89=E6=8B=A9?= =?UTF-8?q?=E5=99=A8=E7=9A=84=E6=98=BE=E7=A4=BA=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 移除手动计算锚点的冗余代码,统一从悬浮按钮位置展开 优化选择器动画和背景处理,提升用户体验 --- .../controllers/translation_controller.dart | 24 +++++++ .../dialogs/translation_mode_selector.dart | 68 +++++++++++-------- .../translation/views/translation_view.dart | 18 ++--- .../widgets/draggable_floating_button.dart | 10 +-- 4 files changed, 74 insertions(+), 46 deletions(-) diff --git a/lib/modules/translation/controllers/translation_controller.dart b/lib/modules/translation/controllers/translation_controller.dart index cfb187c98..9b0ee6374 100644 --- a/lib/modules/translation/controllers/translation_controller.dart +++ b/lib/modules/translation/controllers/translation_controller.dart @@ -23,6 +23,7 @@ import '../../../data/services/ble_manager.dart'; import 'record_timer_controller.dart'; import 'translation_history_controller.dart'; import '../models/translation_models.dart'; +import '../views/dialogs/translation_mode_selector.dart'; /// 音频输出设备枚举 enum AudioOutputDevice { @@ -285,6 +286,17 @@ class TranslationController extends GetxController with WidgetsBindingObserver { // 监听应用生命周期:当应用切到后台/失焦时兜底重置 activeSpeaker WidgetsBinding.instance.addObserver(this); + + // 延迟显示模式选择器,确保页面已经完全加载 + // Future.delayed(const Duration(milliseconds: 100), () { + // _showModeSelector(); + // }); + //确保关键服务初始化完成 + WidgetsBinding.instance.addPostFrameCallback((_) async { + // 确保上下文有效 + if (Get.context == null || !Get.context!.mounted) return; + _showModeSelector(); + }); } /// 预先检查权限 @@ -339,6 +351,18 @@ class TranslationController extends GetxController with WidgetsBindingObserver { } } + /// 显示模式选择器 + void _showModeSelector() { + try { + final isDarkMode = Get.theme.brightness == Brightness.dark; + + // 直接使用现有的翻译模式选择器组件 + TranslationModeSelector.show(isDarkMode); + } catch (e) { + Logger.error('显示模式选择器失败: $e'); + } + } + /// 保存悬浮按钮位置 void saveFloatingButtonPosition(Offset position) { floatingButtonPosition.value = position; diff --git a/lib/modules/translation/views/dialogs/translation_mode_selector.dart b/lib/modules/translation/views/dialogs/translation_mode_selector.dart index c9d4ed3c7..a1fe0f0f6 100644 --- a/lib/modules/translation/views/dialogs/translation_mode_selector.dart +++ b/lib/modules/translation/views/dialogs/translation_mode_selector.dart @@ -11,20 +11,25 @@ class TranslationModeSelector { static void show(bool isDarkMode, {Offset? anchor}) { final controller = Get.find(); - // 计算缩放对齐点(-1..1)。如果未提供 anchor,则从底部中间展开 - Alignment alignment = Alignment.bottomCenter; - if (anchor != null) { - final Size screenSize = MediaQuery.of(Get.context!).size; - final double ax = (anchor.dx / screenSize.width) * 2 - 1; - final double ay = (anchor.dy / screenSize.height) * 2 - 1; - alignment = Alignment(ax, ay); - } + // 总是从悬浮按钮位置展开,不依赖传入的anchor参数 + final Size screenSize = MediaQuery.of(Get.context!).size; + final Offset floatingButtonPos = controller.floatingButtonPosition.value; + + // 计算悬浮按钮的中心点位置(从右下角偏移转换为屏幕坐标) + final double buttonSize = 56.0; // 悬浮按钮的大小 + final double buttonCenterX = screenSize.width - floatingButtonPos.dx - buttonSize / 2; + final double buttonCenterY = screenSize.height - floatingButtonPos.dy - buttonSize / 2; + + // 计算缩放对齐点(-1..1) + final double ax = (buttonCenterX / screenSize.width) * 2 - 1; + final double ay = (buttonCenterY / screenSize.height) * 2 - 1; + final Alignment alignment = Alignment(ax, ay); Get.generalDialog( barrierLabel: 'translation_mode_selector', - barrierDismissible: true, - barrierColor: Colors.black.withOpacity(0.35), - transitionDuration: const Duration(milliseconds: 250), + barrierDismissible: true, // 允许点击背景关闭 + barrierColor: Colors.transparent, // 设置为透明,让自定义模糊层处理点击 + transitionDuration: const Duration(milliseconds: 300), pageBuilder: (context, animation, secondaryAnimation) { return const SizedBox.shrink(); }, @@ -35,17 +40,22 @@ class TranslationModeSelector { reverseCurve: Curves.easeInCubic); return Stack( children: [ - // 模糊背景 - Positioned.fill( + // 修复:将模糊背景放在可点击区域 + GestureDetector( + onTap: () => Get.back(), // 添加点击关闭功能 child: BackdropFilter( filter: ImageFilter.blur(sigmaX: 10.0, sigmaY: 10.0), - child: Container(color: Colors.transparent), + child: Container( + color: isDarkMode + ? Colors.black.withOpacity(0.55) + : Colors.grey.withOpacity(0.95), // 添加半透明背景 + ), ), ), - // 将缩放应用在一个全屏容器上,以便 alignment 以屏幕为参考 + // 内容区域 ScaleTransition( alignment: alignment, - scale: Tween(begin: 0.0, end: 1.0).animate(curved), + scale: Tween(begin: 0.1, end: 1.0).animate(curved), child: FadeTransition( opacity: curved, child: SizedBox.expand( @@ -127,19 +137,19 @@ class TranslationModeSelector { } : null, ), - SizedBox(height: 24.w), - GestureDetector( - onTap: () => Get.back(), - child: Container( - width: 42, - height: 42, - decoration: BoxDecoration( - color: Colors.black.withValues(alpha: 0.8), - borderRadius: BorderRadius.circular(50), - ), - child: const Icon(Icons.close, color: Colors.white), - ), - ), + SizedBox(height: 64.w), + // GestureDetector( + // onTap: () => Get.back(), + // child: Container( + // width: 42, + // height: 42, + // decoration: BoxDecoration( + // color: Colors.black.withValues(alpha: 0.8), + // borderRadius: BorderRadius.circular(50), + // ), + // child: const Icon(Icons.close, color: Colors.white), + // ), + // ), if (Platform.isAndroid) SizedBox(height: 20.h), ], ), diff --git a/lib/modules/translation/views/translation_view.dart b/lib/modules/translation/views/translation_view.dart index a9a792433..b7fc30206 100644 --- a/lib/modules/translation/views/translation_view.dart +++ b/lib/modules/translation/views/translation_view.dart @@ -145,15 +145,15 @@ class TranslationView extends GetView { onTap: () { // 点击标题时弹出翻译模式选择器 // 从拖拽按钮位置作为锚点打开 - final pos = controller.floatingButtonPosition.value; - final Size screen = MediaQuery.of(Get.context!).size; - // 已保存的是距右、下的偏移,需要换算为屏幕坐标中的中心点 - final double buttonSize = 56.w; - final double left = screen.width - pos.dx - buttonSize; - final double top = screen.height - pos.dy - buttonSize; - final Offset anchor = - Offset(left + buttonSize / 2, top + buttonSize / 2); - TranslationModeSelector.show(isDarkMode, anchor: anchor); + // final pos = controller.floatingButtonPosition.value; + // final Size screen = MediaQuery.of(Get.context!).size; + // // 已保存的是距右、下的偏移,需要换算为屏幕坐标中的中心点 + // final double buttonSize = 56.w; + // final double left = screen.width - pos.dx - buttonSize; + // final double top = screen.height - pos.dy - buttonSize; + // final Offset anchor = + // Offset(left + buttonSize / 2, top + buttonSize / 2); + TranslationModeSelector.show(isDarkMode); }, child: Text( controller.currentModeTitle.value.tr, diff --git a/lib/modules/translation/views/widgets/draggable_floating_button.dart b/lib/modules/translation/views/widgets/draggable_floating_button.dart index bef3dbef1..9c4935a21 100644 --- a/lib/modules/translation/views/widgets/draggable_floating_button.dart +++ b/lib/modules/translation/views/widgets/draggable_floating_button.dart @@ -108,13 +108,7 @@ class DraggableFloatingButton extends StatelessWidget { /// 显示翻译模式选择器 void _showTranslationModeSelector(bool isDarkMode) { - // 使用保存的位置换算按钮中心点(与标题点击一致) - final pos = controller.floatingButtonPosition.value; // 距右、下的偏移 - final Size screen = MediaQuery.of(Get.context!).size; - final double buttonSize = 56.w; - final double left = screen.width - pos.dx - buttonSize; - final double top = screen.height - pos.dy - buttonSize; - final Offset anchor = Offset(left + buttonSize / 2, top + buttonSize / 2); - TranslationModeSelector.show(isDarkMode, anchor: anchor); + // 直接调用模式选择器,它会自动从悬浮按钮位置展开 + TranslationModeSelector.show(isDarkMode); } } From a764117e2fd81f5c951fb60b0ad203f56aeb523b Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Mon, 22 Sep 2025 20:57:49 +0800 Subject: [PATCH 27/55] =?UTF-8?q?refactor(pairing):=20=E9=87=8D=E6=9E=84?= =?UTF-8?q?=E9=85=8D=E5=AF=B9=E9=A1=B5=E9=9D=A2=E5=B8=83=E5=B1=80=E7=BB=93?= =?UTF-8?q?=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将配对页面的主要内容拆分为固定顶部区域和可滚动内容区域,提高代码可维护性 同时优化翻译模式选择器的位置计算逻辑 --- lib/modules/pairing/views/pairing_view.dart | 84 ++++++++++--------- .../controllers/translation_controller.dart | 7 +- .../dialogs/translation_mode_selector.dart | 12 +-- 3 files changed, 54 insertions(+), 49 deletions(-) diff --git a/lib/modules/pairing/views/pairing_view.dart b/lib/modules/pairing/views/pairing_view.dart index 1151eeecf..fcefe06a4 100644 --- a/lib/modules/pairing/views/pairing_view.dart +++ b/lib/modules/pairing/views/pairing_view.dart @@ -72,56 +72,60 @@ class _PairingViewState extends State return Scaffold( backgroundColor: isDarkMode ? Colors.black : Colors.white, body: SafeArea( - child: _buildMainContent(isDarkMode), + child: Column( + children: [ + // 固定顶部区域 - 不受下方内容影响 + _buildFixedTopSection(isDarkMode), + + // 可滚动的动态内容区域 + Expanded( + child: _buildDynamicContentSection(isDarkMode), + ), + ], + ), ), - // bottomNavigationBar: Opacity( - // opacity: 0.5, - // child: const CommonBottomNav(currentIndex: 0), - // ), ); } - // 主要内容 - Widget _buildMainContent(bool isDarkMode) { - return Container( - width: double.infinity, - height: MediaQuery.of(Get.context!).size.height, - padding: EdgeInsets.symmetric(horizontal: 24.w), - // 添加SingleChildScrollView以防止溢出 - child: SingleChildScrollView( - physics: const BouncingScrollPhysics(), - child: ConstrainedBox( - constraints: BoxConstraints( - minHeight: - MediaQuery.of(Get.context!).size.height - 100.h, // 减去底部导航和状态栏高度 - ), - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - SizedBox(height: 20.h), // 顶部间距 - - // 标题部分 - _buildHeader(isDarkMode), - - SizedBox(height: 20.h), + // 固定顶部区域 + Widget _buildFixedTopSection(bool isDarkMode) { + return Padding( + padding: EdgeInsets.only(top: 60.h, left: 24.w, right: 24.w), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + // 标题部分 + _buildHeader(isDarkMode), + SizedBox(height: 20.h), - // 设备动画 - 根据状态显示不同动画 - _buildDeviceAnimation(isDarkMode), + _buildDeviceAnimation(isDarkMode), + ], + ), + ); + } - SizedBox(height: 20.h), + // 动态内容区域 + Widget _buildDynamicContentSection(bool isDarkMode) { + return SingleChildScrollView( + physics: const BouncingScrollPhysics(), + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 24.w), + child: Column( + mainAxisAlignment: MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + SizedBox(height: 30.h), - // 状态信息 - _buildStatusInfo(isDarkMode), + // 状态信息 + _buildStatusInfo(isDarkMode), - SizedBox(height: 40.h), + SizedBox(height: 40.h), - // 底部按钮 - _buildActionButtons(isDarkMode), + // 底部按钮 + _buildActionButtons(isDarkMode), - SizedBox(height: 20.h), - ], - ), + SizedBox(height: 20.h), + ], ), ), ); diff --git a/lib/modules/translation/controllers/translation_controller.dart b/lib/modules/translation/controllers/translation_controller.dart index 9b0ee6374..43e3016ed 100644 --- a/lib/modules/translation/controllers/translation_controller.dart +++ b/lib/modules/translation/controllers/translation_controller.dart @@ -288,14 +288,13 @@ class TranslationController extends GetxController with WidgetsBindingObserver { WidgetsBinding.instance.addObserver(this); // 延迟显示模式选择器,确保页面已经完全加载 - // Future.delayed(const Duration(milliseconds: 100), () { - // _showModeSelector(); - // }); - //确保关键服务初始化完成 WidgetsBinding.instance.addPostFrameCallback((_) async { // 确保上下文有效 if (Get.context == null || !Get.context!.mounted) return; + + // Future.delayed(const Duration(milliseconds: 200), () { _showModeSelector(); + // }); }); } diff --git a/lib/modules/translation/views/dialogs/translation_mode_selector.dart b/lib/modules/translation/views/dialogs/translation_mode_selector.dart index a1fe0f0f6..50bde22e0 100644 --- a/lib/modules/translation/views/dialogs/translation_mode_selector.dart +++ b/lib/modules/translation/views/dialogs/translation_mode_selector.dart @@ -14,12 +14,14 @@ class TranslationModeSelector { // 总是从悬浮按钮位置展开,不依赖传入的anchor参数 final Size screenSize = MediaQuery.of(Get.context!).size; final Offset floatingButtonPos = controller.floatingButtonPosition.value; - + // 计算悬浮按钮的中心点位置(从右下角偏移转换为屏幕坐标) - final double buttonSize = 56.0; // 悬浮按钮的大小 - final double buttonCenterX = screenSize.width - floatingButtonPos.dx - buttonSize / 2; - final double buttonCenterY = screenSize.height - floatingButtonPos.dy - buttonSize / 2; - + final double buttonSize = 56.0.w; // 悬浮按钮的大小 + final double buttonCenterX = + screenSize.width - floatingButtonPos.dx - buttonSize / 2; + final double buttonCenterY = + screenSize.height - floatingButtonPos.dy - buttonSize / 2; + // 计算缩放对齐点(-1..1) final double ax = (buttonCenterX / screenSize.width) * 2 - 1; final double ay = (buttonCenterY / screenSize.height) * 2 - 1; From fa3a675cfa1322237299474365bbfc8fa1741091 Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Tue, 23 Sep 2025 12:08:20 +0800 Subject: [PATCH 28/55] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- lib/data/services/pay_service.dart | 175 +++++++++ .../payment/bindings/payment_binding.dart | 9 + .../controllers/payment_controller.dart | 85 ++++ lib/modules/payment/views/payment_view.dart | 365 ++++++++++++++++++ lib/modules/settings/views/settings_view.dart | 23 ++ lib/routes/app_pages.dart | 9 + lib/routes/app_routes.dart | 3 + 7 files changed, 669 insertions(+) create mode 100644 lib/data/services/pay_service.dart create mode 100644 lib/modules/payment/bindings/payment_binding.dart create mode 100644 lib/modules/payment/controllers/payment_controller.dart create mode 100644 lib/modules/payment/views/payment_view.dart diff --git a/lib/data/services/pay_service.dart b/lib/data/services/pay_service.dart new file mode 100644 index 000000000..82b241e04 --- /dev/null +++ b/lib/data/services/pay_service.dart @@ -0,0 +1,175 @@ +import 'package:get/get.dart'; +import 'package:fluwx/fluwx.dart'; +import '../../modules/login/controllers/weixinLogin.dart'; + +/// 支付结果回调 +typedef PaymentResultCallback = void Function(bool success, String? message); + +/// 支付服务 +class PayService extends GetxService { + late FlWX _flwx; + + @override + void onInit() { + super.onInit(); + _flwx = FlWX(); + } + + /// 微信支付 + /// + /// [paymentData] 支付参数,包含以下字段: + /// - appId: 应用ID + /// - partnerId: 商户号 + /// - prepayId: 预支付交易会话ID + /// - packageValue: 扩展字段,固定值"Sign=WXPay" + /// - nonceStr: 随机字符串 + /// - timestamp: 时间戳 + /// - sign: 签名 + /// + /// [onResult] 支付结果回调 + Future wechatPay({ + required Map paymentData, + PaymentResultCallback? onResult, + }) async { + try { + // 检查微信是否安装 + if (!await _flwx.isInstalled) { + onResult?.call(false, '微信未安装'); + return false; + } + + // 将支付数据转换为Payment对象 + final payment = paymentData.toPayment(); + if (payment == null) { + onResult?.call(false, '支付参数格式错误'); + return false; + } + + // 调用微信支付 + final result = await _flwx.pay( + payment, + onSuccess: () { + onResult?.call(true, '支付成功'); + }, + onResponse: (response) { + if (response.errCode == 0) { + onResult?.call(true, '支付成功'); + } else if (response.errCode == -2) { + onResult?.call(false, '用户取消支付'); + } else { + onResult?.call(false, '支付失败: ${response.errStr}'); + } + }, + ); + + return result; + } catch (e) { + onResult?.call(false, '支付异常: $e'); + return false; + } + } + + /// 创建微信支付订单 + /// + /// [orderInfo] 订单信息,包含: + /// - orderId: 订单ID + /// - amount: 支付金额(分) + /// - description: 商品描述 + /// - userId: 用户ID + /// + /// 返回支付参数,用于调用 [wechatPay] 方法 + Future?> createWechatPayOrder({ + required String orderId, + required int amount, + required String description, + String? userId, + }) async { + try { + // TODO: 这里需要调用后端API创建支付订单 + // 示例代码,实际需要根据后端接口调整 + + // final response = await Api.createPayOrder({ + // 'orderId': orderId, + // 'amount': amount, + // 'description': description, + // 'userId': userId, + // 'payType': 'wechat', + // }); + + // if (response['success'] == true) { + // return response['data'] as Map; + // } + + // 临时返回示例数据结构,实际使用时需要删除 + return { + 'appId': 'your_app_id', + 'partnerId': 'your_partner_id', + 'prepayId': 'prepay_id_from_server', + 'packageValue': 'Sign=WXPay', + 'nonceStr': 'random_string', + 'timestamp': DateTime.now().millisecondsSinceEpoch ~/ 1000, + 'sign': 'signature_from_server', + }; + } catch (e) { + print('创建支付订单失败: $e'); + return null; + } + } + + /// 查询支付结果 + /// + /// [orderId] 订单ID + /// 返回支付状态:success, failed, pending + Future queryPaymentStatus(String orderId) async { + try { + // TODO: 调用后端API查询支付状态 + // final response = await Api.queryPaymentStatus({'orderId': orderId}); + // return response['status'] as String?; + + // 临时返回,实际使用时需要删除 + return 'pending'; + } catch (e) { + print('查询支付状态失败: $e'); + return null; + } + } + + /// 完整的支付流程 + /// + /// [orderId] 订单ID + /// [amount] 支付金额(分) + /// [description] 商品描述 + /// [userId] 用户ID + /// [onResult] 支付结果回调 + Future processWechatPayment({ + required String orderId, + required int amount, + required String description, + String? userId, + PaymentResultCallback? onResult, + }) async { + try { + // 1. 创建支付订单 + final paymentData = await createWechatPayOrder( + orderId: orderId, + amount: amount, + description: description, + userId: userId, + ); + + if (paymentData == null) { + onResult?.call(false, '创建支付订单失败'); + return false; + } + + // 2. 调用微信支付 + return await wechatPay( + paymentData: paymentData, + onResult: onResult, + ); + } catch (e) { + onResult?.call(false, '支付流程异常: $e'); + return false; + } + } +} \ No newline at end of file diff --git a/lib/modules/payment/bindings/payment_binding.dart b/lib/modules/payment/bindings/payment_binding.dart new file mode 100644 index 000000000..fb38d6f9d --- /dev/null +++ b/lib/modules/payment/bindings/payment_binding.dart @@ -0,0 +1,9 @@ +import 'package:get/get.dart'; +import '../controllers/payment_controller.dart'; + +class PaymentBinding extends Bindings { + @override + void dependencies() { + Get.lazyPut(() => PaymentController()); + } +} \ No newline at end of file diff --git a/lib/modules/payment/controllers/payment_controller.dart b/lib/modules/payment/controllers/payment_controller.dart new file mode 100644 index 000000000..f7b4178ff --- /dev/null +++ b/lib/modules/payment/controllers/payment_controller.dart @@ -0,0 +1,85 @@ +import 'package:get/get.dart'; +import '../../../data/services/pay_service.dart'; + +class PaymentController extends GetxController { + final PayService _payService = Get.find(); + + // 支付状态 + final RxBool isLoading = false.obs; + final RxString paymentStatus = ''.obs; + final RxString errorMessage = ''.obs; + + // 测试订单信息 + final RxString testOrderId = ''.obs; + final RxDouble testAmount = 0.01.obs; // 测试金额 1分钱 + + @override + void onInit() { + super.onInit(); + // 生成测试订单ID + testOrderId.value = 'test_${DateTime.now().millisecondsSinceEpoch}'; + } + + /// 测试微信支付 + Future testWechatPayment() async { + try { + isLoading.value = true; + paymentStatus.value = '正在发起支付...'; + errorMessage.value = ''; + + // 调用支付服务,使用回调处理结果 + final launched = await _payService.processWechatPayment( + orderId: testOrderId.value, + amount: (testAmount.value * 100).toInt(), // 转换为分 + description: '微信支付测试', + onResult: (success, message) { + // 支付结果回调 + isLoading.value = false; + + if (success) { + paymentStatus.value = '支付成功!'; + errorMessage.value = ''; + Get.snackbar( + '支付成功', + message ?? '微信支付测试完成', + snackPosition: SnackPosition.TOP, + ); + } else { + paymentStatus.value = '支付失败'; + errorMessage.value = message ?? '未知错误'; + Get.snackbar( + '支付失败', + message ?? '未知错误', + snackPosition: SnackPosition.TOP, + ); + } + }, + ); + + if (!launched) { + // 如果支付没有成功启动 + isLoading.value = false; + paymentStatus.value = '支付启动失败'; + errorMessage.value = '无法启动微信支付'; + Get.snackbar('支付启动失败', '无法启动微信支付'); + } + } catch (e) { + isLoading.value = false; + paymentStatus.value = '支付异常'; + errorMessage.value = e.toString(); + Get.snackbar('支付异常', e.toString()); + } + } + + /// 重置状态 + void resetStatus() { + paymentStatus.value = ''; + errorMessage.value = ''; + testOrderId.value = 'test_${DateTime.now().millisecondsSinceEpoch}'; + } + + /// 更新测试金额 + void updateTestAmount(double amount) { + testAmount.value = amount; + } +} diff --git a/lib/modules/payment/views/payment_view.dart b/lib/modules/payment/views/payment_view.dart new file mode 100644 index 000000000..04a1a7480 --- /dev/null +++ b/lib/modules/payment/views/payment_view.dart @@ -0,0 +1,365 @@ +import 'package:flutter/material.dart'; +import 'package:get/get.dart'; +import 'package:flutter_screenutil/flutter_screenutil.dart'; +import '../controllers/payment_controller.dart'; + +class PaymentView extends GetView { + const PaymentView({Key? key}) : super(key: key); + + @override + Widget build(BuildContext context) { + final isDarkMode = Theme.of(context).brightness == Brightness.dark; + + return Scaffold( + backgroundColor: isDarkMode ? Colors.black : Colors.grey[50], + appBar: AppBar( + backgroundColor: isDarkMode ? Colors.black : Colors.white, + elevation: 0.5, + title: Text( + '微信支付测试', + style: TextStyle( + fontSize: 18.sp, + fontWeight: FontWeight.w600, + color: isDarkMode ? Colors.white : Colors.black, + ), + ), + centerTitle: true, + leading: IconButton( + icon: Icon( + Icons.arrow_back_ios, + color: isDarkMode ? Colors.white : Colors.black, + ), + onPressed: () => Get.back(), + ), + ), + body: SingleChildScrollView( + padding: EdgeInsets.all(16.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildTestInfoCard(isDarkMode), + SizedBox(height: 20.h), + _buildAmountSelector(isDarkMode), + SizedBox(height: 20.h), + _buildPaymentButton(isDarkMode), + SizedBox(height: 20.h), + _buildStatusCard(isDarkMode), + SizedBox(height: 20.h), + _buildInstructions(isDarkMode), + ], + ), + ), + ); + } + + /// 测试信息卡片 + Widget _buildTestInfoCard(bool isDarkMode) { + return Container( + padding: EdgeInsets.all(16.w), + decoration: BoxDecoration( + color: isDarkMode ? Colors.grey[900] : Colors.white, + borderRadius: BorderRadius.circular(12.r), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.05), + blurRadius: 10, + offset: const Offset(0, 2), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '测试订单信息', + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w600, + color: isDarkMode ? Colors.white : Colors.black, + ), + ), + SizedBox(height: 12.h), + Obx(() => _buildInfoRow('订单ID', controller.testOrderId.value, isDarkMode)), + SizedBox(height: 8.h), + _buildInfoRow('商品描述', '微信支付测试', isDarkMode), + SizedBox(height: 8.h), + Obx(() => _buildInfoRow('支付金额', '¥${controller.testAmount.value.toStringAsFixed(2)}', isDarkMode)), + ], + ), + ); + } + + /// 信息行 + Widget _buildInfoRow(String label, String value, bool isDarkMode) { + return Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 80.w, + child: Text( + '$label:', + style: TextStyle( + fontSize: 14.sp, + color: isDarkMode ? Colors.grey[400] : Colors.grey[600], + ), + ), + ), + Expanded( + child: Text( + value, + style: TextStyle( + fontSize: 14.sp, + color: isDarkMode ? Colors.white : Colors.black, + ), + ), + ), + ], + ); + } + + /// 金额选择器 + Widget _buildAmountSelector(bool isDarkMode) { + final amounts = [0.01, 0.10, 1.00, 10.00]; + + return Container( + padding: EdgeInsets.all(16.w), + decoration: BoxDecoration( + color: isDarkMode ? Colors.grey[900] : Colors.white, + borderRadius: BorderRadius.circular(12.r), + boxShadow: [ + BoxShadow( + color: Colors.black.withOpacity(0.05), + blurRadius: 10, + offset: const Offset(0, 2), + ), + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + '选择测试金额', + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w600, + color: isDarkMode ? Colors.white : Colors.black, + ), + ), + SizedBox(height: 12.h), + Wrap( + spacing: 12.w, + runSpacing: 8.h, + children: amounts.map((amount) { + return Obx(() => GestureDetector( + onTap: () => controller.updateTestAmount(amount), + child: Container( + padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 8.h), + decoration: BoxDecoration( + color: controller.testAmount.value == amount + ? Colors.blue[600] + : (isDarkMode ? Colors.grey[800] : Colors.grey[200]), + borderRadius: BorderRadius.circular(20.r), + ), + child: Text( + '¥${amount.toStringAsFixed(2)}', + style: TextStyle( + fontSize: 14.sp, + color: controller.testAmount.value == amount + ? Colors.white + : (isDarkMode ? Colors.white : Colors.black), + ), + ), + ), + )); + }).toList(), + ), + ], + ), + ); + } + + /// 支付按钮 + Widget _buildPaymentButton(bool isDarkMode) { + return Obx(() => SizedBox( + width: double.infinity, + height: 50.h, + child: ElevatedButton( + onPressed: controller.isLoading.value ? null : controller.testWechatPayment, + style: ElevatedButton.styleFrom( + backgroundColor: Colors.green[600], + foregroundColor: Colors.white, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(12.r), + ), + elevation: 2, + ), + child: controller.isLoading.value + ? Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + SizedBox( + width: 20.w, + height: 20.w, + child: const CircularProgressIndicator( + strokeWidth: 2, + valueColor: AlwaysStoppedAnimation(Colors.white), + ), + ), + SizedBox(width: 12.w), + const Text('处理中...'), + ], + ) + : Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Icon(Icons.payment, size: 20.w), + SizedBox(width: 8.w), + Text( + '发起微信支付', + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w600, + ), + ), + ], + ), + ), + )); + } + + /// 状态卡片 + Widget _buildStatusCard(bool isDarkMode) { + return Obx(() { + if (controller.paymentStatus.value.isEmpty) { + return const SizedBox.shrink(); + } + + final isError = controller.errorMessage.value.isNotEmpty; + + return Container( + padding: EdgeInsets.all(16.w), + decoration: BoxDecoration( + color: isError + ? (isDarkMode ? Colors.red[900] : Colors.red[50]) + : (isDarkMode ? Colors.green[900] : Colors.green[50]), + borderRadius: BorderRadius.circular(12.r), + border: Border.all( + color: isError ? Colors.red[300]! : Colors.green[300]!, + width: 1, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon( + isError ? Icons.error : Icons.check_circle, + color: isError ? Colors.red[600] : Colors.green[600], + size: 20.w, + ), + SizedBox(width: 8.w), + Text( + '支付状态', + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w600, + color: isError ? Colors.red[600] : Colors.green[600], + ), + ), + ], + ), + SizedBox(height: 8.h), + Text( + controller.paymentStatus.value, + style: TextStyle( + fontSize: 14.sp, + color: isDarkMode ? Colors.white : Colors.black, + ), + ), + if (controller.errorMessage.value.isNotEmpty) ...[ + SizedBox(height: 8.h), + Text( + '错误信息: ${controller.errorMessage.value}', + style: TextStyle( + fontSize: 12.sp, + color: Colors.red[600], + ), + ), + ], + SizedBox(height: 12.h), + SizedBox( + width: double.infinity, + child: OutlinedButton( + onPressed: controller.resetStatus, + style: OutlinedButton.styleFrom( + side: BorderSide( + color: isError ? Colors.red[600]! : Colors.green[600]!, + ), + ), + child: Text( + '重新测试', + style: TextStyle( + color: isError ? Colors.red[600] : Colors.green[600], + ), + ), + ), + ), + ], + ), + ); + }); + } + + /// 使用说明 + Widget _buildInstructions(bool isDarkMode) { + return Container( + padding: EdgeInsets.all(16.w), + decoration: BoxDecoration( + color: isDarkMode ? Colors.grey[900] : Colors.blue[50], + borderRadius: BorderRadius.circular(12.r), + border: Border.all( + color: Colors.blue[200]!, + width: 1, + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Icon( + Icons.info_outline, + color: Colors.blue[600], + size: 20.w, + ), + SizedBox(width: 8.w), + Text( + '使用说明', + style: TextStyle( + fontSize: 16.sp, + fontWeight: FontWeight.w600, + color: Colors.blue[600], + ), + ), + ], + ), + SizedBox(height: 12.h), + Text( + '1. 选择测试金额(建议使用0.01元进行测试)\n' + '2. 点击"发起微信支付"按钮\n' + '3. 系统将调用微信支付SDK\n' + '4. 在微信中完成支付流程\n' + '5. 返回应用查看支付结果\n\n' + '注意:这是测试环境,实际不会产生真实扣费', + style: TextStyle( + fontSize: 14.sp, + color: isDarkMode ? Colors.grey[300] : Colors.grey[700], + height: 1.5, + ), + ), + ], + ), + ); + } +} \ No newline at end of file diff --git a/lib/modules/settings/views/settings_view.dart b/lib/modules/settings/views/settings_view.dart index d36801018..912b6644e 100644 --- a/lib/modules/settings/views/settings_view.dart +++ b/lib/modules/settings/views/settings_view.dart @@ -516,6 +516,29 @@ class SettingsView extends GetView { // : Colors.grey[200]), // //BLE测试 + // 支付测试 + _buildSimpleNavigationSetting( + title: '支付测试', + subtitle: '测试微信支付功能', + icon: Icons.payment, + iconBgColor: isDarkMode + ? Colors.green[900]!.withOpacity(0.3) + : Colors.green[100]!, + iconColor: + isDarkMode ? Colors.green[300]! : Colors.green[600]!, + titleColor: isDarkMode ? Colors.white : null, + subtitleColor: isDarkMode ? Colors.white70 : null, + onTap: () { + Get.toNamed(Routes.payment); + }, + isDarkMode: isDarkMode, + ), + Divider( + height: 1, + color: isDarkMode + ? Colors.white.withOpacity(0.1) + : Colors.grey[200]), + Obx(() => controller.showDebugInfo.value ? _buildSimpleNavigationSetting( title: 'opus解码测试', diff --git a/lib/routes/app_pages.dart b/lib/routes/app_pages.dart index 7b8c533f2..6e1082812 100644 --- a/lib/routes/app_pages.dart +++ b/lib/routes/app_pages.dart @@ -63,6 +63,8 @@ import '../modules/music/views/music_playlist_view.dart'; import '../modules/music/bindings/music_playlist_binding.dart'; import '../modules/FTFTranslation/bindings/FTFTranslation_binding.dart'; import '../modules/FTFTranslation/views/FTFTranslation_view.dart'; +import '../modules/payment/bindings/payment_binding.dart'; +import '../modules/payment/views/payment_view.dart'; abstract class AppPages { static final pages = [ @@ -288,5 +290,12 @@ abstract class AppPages { page: () => const QqmusicPlaylistView(), binding: QQMusicPlaylistBinding(), ), + GetPage( + name: Routes.payment, + page: () => const PaymentView(), + binding: PaymentBinding(), + transition: Transition.rightToLeft, + transitionDuration: Duration(milliseconds: 250), + ), ]; } diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index 3a4fc6973..f2e78cbf6 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -63,4 +63,7 @@ abstract class Routes { static const ble = '/ble'; // ble设备管理 static const qqmusic = '/qqmusic'; + + // 支付相关 + static const payment = '/payment'; // 支付测试页面 } From 54cd50fe60c4669ec0c77c882268f65de4984eca Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Tue, 23 Sep 2025 12:12:24 +0800 Subject: [PATCH 29/55] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E5=AF=BC=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E5=AF=BC=E8=88=AA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../agent_service/Sources/agent_service/AgentServiceImpl.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift b/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift index 6b2415e07..5f8eedde6 100644 --- a/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift +++ b/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift @@ -986,7 +986,7 @@ private func jsonToString(_ json: [String: Any]) -> String? { // 处理导航卡片 private func processNavigationCard(_ cardNavigation: [String: Any]) -> Bool { let start = cardNavigation["start"] as? String ?? "" - let destination = cardNavigation["end"] as? String ?? "" + let destination = cardNavigation["destination"] as? String ?? "" let endlatitude = cardNavigation["endlatitude"] as? String ?? "" let endlongitude = cardNavigation["endlongitude"] as? String ?? "" let mode = cardNavigation["mode"] as? String ?? "driving" From 0fa217170318d610c1855d31a4da56bbcf85b4bd Mon Sep 17 00:00:00 2001 From: liwei1dao Date: Tue, 23 Sep 2025 12:13:01 +0800 Subject: [PATCH 30/55] =?UTF-8?q?=E4=B8=8A=E4=BC=A0=E5=AF=BC=E8=88=AA?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../agent_service/Sources/agent_service/AgentServiceImpl.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift b/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift index 6b2415e07..5f8eedde6 100644 --- a/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift +++ b/local_plugins/agent_service/ios/agent_service/Sources/agent_service/AgentServiceImpl.swift @@ -986,7 +986,7 @@ private func jsonToString(_ json: [String: Any]) -> String? { // 处理导航卡片 private func processNavigationCard(_ cardNavigation: [String: Any]) -> Bool { let start = cardNavigation["start"] as? String ?? "" - let destination = cardNavigation["end"] as? String ?? "" + let destination = cardNavigation["destination"] as? String ?? "" let endlatitude = cardNavigation["endlatitude"] as? String ?? "" let endlongitude = cardNavigation["endlongitude"] as? String ?? "" let mode = cardNavigation["mode"] as? String ?? "driving" From e253a4aeba15aecf8ad58feb10c9f0642f5364df Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Wed, 24 Sep 2025 21:27:58 +0800 Subject: [PATCH 31/55] =?UTF-8?q?feat(Android=E6=82=AC=E6=B5=AE=E7=AA=97):?= =?UTF-8?q?=20=E6=B7=BB=E5=8A=A0=E6=82=AC=E6=B5=AE=E7=AA=97=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E6=94=AF=E6=8C=81=E5=AE=9E=E6=97=B6=E7=BF=BB=E8=AF=91?= =?UTF-8?q?=E6=98=BE=E7=A4=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增悬浮窗服务用于显示实时翻译结果 - 实现悬浮窗权限管理和状态控制 - 添加悬浮窗管理器处理窗口显示、更新和交互 - 在翻译界面添加悬浮窗开关按钮 - 支持悬浮窗最小化、展开和拖拽功能 - 添加Android原生悬浮窗服务和权限插件 --- android/app/build.gradle.kts | 3 + android/app/src/main/AndroidManifest.xml | 6 + .../saitong/voitrans/FloatingWindowPlugin.kt | 133 +++++++ .../saitong/voitrans/FloatingWindowService.kt | 376 ++++++++++++++++++ .../voitrans}/MainActivity.kt | 4 + .../voitrans}/MyApplication.kt | 6 + .../voitrans/OverlayPermissionPlugin.kt | 66 +++ lib/core/bindings/initial_binding.dart | 5 + .../services/floating_window_manager.dart | 199 +++++++++ lib/core/utils/overlay_permission_util.dart | 70 ++++ .../controllers/translation_controller.dart | 127 ++++++ .../translation/views/translation_view.dart | 24 +- 12 files changed, 1018 insertions(+), 1 deletion(-) create mode 100644 android/app/src/main/kotlin/com/saitong/voitrans/FloatingWindowPlugin.kt create mode 100644 android/app/src/main/kotlin/com/saitong/voitrans/FloatingWindowService.kt rename android/app/src/main/kotlin/com/{yunqiinnovation/deepsound => saitong/voitrans}/MainActivity.kt (98%) rename android/app/src/main/kotlin/com/{yunqiinnovation/deepsound => saitong/voitrans}/MyApplication.kt (95%) create mode 100644 android/app/src/main/kotlin/com/saitong/voitrans/OverlayPermissionPlugin.kt create mode 100644 lib/core/services/floating_window_manager.dart create mode 100644 lib/core/utils/overlay_permission_util.dart diff --git a/android/app/build.gradle.kts b/android/app/build.gradle.kts index 6f29c8c95..cab2efce1 100644 --- a/android/app/build.gradle.kts +++ b/android/app/build.gradle.kts @@ -123,6 +123,9 @@ android { dependencies { // 添加核心库反糖化 coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.0.3") + + // 添加 CardView 依赖 + implementation("androidx.cardview:cardview:1.0.0") } flutter { diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 24d99722b..915595a3f 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -121,6 +121,12 @@ android:value="78e7946dd602778df82c6ae143a88184"/> + +