Browse Source

refactor(splash): 重构启动流程并优化状态管理

- 使用单一反应式变量 `_canNavigate` 替代多个布尔标记
- 将并行任务封装为独立方法并通过 `Future.wait` 协调
- 新增 `AppConfig.isInitialized()` 和 `User.isLoggedIn()` 方法
- 清理用户会话时增加本地存储和内存数据的清除
- 简化导航逻辑,直接检查最终数据状态
newdev_shunjiawei
lxm 1 year ago
parent
commit
5cdd3e4a57
  1. 11
      lib/data/models/appconfig.dart
  2. 22
      lib/data/models/user_Info.dart
  3. 364
      lib/modules/splash/controllers/splash_controller.dart

11
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; // <-- 新增此行
}
//环境变量

22
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<void> 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<String, dynamic>?; // 允许空值
final devicesData = json['devices'] as List<dynamic>?; // 允许空值
print("数据获取fronTokenJson${userData} ${devicesData}");
print("数据获取fronTokenJson$userData $devicesData");
// 更新单例实例
return _instance = User._(
token: '',

364
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();
}
});
// 注意:追踪权限请求将在用户同意隐私政策后执行
// 监听 _canNavigate 状态,一旦为 true,立即且仅执行一次导航
once(_canNavigate, (_) => _navigateToTargetPage());
// 启动整个初始化流程
_initializeApp();
}
/// 异步请求追踪权限
Future<void> _requestTrackingPermissionAsync() async {
try {
await AppTrackingTransparency.requestTrackingAuthorization();
Logger.d(_tag, '用户追踪权限请求完成');
} catch (e) {
Logger.e(_tag, '用户追踪权限请求失败: $e');
} finally {
_isTrackingPermissionRequested.value = true;
}
/// 启动并协调所有初始化任务
Future<void> _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<void> _performVersionCheckAsync() async {
try {
Logger.d(_tag, '开始执行版本检查');
await _checkVersionUpdateIfNeeded();
} catch (e) {
Logger.e(_tag, '版本检查失败: $e');
}
//finally {
// _isVersionCheckCompleted.value = true;
// }
/// 检查隐私政策,如果未同意,则显示弹窗并等待用户操作
/// 返回用户是否同意
Future<bool> _checkAndRequestPrivacy() async {
if (_storage.read<bool>('hasAcceptedPrivacy') ?? false) {
return true;
}
void _onPrivacyAccepted() {
final completer = Completer<bool>();
WidgetsBinding.instance.addPostFrameCallback((_) {
PrivacyPolicyDialog.show(
onAccept: () {
_storage.write('hasAcceptedPrivacy', true);
Get.back(); // 关闭弹窗
_startNormalFlow(); // 开始正常流程(已包含版本检查)
}
void _onPrivacyRejected() {
if (!completer.isCompleted) completer.complete(true);
},
onReject: () {
if (!completer.isCompleted) completer.complete(false);
exit(0); // 退出应用
}
// 显示隐私政策弹窗
void _showPrivacyDialog() {
PrivacyPolicyDialog.show(
onAccept: () {
_onPrivacyAccepted();
_hasAcceptedPrivacy.value = true;
},
onReject: _onPrivacyRejected,
canDismiss: false, // 禁止通过返回键关闭弹窗
canDismiss: false,
);
});
return completer.future;
}
// 进度条动画(持续1.5秒)
void _startLoadingAnimation() {
/// 动画任务:返回一个在动画结束时完成的 Future
Future<void> _startLoadingAnimation() {
const totalDuration = 1500; // 动画总时长1.5秒
const steps = totalDuration ~/ 30; // 30ms间隔,更平滑的动画
const steps = totalDuration ~/ 30;
const stepValue = 1.0 / steps;
final completer = Completer<void>();
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<void> _performParallelTasks() async {
// 同样使用 Future.wait,确保此函数在所有子任务完成后才结束
await Future.wait([
_requestTrackingPermissionAsync(),
_validateTokenAsync(),
_performVersionCheckAsync(),
]);
Logger.d(_tag, '所有后台任务已完成');
}
/// 异步请求追踪权限
Future<void> _requestTrackingPermissionAsync() async {
try {
await AppTrackingTransparency.requestTrackingAuthorization();
Logger.d(_tag, '用户追踪权限请求完成');
} catch (e) {
Logger.e(_tag, '用户追踪权限请求失败: $e');
// 即使失败,也视为完成,不阻塞主流程
}
}
/// 异步验证Token和加载应用配置
Future<void> _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<void> _performVersionCheckAsync() async {
try {
final versionService = VersionUpdateService.to;
final updateInfo = await versionService.checkForUpdate();
if (updateInfo.hasUpdate) {
Logger.i(_tag, '发现新版本: ${updateInfo.latestVersion}');
final completer = Completer<void>();
// 在主线程中显示更新对话框
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<bool> 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<bool> 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<void> _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<void> _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');
}
}
}

Loading…
Cancel
Save