You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
325 lines
10 KiB
325 lines
10 KiB
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 _canNavigate = false.obs;
|
|
|
|
@override
|
|
void onInit() {
|
|
super.onInit();
|
|
// 监听 _canNavigate 状态,一旦为 true,立即且仅执行一次导航
|
|
once(_canNavigate, (_) => _navigateToTargetPage());
|
|
// 启动整个初始化流程
|
|
_initializeApp();
|
|
}
|
|
|
|
/// 启动并协调所有初始化任务
|
|
Future<void> _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;
|
|
}
|
|
|
|
/// 检查隐私政策,如果未同意,则显示弹窗并等待用户操作
|
|
/// 返回用户是否同意
|
|
Future<bool> _checkAndRequestPrivacy() async {
|
|
if (_storage.read<bool>('hasAcceptedPrivacy') ?? false) {
|
|
return true;
|
|
}
|
|
|
|
final completer = Completer<bool>();
|
|
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,
|
|
);
|
|
});
|
|
|
|
return completer.future;
|
|
}
|
|
|
|
/// 网络连接检查
|
|
/// 网络连接检查 - 阻塞版本
|
|
Future<void> _checkNetworkStatus() async {
|
|
while (true) {
|
|
// 无限循环直到网络检查成功
|
|
try {
|
|
final connectivityResult = await Connectivity().checkConnectivity();
|
|
|
|
// 检查是否有网络连接
|
|
if (connectivityResult.contains(ConnectivityResult.none)) {
|
|
// 显示弹窗并等待用户操作
|
|
await _showNoNetworkDialogAndWait();
|
|
continue; // 重新检查
|
|
}
|
|
|
|
// 进一步检查网络连通性
|
|
final hasInternet = await _checkInternetConnection();
|
|
if (!hasInternet) {
|
|
// 显示弹窗并等待用户操作
|
|
await _showNoNetworkDialogAndWait();
|
|
continue; // 重新检查
|
|
}
|
|
|
|
// 网络检查成功,跳出循环
|
|
Logger.d(_tag, '网络检查成功,继续APP初始化流程');
|
|
break;
|
|
} catch (e) {
|
|
Logger.e(_tag, '网络检查失败: $e');
|
|
// 显示弹窗并等待用户操作
|
|
await _showNoNetworkDialogAndWait();
|
|
continue; // 重新检查
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 检查实际的网络连通性
|
|
Future<bool> _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<void> _startLoadingAnimation() {
|
|
const totalDuration = 1500; // 动画总时长1.5秒
|
|
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();
|
|
if (!completer.isCompleted) completer.complete();
|
|
}
|
|
});
|
|
|
|
return completer.future;
|
|
}
|
|
|
|
/// 将所有并行的后台任务打包成一个 Future
|
|
Future<void> _performParallelTasks() async {
|
|
//步骤2:检查网络连接状态
|
|
await _checkNetworkStatus();
|
|
// 同样使用 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 {
|
|
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<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() {
|
|
// 导航时直接检查最终的数据状态,而不是依赖中间变量
|
|
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 {
|
|
final appVersion = versionService.cachedAppVersion;
|
|
final response = await Api.tokenlogin({
|
|
'version': appVersion,
|
|
});
|
|
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<bool> 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/// 显示无网络连接对话框并等待用户操作(阻塞版本)
|
|
Future<void> _showNoNetworkDialogAndWait() async {
|
|
final completer = Completer<void>();
|
|
|
|
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;
|
|
}
|
|
|