AtmoSphere/lib/tools/start_page.dart
2026-01-16 18:22:32 +08:00

283 lines
8.4 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'dart:io' show Platform;
import 'package:flutter/material.dart';
import 'package:unity_levelplay_mediation/unity_levelplay_mediation.dart';
import '../screens/main_screen.dart';
import 'app_ads_managers.dart';
/// Loading 页面 - 显示 logo 和 loading 动画,初始化广告
class StartPage extends StatefulWidget {
final Widget Function()? homePageBuilder;
const StartPage({super.key, this.homePageBuilder});
@override
State<StartPage> createState() => _StartPageState();
}
class _StartPageState extends State<StartPage> with WidgetsBindingObserver {
final InterstitialAdManager _adManager = InterstitialAdManager();
bool _hasRequestedATT = false;
bool _isInitializing = false;
bool _isWaitingForATT = false;
@override
void initState() {
super.initState();
WidgetsBinding.instance.addObserver(this);
_initializeAds();
}
@override
void dispose() {
WidgetsBinding.instance.removeObserver(this);
super.dispose();
}
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
// 当应用从后台回到前台时如果还没有请求过ATT则请求
if (state == AppLifecycleState.resumed &&
!_hasRequestedATT &&
Platform.isIOS &&
!_isInitializing) {
_requestATTWhenReady();
}
}
Future<void> _initializeAds() async {
_isInitializing = true;
try {
// iOS 平台:智能等待并请求 ATT 权限
if (Platform.isIOS) {
await _requestATTWhenReady();
}
// ATT 请求完成后,初始化广告管理器
_adManager.initialize();
// 等待初始化完成
await _waitForInitialization();
if (mounted) {
// 并行加载三个广告,任意一个成功就立即跳转并显示
_adManager.loadInitialSplashAd(
onAdReady: (adType) {
if (mounted) {
// 延迟一小段时间后跳转并显示广告
Future.delayed(const Duration(milliseconds: 500)).then((_) {
if (mounted) {
_navigateToHomeAndShowAd(adType);
}
});
}
},
onAllAdsFailed: () {
if (mounted) {
// 所有广告都失败,延迟后进入主页
Future.delayed(const Duration(seconds: 1)).then((_) {
if (mounted) {
_navigateToHome();
}
});
}
},
);
}
} catch (e) {
if (mounted) {
// 即使初始化失败,也允许进入主页(广告可能稍后可用)
await Future.delayed(const Duration(seconds: 2));
if (mounted) {
_navigateToHome();
}
}
} finally {
_isInitializing = false;
}
}
/// 导航到主页并显示广告
void _navigateToHomeAndShowAd(InterstitialAdType adType) {
// 对于开屏场景,直接跳到 HomePage并把已准备好的广告类型传过去
Navigator.of(context).pushReplacement(
MaterialPageRoute(
builder: (context) => MainScreen(initialSplashAdType: adType),
),
);
}
/// 导航到主页
void _navigateToHome() {
if (widget.homePageBuilder != null) {
Navigator.of(context).pushReplacement(
MaterialPageRoute(builder: (context) => widget.homePageBuilder!()),
);
}
}
/// 智能等待并请求 ATT 权限,确保没有其他权限对话框在显示
Future<void> _requestATTWhenReady() async {
if (_hasRequestedATT || _isWaitingForATT) return;
_isWaitingForATT = true;
try {
// 检查当前ATT状态
final currentStatus =
await ATTrackingManager.getTrackingAuthorizationStatus();
debugPrint('LoadingPage: ATT Status: $currentStatus');
if (currentStatus != ATTStatus.NotDetermined) {
_hasRequestedATT = true;
_isWaitingForATT = false;
return;
}
// 初始延迟,让系统处理其他权限
await Future.delayed(const Duration(milliseconds: 2000));
// 智能等待:检测是否有其他系统对话框
await _waitForSystemDialogsToClear();
// 最终检查:确保状态仍然是 NotDetermined
final finalStatus =
await ATTrackingManager.getTrackingAuthorizationStatus();
if (finalStatus == ATTStatus.NotDetermined && mounted) {
_hasRequestedATT = true;
debugPrint('LoadingPage: Requesting ATT permission...');
final returnedStatus =
await ATTrackingManager.requestTrackingAuthorization();
debugPrint('LoadingPage: ATT Status returned: $returnedStatus');
} else {
_hasRequestedATT = true;
}
} catch (e) {
debugPrint('LoadingPage: ATT request error: $e');
_hasRequestedATT = true;
} finally {
_isWaitingForATT = false;
}
}
/// 等待系统对话框清除
/// 通过监听应用生命周期状态变化来检测是否有系统对话框
Future<void> _waitForSystemDialogsToClear() async {
const maxWaitTime = Duration(seconds: 30); // 最多等待30秒
const checkInterval = Duration(milliseconds: 500);
final startTime = DateTime.now();
AppLifecycleState? lastState = WidgetsBinding.instance.lifecycleState;
int stableCount = 0;
const requiredStableCount = 4; // 需要连续2秒保持稳定状态
while (DateTime.now().difference(startTime) < maxWaitTime) {
if (!mounted) break;
await Future.delayed(checkInterval);
final currentState = WidgetsBinding.instance.lifecycleState;
// 如果应用状态稳定在 resumed说明没有系统对话框
if (currentState == AppLifecycleState.resumed) {
if (lastState == AppLifecycleState.resumed) {
stableCount++;
if (stableCount >= requiredStableCount) {
debugPrint('LoadingPage: App state stable, ready for ATT request');
break;
}
} else {
stableCount = 1;
}
} else {
// 如果状态不是 resumed重置计数
stableCount = 0;
debugPrint('LoadingPage: App state: $currentState, waiting...');
}
lastState = currentState;
}
// 额外的安全延迟
await Future.delayed(const Duration(milliseconds: 1000));
}
/// 等待广告管理器初始化完成
Future<void> _waitForInitialization() async {
// 轮询检查初始化状态,最多等待 15 秒
const maxWaitTime = Duration(seconds: 15);
const checkInterval = Duration(milliseconds: 200);
final startTime = DateTime.now();
while (DateTime.now().difference(startTime) < maxWaitTime) {
if (_adManager.isInitialized) {
return;
}
await Future.delayed(checkInterval);
}
// 如果超时仍未初始化,记录警告但继续
if (!_adManager.isInitialized) {
debugPrint('Warning: Ad initialization timeout, proceeding anyway');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Color(0xff141420),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// Logo with animation
TweenAnimationBuilder<double>(
tween: Tween(begin: 0.0, end: 1.0),
duration: const Duration(milliseconds: 800),
curve: Curves.easeOut,
builder: (context, value, child) {
return Opacity(
opacity: value,
child: Transform.scale(scale: value, child: child),
);
},
child: Image.asset(
'assets/images/stonicons.png',
width: 100,
height: 100,
fit: BoxFit.contain,
),
),
const SizedBox(height: 5),
// 状态文本
const Text(
'Resource Loading',
style: TextStyle(
fontSize: 16,
color: Colors.white,
fontWeight: FontWeight.w500,
),
),
const SizedBox(height: 5),
// Loading 动画
const SizedBox(
width: 30,
height: 30,
child: CircularProgressIndicator(
strokeWidth: 4,
valueColor: AlwaysStoppedAnimation<Color>(Color(0xFFFFFFFF)),
),
),
const SizedBox(height: 30),
],
),
),
);
}
}