From ebb170c93a97dd8e74374f208bcbc16267e84664 Mon Sep 17 00:00:00 2001 From: lxm <1274844347@qq.com> Date: Sat, 2 Aug 2025 17:21:07 +0800 Subject: [PATCH] =?UTF-8?q?refactor(splash):=20=E9=87=8D=E6=9E=84=E5=90=AF?= =?UTF-8?q?=E5=8A=A8=E6=B5=81=E7=A8=8B=E5=B9=B6=E4=BC=98=E5=8C=96=E7=8A=B6?= =?UTF-8?q?=E6=80=81=E7=AE=A1=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 使用单一反应式变量 `_canNavigate` 替代多个布尔标记 - 将并行任务封装为独立方法并通过 `Future.wait` 协调 - 新增 `AppConfig.isInitialized()` 和 `User.isLoggedIn()` 方法 - 清理用户会话时增加本地存储和内存数据的清除 - 简化导航逻辑,直接检查最终数据状态 --- lib/data/models/appconfig.dart | 11 + lib/data/models/user_Info.dart | 22 +- .../splash/controllers/splash_controller.dart | 376 ++++++------------ 3 files changed, 147 insertions(+), 262 deletions(-) diff --git a/lib/data/models/appconfig.dart b/lib/data/models/appconfig.dart index 19dcf0565..22a09d84a 100644 --- a/lib/data/models/appconfig.dart +++ b/lib/data/models/appconfig.dart @@ -4,9 +4,20 @@ import '../../../data/models/appconfig_model.dart'; class AppConfig { static late UserGetAppConfigResp config; + + // 新增一个私有的静态布尔值来跟踪初始化状态 + static bool _isInitialized = false; + + /// 检查AppConfig单例是否已经被初始化 + /// 外部通过调用此方法来安全地检查状态 + static bool isInitialized() { + return _isInitialized; + } + // 可选:提供初始化方法 static void initialize(UserGetAppConfigResp resp) { config = resp; + _isInitialized = true; // <-- 新增此行 } //环境变量 diff --git a/lib/data/models/user_Info.dart b/lib/data/models/user_Info.dart index e5efa62ec..d5d369ba6 100644 --- a/lib/data/models/user_Info.dart +++ b/lib/data/models/user_Info.dart @@ -1,9 +1,15 @@ -import 'dart:ffi'; +import 'package:get_storage/get_storage.dart'; +import '../../../core/utils/logger.dart'; class User { // 单例实例 static User? _instance; + /// 安全地检查用户是否已经登录(即单例是否已初始化) + static bool isLoggedIn() { + return _instance != null; + } + // 单例访问器(增加空安全保护) static User get instance { if (_instance == null) { @@ -12,6 +18,18 @@ class User { return _instance!; } + // --- 新增 --- + /// 清理所有与用户会话相关的数据 + static Future clearUserSession() async { + final GetStorage storage = GetStorage(); + // 1. 清理本地存储 + await storage.remove('logintoken'); + await storage.remove('user_info'); + // 2. 清理内存中的单例 + _instance = null; + Logger.i("User", "用户会话数据已安全清理。"); + } + // 最终属性(不可变) String name; String uid; @@ -72,7 +90,7 @@ class User { // 修复1:安全获取嵌套的 user 字段 final userData = json['user'] as Map?; // 允许空值 final devicesData = json['devices'] as List?; // 允许空值 - print("数据获取fronTokenJson${userData} ${devicesData}"); + print("数据获取fronTokenJson$userData $devicesData"); // 更新单例实例 return _instance = User._( token: '', diff --git a/lib/modules/splash/controllers/splash_controller.dart b/lib/modules/splash/controllers/splash_controller.dart index 013ba187e..5c90dacca 100644 --- a/lib/modules/splash/controllers/splash_controller.dart +++ b/lib/modules/splash/controllers/splash_controller.dart @@ -1,15 +1,14 @@ import 'dart:async'; import 'dart:io'; import 'package:app_tracking_transparency/app_tracking_transparency.dart'; - -import '../../../data/models/appconfig.dart'; -import '../../../data/models/appconfig_model.dart'; import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'package:get_storage/get_storage.dart'; + +import '../../../data/models/appconfig.dart'; +import '../../../data/models/appconfig_model.dart'; import '../../../data/models/user_Info.dart'; import '../../../routes/app_routes.dart'; -import '../../../core/themes/theme_service.dart'; import '../../../data/services/network/api.dart'; import '../../../data/services/version_update_service.dart'; import '../../../modules/common/widgets/version_update_dialog.dart'; @@ -24,191 +23,174 @@ class SplashController extends GetxController { final _loadingProgress = 0.0.obs; double get loadingProgress => _loadingProgress.value; - // 各项任务完成状态 - final _isTrackingPermissionRequested = false.obs; // 追踪权限请求完成 - final _isVersionCheckCompleted = false.obs; // 版本检查完成 - final _isTokenValidated = false.obs; // Token验证完成 - final _isAppConfigLoaded = false.obs; // 应用配置加载完成 - final _isAnimationCompleted = false.obs; // 动画完成 - - // 登录状态 - final _isLoggedIn = false.obs; - bool get isLoggedIn => _isLoggedIn.value; - - final _hasAcceptedPrivacy = false.obs; //是否同意隐私政策 - - // 版本号 - //final String version = 'V1.0.0'; - - // 主题服务 - final ThemeService _themeService = ThemeService(); - - // 会话级别的版本检查标记(静态变量,整个应用会话期间保持) - static bool _hasCheckedVersionInSession = false; - - // 版本检查相关常量 - static const String _lastVersionCheckKey = 'last_version_check_timestamp'; - static const int _versionCheckIntervalHours = 24; // 24小时间隔 + // 使用一个反应式变量来决定是否可以导航,替代所有布尔标记 + final _canNavigate = false.obs; @override void onInit() { super.onInit(); - - // 首先检查隐私政策状态 - final hasAcceptedPrivacy = _storage.read('hasAcceptedPrivacy') ?? false; - WidgetsBinding.instance.addPostFrameCallback((_) { - if (!hasAcceptedPrivacy) { - // 优先显示隐私政策弹窗,暂停其他初始化 - _showPrivacyDialog(); - } else { - // 已同意隐私政策,正常启动流程 - _startNormalFlow(); - } - }); - - // 注意:追踪权限请求将在用户同意隐私政策后执行 - } - - /// 异步请求追踪权限 - Future _requestTrackingPermissionAsync() async { - try { - await AppTrackingTransparency.requestTrackingAuthorization(); - Logger.d(_tag, '用户追踪权限请求完成'); - } catch (e) { - Logger.e(_tag, '用户追踪权限请求失败: $e'); - } finally { - _isTrackingPermissionRequested.value = true; + // 监听 _canNavigate 状态,一旦为 true,立即且仅执行一次导航 + once(_canNavigate, (_) => _navigateToTargetPage()); + // 启动整个初始化流程 + _initializeApp(); + } + + /// 启动并协调所有初始化任务 + Future _initializeApp() async { + // 步骤 1: 检查并等待隐私政策同意 + final hasAccepted = await _checkAndRequestPrivacy(); + if (!hasAccepted) { + // 如果用户拒绝隐私政策,应用会退出,后续代码不执行 + return; } - } - void _startNormalFlow() { - _startLoadingAnimation(); - _startParallelTasks(); - } + // 步骤 2: 并行执行所有启动任务,并等待它们全部完成 + // Future.wait 会等待数组中所有的 Future 都完成后才继续 + await Future.wait([ + _startLoadingAnimation(), // 任务1: 最小动画时长 + _performParallelTasks(), // 任务2: 其他所有后台任务 + ]); - /// 启动并行任务:在1.5秒动画期间同时执行多个任务 - void _startParallelTasks() { - // 并行执行所有任务 - _requestTrackingPermissionAsync(); - _performVersionCheckAsync(); - _validateTokenAsync(); + // 步骤 3: 所有任务(动画和后台)都已完成,触发导航 + _canNavigate.value = true; } - /// 异步执行版本检查 - Future _performVersionCheckAsync() async { - try { - Logger.d(_tag, '开始执行版本检查'); - await _checkVersionUpdateIfNeeded(); - } catch (e) { - Logger.e(_tag, '版本检查失败: $e'); + /// 检查隐私政策,如果未同意,则显示弹窗并等待用户操作 + /// 返回用户是否同意 + Future _checkAndRequestPrivacy() async { + if (_storage.read('hasAcceptedPrivacy') ?? false) { + return true; } - //finally { - // _isVersionCheckCompleted.value = true; - // } - } - - void _onPrivacyAccepted() { - _storage.write('hasAcceptedPrivacy', true); - Get.back(); // 关闭弹窗 - _startNormalFlow(); // 开始正常流程(已包含版本检查) - } - void _onPrivacyRejected() { - exit(0); // 退出应用 - } + final completer = Completer(); + WidgetsBinding.instance.addPostFrameCallback((_) { + PrivacyPolicyDialog.show( + onAccept: () { + _storage.write('hasAcceptedPrivacy', true); + Get.back(); // 关闭弹窗 + if (!completer.isCompleted) completer.complete(true); + }, + onReject: () { + if (!completer.isCompleted) completer.complete(false); + exit(0); // 退出应用 + }, + canDismiss: false, + ); + }); - // 显示隐私政策弹窗 - void _showPrivacyDialog() { - PrivacyPolicyDialog.show( - onAccept: () { - _onPrivacyAccepted(); - _hasAcceptedPrivacy.value = true; - }, - onReject: _onPrivacyRejected, - canDismiss: false, // 禁止通过返回键关闭弹窗 - ); + return completer.future; } - // 进度条动画(持续1.5秒) - void _startLoadingAnimation() { + /// 动画任务:返回一个在动画结束时完成的 Future + Future _startLoadingAnimation() { const totalDuration = 1500; // 动画总时长1.5秒 - const steps = totalDuration ~/ 30; // 30ms间隔,更平滑的动画 + 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(); - _isAnimationCompleted.value = true; - _checkNavigateCondition(); + if (!completer.isCompleted) completer.complete(); } }); + + return completer.future; + } + + /// 将所有并行的后台任务打包成一个 Future + Future _performParallelTasks() async { + // 同样使用 Future.wait,确保此函数在所有子任务完成后才结束 + 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 { - bool isLoggedIn = false; final token = _storage.read('logintoken'); - Logger.d(_tag, 'Token验证开始: $token'); - if (token != null && token.isNotEmpty) { - isLoggedIn = await getLoginToken(); + final isLoggedIn = await getLoginToken(); if (isLoggedIn) { + // 只有登录成功,才获取App配置 await getAppConfig(); - _isAppConfigLoaded.value = true; + } else { + await User.clearUserSession(); // } } - - _isLoggedIn.value = isLoggedIn; - Logger.d(_tag, 'Token验证完成,登录状态: $isLoggedIn'); } catch (e) { - Logger.e(_tag, 'Token验证失败: $e'); - _isLoggedIn.value = false; - } finally { - _isTokenValidated.value = true; + Logger.e(_tag, 'Token验证或配置加载失败: $e'); + await User.clearUserSession(); } } - // 检查跳转条件:所有任务都完成后才跳转 - void _checkNavigateCondition() { - if (_isAnimationCompleted.value && - _isTrackingPermissionRequested.value && - _isVersionCheckCompleted.value && - _isTokenValidated.value) { - _navigateToTargetPage(); + /// 异步执行版本检查 + Future _performVersionCheckAsync() async { + try { + final versionService = VersionUpdateService.to; + final updateInfo = await versionService.checkForUpdate(); + + if (updateInfo.hasUpdate) { + Logger.i(_tag, '发现新版本: ${updateInfo.latestVersion}'); + final completer = Completer(); + // 在主线程中显示更新对话框 + WidgetsBinding.instance.addPostFrameCallback((_) { + // 使用 whenComplete 确保无论弹窗如何关闭,Future 都会完成 + VersionUpdateDialog.show(updateInfo).whenComplete(() { + if (!completer.isCompleted) completer.complete(); + }); + }); + // 等待弹窗交互结束 + await completer.future; + } else { + Logger.i(_tag, '当前已是最新版本'); + } + } catch (e) { + Logger.e(_tag, '版本检查失败: $e'); + // 即使检查失败,也视为任务完成 } } - // 根据登录状态跳转到目标页面 + /// 根据最终状态跳转到目标页面 void _navigateToTargetPage() { - if (_isLoggedIn.value && _isAppConfigLoaded.value) { + // 导航时直接检查最终的数据状态,而不是依赖中间变量 + 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 { - Logger.d(_tag, 'getLoginToken:进来了'); - //if (User.getname() != "") { final response = await Api.tokenlogin(); - Logger.d(_tag, 'getLoginToken: $response'); - if (response == null) return false; - - if (response['user'] != '') { - Logger.d(_tag, 'getLoginToken:进来了'); - // 根据API设计调整 + if (response?['user'] != null && response['user'] != '') { final user = User.fromJson(response); - - // 存储用户信息(示例) await _storage.write('user_info', user.toJson()); return true; } - //return true; - //} return false; } catch (e) { Logger.e(_tag, 'Token获取失败: $e'); @@ -218,140 +200,14 @@ class SplashController extends GetxController { Future getAppConfig() async { try { - Logger.d(_tag, 'getAppConfig:进来了'); - //if (User.getname() != "") { final response = await Api.getappconfig(); - Logger.d(_tag, 'getAppConfig: $response'); if (response == null) return false; - // 根据API设计调整 final resp = UserGetAppConfigResp.fromJson(response); - Logger.d(_tag, 'getAppConfig:进来了'); - // 存储用户信息(示例) - // _storage.write('ENV', resp.env); - // _storage.write('AGENTS', resp.agents); - // _storage.write('MCPS', resp.mcps); AppConfig.initialize(resp); return true; } catch (e) { - Logger.e(_tag, 'getAppConfig 取失败: $e'); + Logger.e(_tag, 'getAppConfig 获取失败: $e'); return false; } } - - /// 混合检查机制:判断是否需要进行版本检查 - /// 检查条件:1. 会话内未检查过 OR 2. 距离上次检查超过24小时 - Future _checkVersionUpdateIfNeeded() async { - try { - final now = DateTime.now(); - final lastCheckTimestamp = _storage.read(_lastVersionCheckKey); - - bool shouldCheck = false; - String reason = ''; - - // 条件1:会话内未检查过 - if (!_hasCheckedVersionInSession) { - shouldCheck = true; - reason = '会话内首次检查'; - } - // 条件2:距离上次检查超过24小时 - else if (lastCheckTimestamp != null) { - final lastCheckTime = - DateTime.fromMillisecondsSinceEpoch(lastCheckTimestamp); - final timeDifference = now.difference(lastCheckTime); - - if (timeDifference.inHours >= _versionCheckIntervalHours) { - shouldCheck = true; - reason = '距离上次检查已超过${_versionCheckIntervalHours}小时'; - } - } - // 条件3:从未检查过(首次使用应用) - else { - shouldCheck = true; - reason = '首次使用应用'; - } - - if (shouldCheck) { - Logger.i(_tag, '开始版本检查 - 原因: $reason'); - await _performVersionCheck(); - } else { - Logger.i(_tag, '跳过版本检查 - 会话内已检查且未超过时间间隔'); - } - } catch (e) { - Logger.e(_tag, '版本检查判断失败: $e'); - } - } - - /// 执行版本检查(在后台进行,不阻塞启动流程) - Future _performVersionCheck() async { - try { - final versionService = VersionUpdateService.to; - // 异步执行,不等待结果 - versionService.checkForUpdate().then((updateInfo) { - if (updateInfo.hasUpdate) { - Logger.i(_tag, '发现新版本: ${updateInfo.latestVersion}'); - // 在主线程中显示更新对话框 - WidgetsBinding.instance.addPostFrameCallback((_) { - VersionUpdateDialog.show(updateInfo).then((_) { - // 无论用户选择更新还是稍后提醒,都标记版本检查完成 - _isVersionCheckCompleted.value = true; - _checkNavigateCondition(); - }); - }); - } else { - _isVersionCheckCompleted.value = true; - Logger.i(_tag, '当前已是最新版本: ${updateInfo.currentVersion}'); - } - - // 更新检查记录 - _updateVersionCheckRecord(); - }).catchError((e) { - Logger.e(_tag, '版本检查失败: $e'); - // 即使检查失败,也标记版本检查完成,让流程继续 - _isVersionCheckCompleted.value = true; - // 更新检查记录,避免频繁重试 - _updateVersionCheckRecord(); - }); - } catch (e) { - Logger.e(_tag, '版本检查失败: $e'); - // 即使检查失败,也标记版本检查完成,让流程继续 - _isVersionCheckCompleted.value = true; - // 更新检查记录,避免频繁重试 - _updateVersionCheckRecord(); - } - } - - /// 更新版本检查记录 - void _updateVersionCheckRecord() { - try { - // 更新时间戳 - final now = DateTime.now(); - _storage.write(_lastVersionCheckKey, now.millisecondsSinceEpoch); - - // 标记本次会话已检查过版本 - _hasCheckedVersionInSession = true; - - Logger.i(_tag, '版本检查记录已更新: ${now.toIso8601String()}'); - } catch (e) { - Logger.e(_tag, '更新版本检查记录失败: $e'); - } - } - - /// 重置版本检查标记(可用于强制重新检查) - /// 注意:这会清除会话标记,但不会清除时间戳记录 - static void resetVersionCheckFlag() { - _hasCheckedVersionInSession = false; - Logger.i('SplashCtrl', '版本检查会话标记已重置'); - } - - /// 完全重置版本检查状态(包括时间戳) - /// 用于测试或特殊情况下强制重新检查 - void forceResetVersionCheck() { - try { - _hasCheckedVersionInSession = false; - _storage.remove(_lastVersionCheckKey); - Logger.i(_tag, '版本检查状态已完全重置'); - } catch (e) { - Logger.e(_tag, '重置版本检查状态失败: $e'); - } - } }