Compare commits

...

2 Commits

Author SHA1 Message Date
ivan
b05d3c0b0e 优化:启动优化 2026-03-30 01:40:08 +08:00
ivan
f06c49f22a 优化:启动优化 2026-03-30 01:39:47 +08:00
26 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