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音乐登录