优化:启动优化

This commit is contained in:
ivan 2026-03-30 01:39:47 +08:00
parent fb4d1b78d6
commit f06c49f22a
27 changed files with 107 additions and 30 deletions

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 247 KiB

After

Width:  |  Height:  |  Size: 69 B

View File

@ -3,4 +3,7 @@
<item> <item>
<bitmap android:gravity="fill" android:src="@drawable/background"/> <bitmap android:gravity="fill" android:src="@drawable/background"/>
</item> </item>
<item>
<bitmap android:gravity="fill|clip_vertical" android:src="@drawable/splash"/>
</item>
</layer-list> </layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 346 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 601 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 247 KiB

After

Width:  |  Height:  |  Size: 69 B

View File

@ -3,4 +3,7 @@
<item> <item>
<bitmap android:gravity="fill" android:src="@drawable/background"/> <bitmap android:gravity="fill" android:src="@drawable/background"/>
</item> </item>
<item>
<bitmap android:gravity="fill|clip_vertical" android:src="@drawable/splash"/>
</item>
</layer-list> </layer-list>

View File

@ -4,11 +4,12 @@
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar"> <style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">@drawable/launch_background</item> <item name="android:windowBackground">@drawable/launch_background</item>
<item name="android:forceDarkAllowed">false</item> <item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item> <item name="android:windowFullscreen">true</item>
<item name="android:windowDrawsSystemBarBackgrounds">false</item> <item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item> <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
<item name="android:windowSplashScreenBackground">#0a0a12</item> <item name="android:windowSplashScreenBackground">#0a0a12</item>
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item> <item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item>
<item name="android:windowSplashScreenIconBackgroundColor">#0a0a12</item>
</style> </style>
<!-- Theme applied to the Android Window as soon as the process has started. <!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your This theme determines the color of the Android Window while your

View File

@ -3,8 +3,8 @@
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar"> <style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">@drawable/launch_background</item> <item name="android:windowBackground">@drawable/launch_background</item>
<item name="android:forceDarkAllowed">false</item> <item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item> <item name="android:windowFullscreen">true</item>
<item name="android:windowDrawsSystemBarBackgrounds">false</item> <item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item> <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
</style> </style>
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar"> <style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">

View File

@ -4,11 +4,12 @@
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar"> <style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">@drawable/launch_background</item> <item name="android:windowBackground">@drawable/launch_background</item>
<item name="android:forceDarkAllowed">false</item> <item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item> <item name="android:windowFullscreen">true</item>
<item name="android:windowDrawsSystemBarBackgrounds">false</item> <item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item> <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
<item name="android:windowSplashScreenBackground">#0a0a12</item> <item name="android:windowSplashScreenBackground">#0a0a12</item>
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item> <item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item>
<item name="android:windowSplashScreenIconBackgroundColor">#0a0a12</item>
</style> </style>
<!-- Theme applied to the Android Window as soon as the process has started. <!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your This theme determines the color of the Android Window while your

View File

@ -3,8 +3,8 @@
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar"> <style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">@drawable/launch_background</item> <item name="android:windowBackground">@drawable/launch_background</item>
<item name="android:forceDarkAllowed">false</item> <item name="android:forceDarkAllowed">false</item>
<item name="android:windowFullscreen">false</item> <item name="android:windowFullscreen">true</item>
<item name="android:windowDrawsSystemBarBackgrounds">false</item> <item name="android:windowDrawsSystemBarBackgrounds">true</item>
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item> <item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
</style> </style>
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar"> <style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">

Binary file not shown.

Before

Width:  |  Height:  |  Size: 247 KiB

After

Width:  |  Height:  |  Size: 69 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 B

After

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 B

After

Width:  |  Height:  |  Size: 160 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 B

After

Width:  |  Height:  |  Size: 346 KiB

View File

@ -16,14 +16,19 @@
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3"> <view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/> <autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews> <subviews>
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" image="LaunchBackground" translatesAutoresizingMaskIntoConstraints="NO" id="tWc-Dq-wcI"/> <imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleToFill" image="LaunchBackground" translatesAutoresizingMaskIntoConstraints="NO" id="tWc-Dq-wcI"/>
<imageView opaque="NO" clipsSubviews="YES" multipleTouchEnabled="YES" contentMode="scaleAspectFill" image="LaunchImage" translatesAutoresizingMaskIntoConstraints="NO" id="YRO-k0-Ey4"></imageView>
</subviews> </subviews>
<color key="backgroundColor" red="0.04" green="0.04" blue="0.07" alpha="1" colorSpace="custom" customColorSpace="sRGB"/> <color key="backgroundColor" red="0.039215686274509803" green="0.039215686274509803" blue="0.07058823529411765" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints> <constraints>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="leading" secondItem="Ze5-6b-2t3" secondAttribute="leading" id="3T2-ad-Qdv"/>
<constraint firstItem="tWc-Dq-wcI" firstAttribute="bottom" secondItem="Ze5-6b-2t3" secondAttribute="bottom" id="RPx-PI-7Xg"/> <constraint firstItem="tWc-Dq-wcI" firstAttribute="bottom" secondItem="Ze5-6b-2t3" secondAttribute="bottom" id="RPx-PI-7Xg"/>
<constraint firstItem="tWc-Dq-wcI" firstAttribute="top" secondItem="Ze5-6b-2t3" secondAttribute="top" id="SdS-ul-q2q"/> <constraint firstItem="tWc-Dq-wcI" firstAttribute="top" secondItem="Ze5-6b-2t3" secondAttribute="top" id="SdS-ul-q2q"/>
<constraint firstAttribute="trailing" secondItem="tWc-Dq-wcI" secondAttribute="trailing" id="Swv-Gf-Rwn"/> <constraint firstAttribute="trailing" secondItem="tWc-Dq-wcI" secondAttribute="trailing" id="Swv-Gf-Rwn"/>
<constraint firstAttribute="trailing" secondItem="YRO-k0-Ey4" secondAttribute="trailing" id="TQA-XW-tRk"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="bottom" secondItem="Ze5-6b-2t3" secondAttribute="bottom" id="duK-uY-Gun"/>
<constraint firstItem="tWc-Dq-wcI" firstAttribute="leading" secondItem="Ze5-6b-2t3" secondAttribute="leading" id="kV7-tw-vXt"/> <constraint firstItem="tWc-Dq-wcI" firstAttribute="leading" secondItem="Ze5-6b-2t3" secondAttribute="leading" id="kV7-tw-vXt"/>
<constraint firstItem="YRO-k0-Ey4" firstAttribute="top" secondItem="Ze5-6b-2t3" secondAttribute="top" id="xPn-NY-SIU"/>
</constraints> </constraints>
</view> </view>
</viewController> </viewController>
@ -33,6 +38,7 @@
</scene> </scene>
</scenes> </scenes>
<resources> <resources>
<image name="LaunchBackground" width="375" height="812"/> <image name="LaunchImage" width="512" height="512"/>
<image name="LaunchBackground" width="1" height="1"/>
</resources> </resources>
</document> </document>

View File

@ -51,6 +51,8 @@
<string>UIInterfaceOrientationLandscapeRight</string> <string>UIInterfaceOrientationLandscapeRight</string>
</array> </array>
<key>UIStatusBarHidden</key> <key>UIStatusBarHidden</key>
<true/>
<key>UIViewControllerBasedStatusBarAppearance</key>
<false/> <false/>
</dict> </dict>
</plist> </plist>

View File

@ -197,8 +197,8 @@ class AuthService {
const retryDelay = Duration(seconds: 2); const retryDelay = Duration(seconds: 2);
try { try {
// 等待网络就绪(浏览器能访问但 App 报错时,多为启动时网络未初始化) // 极短让步:避免个别机型刚起进程时网络栈未就绪;过长会拖慢冷启动
await Future<void>.delayed(const Duration(seconds: 2)); await Future<void>.delayed(const Duration(milliseconds: 400));
final deviceId = await _getDeviceId(); final deviceId = await _getDeviceId();
_logMsg('init: deviceId=$deviceId'); _logMsg('init: deviceId=$deviceId');

View File

@ -18,7 +18,8 @@ class ReferrerService {
static final Completer<AdjustAttribution?> _attributionCallbackCompleter = static final Completer<AdjustAttribution?> _attributionCallbackCompleter =
Completer<AdjustAttribution?>(); Completer<AdjustAttribution?>();
static const int _adjustTimeoutMs = 15000; /// 过长会拖慢 [AuthService.init] 内首次 getReferrer;过短可能拿不到 Adjust,仍有 Play Install Referrer fallback
static const int _adjustTimeoutMs = 4500;
/// 由 Adjust attributionCallback 调用,首次安装时归因往往通过此回调返回(晚于 getAttributionWithTimeout) /// 由 Adjust attributionCallback 调用,首次安装时归因往往通过此回调返回(晚于 getAttributionWithTimeout)
static void receiveAttributionFromCallback(AdjustAttribution attribution) { static void receiveAttributionFromCallback(AdjustAttribution attribution) {

View File

@ -1,4 +1,7 @@
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
/// 子路由从 Navigator pop 回主导航后,通知首页刷新 [VisibilityDetector](见 [_MainScaffoldState.didPopNext]) /// 统一信号:递增后首页多帧刷新 [VisibilityDetector],视频卡片尝试恢复 [VideoPlayer]。
///
/// 递增场景包括:子路由 pop 回首页、底部导航切回首页、前后台切换回前台(首页 Tab)、
/// 列表/分类与 ext 配置就绪后需重算可见卡片等(见 `HomeScreen._requestPlaybackPipeline`)。
final ValueNotifier<int> homePlaybackResumeNonce = ValueNotifier<int>(0); final ValueNotifier<int> homePlaybackResumeNonce = ValueNotifier<int>(0);

View File

@ -27,7 +27,7 @@ class HomeScreen extends StatefulWidget {
State<HomeScreen> createState() => _HomeScreenState(); State<HomeScreen> createState() => _HomeScreenState();
} }
class _HomeScreenState extends State<HomeScreen> { class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
List<CategoryItem> _categories = []; List<CategoryItem> _categories = [];
CategoryItem? _selectedCategory; CategoryItem? _selectedCategory;
List<TaskItem> _tasks = []; List<TaskItem> _tasks = [];
@ -44,8 +44,13 @@ class _HomeScreenState extends State<HomeScreen> {
/// 是否曾有过「可见比例 ≥ 阈值」的探测器回调(用于区分冷启动无回调 vs 已全部滑出视口) /// 是否曾有过「可见比例 ≥ 阈值」的探测器回调(用于区分冷启动无回调 vs 已全部滑出视口)
bool _visibilityHadMeaningfulReport = false; bool _visibilityHadMeaningfulReport = false;
/// IndexedStack 切回首页或 [homePlaybackResumeNonce] 递增后,让 [VisibilityDetector] 重新计算。 /// 统一入口:递增 nonce → [VideoCard] 恢复播放 + [_onHomePlaybackResumeSignal] 内多帧刷新可见性。
/// 单次 [notifyNow] 在「Modal 收起 / 子路由 pop」后首帧常过早,与 My Gallery 一致采用多帧 + 短延迟。 void _requestPlaybackPipeline() {
if (!mounted || !widget.isActive) return;
homePlaybackResumeNonce.value++;
}
/// [homePlaybackResumeNonce] 监听里调用:多帧 + 短延迟 [notifyNow],避免单次过早。
void _scheduleVisibilityRefresh() { void _scheduleVisibilityRefresh() {
if (!mounted || !widget.isActive) return; if (!mounted || !widget.isActive) return;
void notify() { void notify() {
@ -80,6 +85,7 @@ class _HomeScreenState extends State<HomeScreen> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
WidgetsBinding.instance.addObserver(this);
UserState.needShowVideoMenu.addListener(_onExtConfigChanged); UserState.needShowVideoMenu.addListener(_onExtConfigChanged);
UserState.extConfigItems.addListener(_onExtConfigChanged); UserState.extConfigItems.addListener(_onExtConfigChanged);
AuthService.isLoginComplete.addListener(_onExtConfigChanged); AuthService.isLoginComplete.addListener(_onExtConfigChanged);
@ -106,6 +112,7 @@ class _HomeScreenState extends State<HomeScreen> {
@override @override
void dispose() { void dispose() {
WidgetsBinding.instance.removeObserver(this);
homePlaybackResumeNonce.removeListener(_onHomePlaybackResumeSignal); homePlaybackResumeNonce.removeListener(_onHomePlaybackResumeSignal);
UserState.needShowVideoMenu.removeListener(_onExtConfigChanged); UserState.needShowVideoMenu.removeListener(_onExtConfigChanged);
UserState.extConfigItems.removeListener(_onExtConfigChanged); UserState.extConfigItems.removeListener(_onExtConfigChanged);
@ -120,11 +127,19 @@ class _HomeScreenState extends State<HomeScreen> {
_scheduleVisibilityRefresh(); _scheduleVisibilityRefresh();
} }
@override
void didChangeAppLifecycleState(AppLifecycleState state) {
super.didChangeAppLifecycleState(state);
if (state == AppLifecycleState.resumed && mounted && widget.isActive) {
_requestPlaybackPipeline();
}
}
void _onExtConfigChanged() { void _onExtConfigChanged() {
if (mounted) setState(() {}); if (mounted) setState(() {});
// 不用 Timer 防抖:连续 listener 会互相 cancel,首装可能永远不触发 notify // 不用 Timer 防抖:连续 listener 会互相 cancel,首装可能永远不触发 notify
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted && widget.isActive) _scheduleVisibilityRefresh(); if (mounted && widget.isActive) _requestPlaybackPipeline();
}); });
} }
@ -139,7 +154,7 @@ class _HomeScreenState extends State<HomeScreen> {
static const int _maxConcurrentHomeVideos = 16; static const int _maxConcurrentHomeVideos = 16;
void _onGridCardVisibilityChanged(int index, VisibilityInfo info) { void _onGridCardVisibilityChanged(int index, VisibilityInfo info) {
if (!mounted) return; if (!mounted || !widget.isActive) return;
if (info.visibleFraction >= _videoVisibilityThreshold) { if (info.visibleFraction >= _videoVisibilityThreshold) {
_cardVisibleFraction[index] = info.visibleFraction; _cardVisibleFraction[index] = info.visibleFraction;
_visibilityHadMeaningfulReport = true; _visibilityHadMeaningfulReport = true;
@ -155,6 +170,7 @@ class _HomeScreenState extends State<HomeScreen> {
} }
void _reconcileVisibleVideoIndicesFromDetector() { void _reconcileVisibleVideoIndicesFromDetector() {
if (!mounted || !widget.isActive) return;
final tasks = _displayTasks; final tasks = _displayTasks;
final scored = <MapEntry<int, double>>[]; final scored = <MapEntry<int, double>>[];
for (final e in _cardVisibleFraction.entries) { for (final e in _cardVisibleFraction.entries) {
@ -200,7 +216,7 @@ class _HomeScreenState extends State<HomeScreen> {
super.didUpdateWidget(oldWidget); super.didUpdateWidget(oldWidget);
if (widget.isActive && !oldWidget.isActive) { if (widget.isActive && !oldWidget.isActive) {
refreshAccount(); refreshAccount();
_scheduleVisibilityRefresh(); _requestPlaybackPipeline();
} }
} }
@ -300,7 +316,7 @@ class _HomeScreenState extends State<HomeScreen> {
setState(() => _categoriesLoading = false); setState(() => _categoriesLoading = false);
// 默认选中 pets 等不会走 [_loadTasks] 的路径,也要触发一次可见性(否则预览不自动播) // 默认选中 pets 等不会走 [_loadTasks] 的路径,也要触发一次可见性(否则预览不自动播)
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted && widget.isActive) _scheduleVisibilityRefresh(); if (mounted && widget.isActive) _requestPlaybackPipeline();
}); });
} }
} }
@ -332,7 +348,7 @@ class _HomeScreenState extends State<HomeScreen> {
setState(() => _tasksLoading = false); setState(() => _tasksLoading = false);
// 列表替换后须强制可见性重算,否则探测器有时不回调,预览一直不播 // 列表替换后须强制可见性重算,否则探测器有时不回调,预览一直不播
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted && widget.isActive) _scheduleVisibilityRefresh(); if (mounted && widget.isActive) _requestPlaybackPipeline();
}); });
} }
} }
@ -352,7 +368,7 @@ class _HomeScreenState extends State<HomeScreen> {
_visibilityHadMeaningfulReport = false; _visibilityHadMeaningfulReport = false;
}); });
WidgetsBinding.instance.addPostFrameCallback((_) { WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted && widget.isActive) _scheduleVisibilityRefresh(); if (mounted && widget.isActive) _requestPlaybackPipeline();
}); });
} else { } else {
_loadTasks(c.id); _loadTasks(c.id);
@ -447,6 +463,8 @@ class _HomeScreenState extends State<HomeScreen> {
_placeholderImage, _placeholderImage,
videoUrl: task.previewVideoUrl, videoUrl: task.previewVideoUrl,
credits: credits, credits: credits,
playbackResumeListenable:
homePlaybackResumeNonce,
isActive: widget.isActive && isActive: widget.isActive &&
_visibleVideoIndices.contains(index) && _visibleVideoIndices.contains(index) &&
!_userPausedVideoIndices !_userPausedVideoIndices

View File

@ -1,6 +1,7 @@
import 'dart:async' show unawaited; import 'dart:async' show unawaited;
import 'dart:io' show File; import 'dart:io' show File;
import 'package:flutter/foundation.dart' show ValueListenable;
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_cache_manager/flutter_cache_manager.dart' import 'package:flutter_cache_manager/flutter_cache_manager.dart'
show DefaultCacheManager, DownloadProgress, FileInfo; show DefaultCacheManager, DownloadProgress, FileInfo;
@ -22,6 +23,8 @@ class VideoCard extends StatefulWidget {
this.showCreditsBadge = true, this.showCreditsBadge = true,
this.showBottomGenerateButton = true, this.showBottomGenerateButton = true,
required this.isActive, required this.isActive,
/// 通常绑定首页全局 nonce;递增时若 [isActive] 则恢复解码/播放(前后台、路由返回等与可见性刷新统一)
this.playbackResumeListenable,
required this.onPlayRequested, required this.onPlayRequested,
required this.onStopRequested, required this.onStopRequested,
this.onGenerateSimilar, this.onGenerateSimilar,
@ -35,6 +38,7 @@ class VideoCard extends StatefulWidget {
final bool showCreditsBadge; final bool showCreditsBadge;
final bool showBottomGenerateButton; final bool showBottomGenerateButton;
final bool isActive; final bool isActive;
final ValueListenable<int>? playbackResumeListenable;
final VoidCallback onPlayRequested; final VoidCallback onPlayRequested;
final VoidCallback onStopRequested; final VoidCallback onStopRequested;
final VoidCallback? onGenerateSimilar; final VoidCallback? onGenerateSimilar;
@ -57,6 +61,7 @@ class _VideoCardState extends State<VideoCard> {
@override @override
void initState() { void initState() {
super.initState(); super.initState();
widget.playbackResumeListenable?.addListener(_onPlaybackResumeSignal);
if (widget.isActive) { if (widget.isActive) {
WidgetsBinding.instance.addPostFrameCallback((_) => _loadAndPlay()); WidgetsBinding.instance.addPostFrameCallback((_) => _loadAndPlay());
} }
@ -64,13 +69,43 @@ class _VideoCardState extends State<VideoCard> {
@override @override
void dispose() { void dispose() {
widget.playbackResumeListenable?.removeListener(_onPlaybackResumeSignal);
_disposeController(); _disposeController();
super.dispose(); super.dispose();
} }
void _onPlaybackResumeSignal() {
if (!mounted || !widget.isActive) return;
// [homePlaybackResumeNonce] 可能在其他路由 dispose 等「树仍锁定」时同步递增;
// 监听器里若立刻 [_kickPlaybackAfterGlobalResume] → setState 会抛错。延后到当前同步栈结束。
Future.microtask(() {
if (!mounted || !widget.isActive) return;
_kickPlaybackAfterGlobalResume();
});
}
/// 与 Visibility 刷新并列:系统从后台恢复时往往已 pause 解码器,需显式 [play],不能仅依赖 [didUpdateWidget]。
void _kickPlaybackAfterGlobalResume() {
if (widget.videoUrl == null || widget.videoUrl!.isEmpty) return;
final c = _controller;
if (c != null && c.value.isInitialized) {
c.setVolume(0);
c.setLooping(true);
if (!c.value.isPlaying) {
unawaited(c.play());
}
return;
}
_loadAndPlay();
}
@override @override
void didUpdateWidget(covariant VideoCard oldWidget) { void didUpdateWidget(covariant VideoCard oldWidget) {
super.didUpdateWidget(oldWidget); super.didUpdateWidget(oldWidget);
if (oldWidget.playbackResumeListenable != widget.playbackResumeListenable) {
oldWidget.playbackResumeListenable?.removeListener(_onPlaybackResumeSignal);
widget.playbackResumeListenable?.addListener(_onPlaybackResumeSignal);
}
if (oldWidget.isActive && !widget.isActive) { if (oldWidget.isActive && !widget.isActive) {
_releaseVideoDecoder(); _releaseVideoDecoder();
} else if (!oldWidget.isActive && widget.isActive) { } else if (!oldWidget.isActive && widget.isActive) {

View File

@ -16,9 +16,7 @@ import 'features/recharge/google_play_purchase_service.dart';
void main() async { void main() async {
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
_initAdjust(); _initAdjust();
// Facebook:安装/启动由 App 内生命周期手动 activateApp(见 app.dart),关闭 AutoLog 后依赖此项 // 勿在此 await ReferrerService.init():会阻塞首帧(Adjust 最长可达十余秒)。[AuthService.init] 内已 await getReferrer()
// 等待 Adjust 归因(ReferrerService 会调用 Adjust.getAttributionWithTimeout)
await ReferrerService.init();
SystemChrome.setSystemUIOverlayStyle( SystemChrome.setSystemUIOverlayStyle(
const SystemUiOverlayStyle( const SystemUiOverlayStyle(
statusBarColor: AppColors.surface, statusBarColor: AppColors.surface,

View File

@ -53,14 +53,20 @@ flutter_launcher_icons:
image_path: "assets/images/logo.png" image_path: "assets/images/logo.png"
remove_alpha_ios: true remove_alpha_ios: true
# 启动屏:与 Android 12+ 统一深色底,避免系统用默认 launcher 图标导致「中间图标过小」;
# android_12 必须单独配 image,否则 12+ 只用 ic_launcher,圆内显得很小且易有白边感。
flutter_native_splash: flutter_native_splash:
background_image: "assets/images/splash.png" color: "#0a0a12"
image: assets/images/logo.png
android: true android: true
ios: true ios: true
# Android 12+ 使用不同 API,不支持全屏背景图,需单独配置 android_gravity: fill|clip_vertical
ios_content_mode: scaleAspectFill
fullscreen: true
android_12: android_12:
color: "#0a0a12" color: "#0a0a12"
image: "assets/images/logo.png" image: assets/images/logo.png
icon_background_color: "#0a0a12"
flutter: flutter:
uses-material-design: true uses-material-design: true