import 'dart:async'; import 'dart:io'; import 'package:app_tracking_transparency/app_tracking_transparency.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:get_storage/get_storage.dart'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:http/http.dart' as http; import '../../../data/models/appconfig.dart'; import '../../../data/models/appconfig_model.dart'; import '../../../data/models/user_Info.dart'; import '../../../routes/app_routes.dart'; import '../../../data/services/network/api.dart'; import '../../../data/services/version_update_service.dart'; import '../../../modules/common/widgets/version_update_dialog.dart'; import '../../../modules/common/widgets/privacy_policy_dialog.dart'; import '../../../core/utils/logger.dart'; class SplashController extends GetxController { final GetStorage _storage = GetStorage(); static const String _tag = 'SplashCtrl'; final versionService = VersionUpdateService.to; // 进度条动画控制 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()); // 启动整个初始化流程 _initializeApp(); } /// 启动并协调所有初始化任务 Future _initializeApp() async { // 步骤 1: 检查并等待隐私政策同意 final hasAccepted = await _checkAndRequestPrivacy(); if (!hasAccepted) { // 如果用户拒绝隐私政策,应用会退出,后续代码不执行 return; } // 步骤 3: 并行执行所有启动任务,并等待它们全部完成 // Future.wait 会等待数组中所有的 Future 都完成后才继续 await Future.wait([ _startLoadingAnimation(), // 任务1: 最小动画时长 _performParallelTasks(), // 任务2: 其他所有后台任务 ]); // 步骤 4: 所有任务(动画和后台)都已完成,触发导航 _canNavigate.value = true; } /// 切换隐私政策同意状态 - 新增方法 // void togglePrivacyAcceptance() { // _hasAcceptedPrivacy.value = !_hasAcceptedPrivacy.value; // _storage.write('hasAcceptedPrivacy', _hasAcceptedPrivacy.value); // } /// 检查隐私政策,如果未同意,则显示弹窗并等待用户操作 /// 返回用户是否同意 Future _checkAndRequestPrivacy() async { if (_hasAcceptedPrivacy.value) { return true; } final completer = Completer(); WidgetsBinding.instance.addPostFrameCallback((_) { PrivacyPolicyDialog.show( onAccept: () { _storage.write('hasAcceptedPrivacy', true); _hasAcceptedPrivacy.value = true; Get.back(); // 关闭弹窗 if (!completer.isCompleted) completer.complete(true); }, onReject: () { if (!completer.isCompleted) completer.complete(false); exit(0); // 退出应用 }, canDismiss: false, ); }); return completer.future; } /// 网络连接检查 - 阻塞版本 Future _checkNetworkStatus() async { while (true) { // 无限循环直到网络检查成功 try { final connectivityResult = await Connectivity().checkConnectivity(); final hasConnectivity = !connectivityResult.contains(ConnectivityResult.none); // 条件变更:满足“有网络连接”或“能访问至少一个测试地址”之一即通过 if (hasConnectivity) { Logger.d(_tag, '网络检查通过:检测到设备网络连接'); break; } // 若设备层面无网络标识,再尝试外网连通性 final hasInternet = await _checkInternetConnection(); if (hasInternet) { Logger.d(_tag, '网络检查通过:可访问外网测试地址'); break; } // 两个条件都不满足,提示并重试 await _showNoNetworkDialogAndWait(); continue; } catch (e) { Logger.e(_tag, '网络检查失败: $e'); Logger.e(_tag, '网络检查异常: $e'); await _showNoNetworkDialogAndWait(); continue; } } } /// 检查实际的网络连通性 Future _checkInternetConnection() async { final testUrls = [ 'https://api.voitrans.net/ping', // 国内服 'https://hw.api.voitrans.net/ping', // 海外服 'https://www.baidu.com', // 百度 'https://www.google.com', // Google ]; for (final url in testUrls) { try { final response = await http.get( Uri.parse(url.trim()), // 去除空格 headers: {'User-Agent': 'Voitrans-App'}, // 有助于后端识别 ).timeout(const Duration(seconds: 5)); if (response.statusCode == 200) { Logger.d(_tag, '网络连接检查成功: $url'); return true; } } on TimeoutException { Logger.w(_tag, '网络连接超时: $url'); continue; } on Exception catch (e) { Logger.w(_tag, '网络连接失败: $url, 错误: $e'); continue; } } Logger.e(_tag, '所有网络连接检查均失败'); return false; } /// 动画任务:返回一个在动画结束时完成的 Future Future _startLoadingAnimation() { const totalDuration = 1500; // 动画总时长1.5秒 const steps = totalDuration ~/ 30; const stepValue = 1.0 / steps; final completer = Completer(); Timer.periodic(const Duration(milliseconds: 30), (timer) { if (_loadingProgress.value < 1.0) { _loadingProgress.value += stepValue; } else { _loadingProgress.value = 1.0; timer.cancel(); if (!completer.isCompleted) completer.complete(); } }); return completer.future; } /// 将所有并行的后台任务打包成一个 Future Future _performParallelTasks() async { //步骤2:检查网络连接状态 await _checkNetworkStatus(); // 同样使用 Future.wait,确保此函数在所有子任务完成后才结束 // 先请求系统权限,避免与版本弹窗竞争 await _requestTrackingPermissionAsync(); await _performVersionCheckAsync(); await _validateTokenAsync(); //await Future.wait([ //_requestTrackingPermissionAsync(), //_validateTokenAsync(), //_performVersionCheckAsync(), //]); Logger.d(_tag, '所有后台任务已完成'); } /// 异步请求追踪权限 Future _requestTrackingPermissionAsync() async { try { await AppTrackingTransparency.requestTrackingAuthorization(); Logger.d(_tag, '用户追踪权限请求完成'); } catch (e) { Logger.e(_tag, '用户追踪权限请求失败: $e'); // 即使失败,也视为完成,不阻塞主流程 } } /// 异步验证Token和加载应用配置 Future _validateTokenAsync() async { try { final token = _storage.read('logintoken'); if (token != null && token.isNotEmpty) { final isLoggedIn = await getLoginToken(); if (isLoggedIn) { // 只有登录成功,才获取App配置 await getAppConfig(); } else { await User.clearUserSession(); // } } } catch (e) { Logger.e(_tag, 'Token验证或配置加载失败: $e'); await User.clearUserSession(); } } /// 异步执行版本检查 Future _performVersionCheckAsync() async { try { final versionService = VersionUpdateService.to; final updateInfo = await versionService.checkForUpdate(); if (updateInfo.hasUpdate) { Logger.i(_tag, '发现新版本: ${updateInfo.latestVersion}'); // 等待到下一微任务,降低与其它 UI 的并发竞争 await Future.delayed(Duration.zero); try { await VersionUpdateDialog.show(updateInfo); } catch (e) { Logger.e(_tag, '更新弹窗显示失败: $e'); } } else { Logger.i(_tag, '当前已是最新版本'); } } catch (e) { Logger.e(_tag, '版本检查失败: $e'); } } /// 根据最终状态跳转到目标页面 void _navigateToTargetPage() { // 导航时直接检查最终的数据状态,而不是依赖中间变量 final token = _storage.read('logintoken'); final isAppConfigInitialized = AppConfig.isInitialized(); // 检查登录状态和配置初始化状态 if (token != null && token.isNotEmpty && isAppConfigInitialized) { Get.offAllNamed(Routes.pairing); } else { Get.offAllNamed(Routes.login); } } /// 异步获取登录Token Future getLoginToken() async { try { final appVersion = versionService.cachedAppVersion; final channel = versionService.cachedChannel; Logger.i(_tag, 'Token登录请求: version=$appVersion, channel=$channel'); final response = await Api.tokenlogin({ 'version': appVersion, 'channel': channel, }); if (response?['user'] != null && response['user'] != '') { final user = User.fromJson(response); await _storage.write('user_info', user.toJson()); return true; } return false; } catch (e) { Logger.e(_tag, 'Token获取失败: $e'); return false; } } Future getAppConfig() async { try { final response = await Api.getappconfig(); if (response == null) return false; final resp = UserGetAppConfigResp.fromJson(response); AppConfig.initialize(resp); return true; } catch (e) { Logger.e(_tag, 'getAppConfig 获取失败: $e'); return false; } } /// 处理QQ音乐登录回调 void handleQQMusicLogin() { try { Logger.i(_tag, '处理QQ音乐登录回调'); // 这里可以添加具体的QQ音乐登录处理逻辑 // 例如:验证登录状态、更新用户信息、导航到相应页面等 // 如果需要导航到特定页面,可以使用: // Get.toNamed(Routes.qqmusic); } catch (e) { Logger.e(_tag, '处理QQ音乐登录回调失败: $e'); } } } /// 显示无网络连接对话框并等待用户操作(阻塞版本) Future _showNoNetworkDialogAndWait() async { final completer = Completer(); WidgetsBinding.instance.addPostFrameCallback((_) { Get.dialog( AlertDialog( title: Row( children: [ const Icon(Icons.wifi_off, color: Colors.orange), const SizedBox(width: 8), Text('networkError'.tr), // 无网络连接 ], ), content: Text('networkConnectionFailed'.tr), // 网络连接失败,请检查网络设置 actions: [ TextButton( onPressed: () { Get.back(); // 关闭弹窗 completer.complete(); // 完成Future,继续执行 }, child: Text('retry'.tr), // 重试 ), ], ), barrierDismissible: false, // 不允许点击外部关闭 ); }); // 等待用户点击重试按钮 return completer.future; }