Compare commits
2 Commits
fb4d1b78d6
...
b05d3c0b0e
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
b05d3c0b0e | ||
|
|
f06c49f22a |
BIN
android/app/src/main/res/drawable-hdpi/splash.png
Normal file
|
After Width: | Height: | Size: 94 KiB |
BIN
android/app/src/main/res/drawable-mdpi/splash.png
Normal file
|
After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 247 KiB After Width: | Height: | Size: 69 B |
@ -3,4 +3,7 @@
|
||||
<item>
|
||||
<bitmap android:gravity="fill" android:src="@drawable/background"/>
|
||||
</item>
|
||||
<item>
|
||||
<bitmap android:gravity="fill|clip_vertical" android:src="@drawable/splash"/>
|
||||
</item>
|
||||
</layer-list>
|
||||
|
||||
BIN
android/app/src/main/res/drawable-xhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 160 KiB |
BIN
android/app/src/main/res/drawable-xxhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 346 KiB |
BIN
android/app/src/main/res/drawable-xxxhdpi/splash.png
Normal file
|
After Width: | Height: | Size: 601 KiB |
|
Before Width: | Height: | Size: 247 KiB After Width: | Height: | Size: 69 B |
@ -3,4 +3,7 @@
|
||||
<item>
|
||||
<bitmap android:gravity="fill" android:src="@drawable/background"/>
|
||||
</item>
|
||||
<item>
|
||||
<bitmap android:gravity="fill|clip_vertical" android:src="@drawable/splash"/>
|
||||
</item>
|
||||
</layer-list>
|
||||
|
||||
@ -4,11 +4,12 @@
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
<item name="android:forceDarkAllowed">false</item>
|
||||
<item name="android:windowFullscreen">false</item>
|
||||
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
||||
<item name="android:windowFullscreen">true</item>
|
||||
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
|
||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||
<item name="android:windowSplashScreenBackground">#0a0a12</item>
|
||||
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item>
|
||||
<item name="android:windowSplashScreenIconBackgroundColor">#0a0a12</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
|
||||
@ -3,8 +3,8 @@
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
<item name="android:forceDarkAllowed">false</item>
|
||||
<item name="android:windowFullscreen">false</item>
|
||||
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
||||
<item name="android:windowFullscreen">true</item>
|
||||
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
|
||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||
</style>
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
|
||||
@ -4,11 +4,12 @@
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
<item name="android:forceDarkAllowed">false</item>
|
||||
<item name="android:windowFullscreen">false</item>
|
||||
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
||||
<item name="android:windowFullscreen">true</item>
|
||||
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
|
||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||
<item name="android:windowSplashScreenBackground">#0a0a12</item>
|
||||
<item name="android:windowSplashScreenAnimatedIcon">@drawable/android12splash</item>
|
||||
<item name="android:windowSplashScreenIconBackgroundColor">#0a0a12</item>
|
||||
</style>
|
||||
<!-- Theme applied to the Android Window as soon as the process has started.
|
||||
This theme determines the color of the Android Window while your
|
||||
|
||||
@ -3,8 +3,8 @@
|
||||
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
<item name="android:windowBackground">@drawable/launch_background</item>
|
||||
<item name="android:forceDarkAllowed">false</item>
|
||||
<item name="android:windowFullscreen">false</item>
|
||||
<item name="android:windowDrawsSystemBarBackgrounds">false</item>
|
||||
<item name="android:windowFullscreen">true</item>
|
||||
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
|
||||
<item name="android:windowLayoutInDisplayCutoutMode">shortEdges</item>
|
||||
</style>
|
||||
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
|
||||
|
||||
|
Before Width: | Height: | Size: 247 KiB After Width: | Height: | Size: 69 B |
|
Before Width: | Height: | Size: 69 B After Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 69 B After Width: | Height: | Size: 160 KiB |
|
Before Width: | Height: | Size: 69 B After Width: | Height: | Size: 346 KiB |
@ -16,14 +16,19 @@
|
||||
<view key="view" contentMode="scaleToFill" id="Ze5-6b-2t3">
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<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>
|
||||
<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>
|
||||
<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="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="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="YRO-k0-Ey4" firstAttribute="top" secondItem="Ze5-6b-2t3" secondAttribute="top" id="xPn-NY-SIU"/>
|
||||
</constraints>
|
||||
</view>
|
||||
</viewController>
|
||||
@ -33,6 +38,7 @@
|
||||
</scene>
|
||||
</scenes>
|
||||
<resources>
|
||||
<image name="LaunchBackground" width="375" height="812"/>
|
||||
<image name="LaunchImage" width="512" height="512"/>
|
||||
<image name="LaunchBackground" width="1" height="1"/>
|
||||
</resources>
|
||||
</document>
|
||||
|
||||
@ -51,6 +51,8 @@
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIStatusBarHidden</key>
|
||||
<true/>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@ -197,8 +197,8 @@ class AuthService {
|
||||
const retryDelay = Duration(seconds: 2);
|
||||
|
||||
try {
|
||||
// 等待网络就绪(浏览器能访问但 App 报错时,多为启动时网络未初始化)
|
||||
await Future<void>.delayed(const Duration(seconds: 2));
|
||||
// 极短让步:避免个别机型刚起进程时网络栈未就绪;过长会拖慢冷启动
|
||||
await Future<void>.delayed(const Duration(milliseconds: 400));
|
||||
|
||||
final deviceId = await _getDeviceId();
|
||||
_logMsg('init: deviceId=$deviceId');
|
||||
|
||||
@ -18,7 +18,8 @@ class ReferrerService {
|
||||
static final Completer<AdjustAttribution?> _attributionCallbackCompleter =
|
||||
Completer<AdjustAttribution?>();
|
||||
|
||||
static const int _adjustTimeoutMs = 15000;
|
||||
/// 过长会拖慢 [AuthService.init] 内首次 getReferrer;过短可能拿不到 Adjust,仍有 Play Install Referrer fallback
|
||||
static const int _adjustTimeoutMs = 4500;
|
||||
|
||||
/// 由 Adjust attributionCallback 调用,首次安装时归因往往通过此回调返回(晚于 getAttributionWithTimeout)
|
||||
static void receiveAttributionFromCallback(AdjustAttribution attribution) {
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
import 'package:flutter/foundation.dart';
|
||||
|
||||
/// 子路由从 Navigator pop 回主导航后,通知首页刷新 [VisibilityDetector](见 [_MainScaffoldState.didPopNext])
|
||||
/// 统一信号:递增后首页多帧刷新 [VisibilityDetector],视频卡片尝试恢复 [VideoPlayer]。
|
||||
///
|
||||
/// 递增场景包括:子路由 pop 回首页、底部导航切回首页、前后台切换回前台(首页 Tab)、
|
||||
/// 列表/分类与 ext 配置就绪后需重算可见卡片等(见 `HomeScreen._requestPlaybackPipeline`)。
|
||||
final ValueNotifier<int> homePlaybackResumeNonce = ValueNotifier<int>(0);
|
||||
|
||||
@ -27,7 +27,7 @@ class HomeScreen extends StatefulWidget {
|
||||
State<HomeScreen> createState() => _HomeScreenState();
|
||||
}
|
||||
|
||||
class _HomeScreenState extends State<HomeScreen> {
|
||||
class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
||||
List<CategoryItem> _categories = [];
|
||||
CategoryItem? _selectedCategory;
|
||||
List<TaskItem> _tasks = [];
|
||||
@ -44,8 +44,13 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
/// 是否曾有过「可见比例 ≥ 阈值」的探测器回调(用于区分冷启动无回调 vs 已全部滑出视口)
|
||||
bool _visibilityHadMeaningfulReport = false;
|
||||
|
||||
/// IndexedStack 切回首页或 [homePlaybackResumeNonce] 递增后,让 [VisibilityDetector] 重新计算。
|
||||
/// 单次 [notifyNow] 在「Modal 收起 / 子路由 pop」后首帧常过早,与 My Gallery 一致采用多帧 + 短延迟。
|
||||
/// 统一入口:递增 nonce → [VideoCard] 恢复播放 + [_onHomePlaybackResumeSignal] 内多帧刷新可见性。
|
||||
void _requestPlaybackPipeline() {
|
||||
if (!mounted || !widget.isActive) return;
|
||||
homePlaybackResumeNonce.value++;
|
||||
}
|
||||
|
||||
/// [homePlaybackResumeNonce] 监听里调用:多帧 + 短延迟 [notifyNow],避免单次过早。
|
||||
void _scheduleVisibilityRefresh() {
|
||||
if (!mounted || !widget.isActive) return;
|
||||
void notify() {
|
||||
@ -80,6 +85,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addObserver(this);
|
||||
UserState.needShowVideoMenu.addListener(_onExtConfigChanged);
|
||||
UserState.extConfigItems.addListener(_onExtConfigChanged);
|
||||
AuthService.isLoginComplete.addListener(_onExtConfigChanged);
|
||||
@ -106,6 +112,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
WidgetsBinding.instance.removeObserver(this);
|
||||
homePlaybackResumeNonce.removeListener(_onHomePlaybackResumeSignal);
|
||||
UserState.needShowVideoMenu.removeListener(_onExtConfigChanged);
|
||||
UserState.extConfigItems.removeListener(_onExtConfigChanged);
|
||||
@ -120,11 +127,19 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
_scheduleVisibilityRefresh();
|
||||
}
|
||||
|
||||
@override
|
||||
void didChangeAppLifecycleState(AppLifecycleState state) {
|
||||
super.didChangeAppLifecycleState(state);
|
||||
if (state == AppLifecycleState.resumed && mounted && widget.isActive) {
|
||||
_requestPlaybackPipeline();
|
||||
}
|
||||
}
|
||||
|
||||
void _onExtConfigChanged() {
|
||||
if (mounted) setState(() {});
|
||||
// 不用 Timer 防抖:连续 listener 会互相 cancel,首装可能永远不触发 notify
|
||||
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;
|
||||
|
||||
void _onGridCardVisibilityChanged(int index, VisibilityInfo info) {
|
||||
if (!mounted) return;
|
||||
if (!mounted || !widget.isActive) return;
|
||||
if (info.visibleFraction >= _videoVisibilityThreshold) {
|
||||
_cardVisibleFraction[index] = info.visibleFraction;
|
||||
_visibilityHadMeaningfulReport = true;
|
||||
@ -155,6 +170,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
}
|
||||
|
||||
void _reconcileVisibleVideoIndicesFromDetector() {
|
||||
if (!mounted || !widget.isActive) return;
|
||||
final tasks = _displayTasks;
|
||||
final scored = <MapEntry<int, double>>[];
|
||||
for (final e in _cardVisibleFraction.entries) {
|
||||
@ -200,7 +216,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (widget.isActive && !oldWidget.isActive) {
|
||||
refreshAccount();
|
||||
_scheduleVisibilityRefresh();
|
||||
_requestPlaybackPipeline();
|
||||
}
|
||||
}
|
||||
|
||||
@ -300,7 +316,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
setState(() => _categoriesLoading = false);
|
||||
// 默认选中 pets 等不会走 [_loadTasks] 的路径,也要触发一次可见性(否则预览不自动播)
|
||||
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);
|
||||
// 列表替换后须强制可见性重算,否则探测器有时不回调,预览一直不播
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted && widget.isActive) _scheduleVisibilityRefresh();
|
||||
if (mounted && widget.isActive) _requestPlaybackPipeline();
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -352,7 +368,7 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
_visibilityHadMeaningfulReport = false;
|
||||
});
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||
if (mounted && widget.isActive) _scheduleVisibilityRefresh();
|
||||
if (mounted && widget.isActive) _requestPlaybackPipeline();
|
||||
});
|
||||
} else {
|
||||
_loadTasks(c.id);
|
||||
@ -447,6 +463,8 @@ class _HomeScreenState extends State<HomeScreen> {
|
||||
_placeholderImage,
|
||||
videoUrl: task.previewVideoUrl,
|
||||
credits: credits,
|
||||
playbackResumeListenable:
|
||||
homePlaybackResumeNonce,
|
||||
isActive: widget.isActive &&
|
||||
_visibleVideoIndices.contains(index) &&
|
||||
!_userPausedVideoIndices
|
||||
|
||||
@ -1,6 +1,7 @@
|
||||
import 'dart:async' show unawaited;
|
||||
import 'dart:io' show File;
|
||||
|
||||
import 'package:flutter/foundation.dart' show ValueListenable;
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_cache_manager/flutter_cache_manager.dart'
|
||||
show DefaultCacheManager, DownloadProgress, FileInfo;
|
||||
@ -22,6 +23,8 @@ class VideoCard extends StatefulWidget {
|
||||
this.showCreditsBadge = true,
|
||||
this.showBottomGenerateButton = true,
|
||||
required this.isActive,
|
||||
/// 通常绑定首页全局 nonce;递增时若 [isActive] 则恢复解码/播放(前后台、路由返回等与可见性刷新统一)
|
||||
this.playbackResumeListenable,
|
||||
required this.onPlayRequested,
|
||||
required this.onStopRequested,
|
||||
this.onGenerateSimilar,
|
||||
@ -35,6 +38,7 @@ class VideoCard extends StatefulWidget {
|
||||
final bool showCreditsBadge;
|
||||
final bool showBottomGenerateButton;
|
||||
final bool isActive;
|
||||
final ValueListenable<int>? playbackResumeListenable;
|
||||
final VoidCallback onPlayRequested;
|
||||
final VoidCallback onStopRequested;
|
||||
final VoidCallback? onGenerateSimilar;
|
||||
@ -57,6 +61,7 @@ class _VideoCardState extends State<VideoCard> {
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
widget.playbackResumeListenable?.addListener(_onPlaybackResumeSignal);
|
||||
if (widget.isActive) {
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => _loadAndPlay());
|
||||
}
|
||||
@ -64,13 +69,43 @@ class _VideoCardState extends State<VideoCard> {
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
widget.playbackResumeListenable?.removeListener(_onPlaybackResumeSignal);
|
||||
_disposeController();
|
||||
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
|
||||
void didUpdateWidget(covariant VideoCard oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
if (oldWidget.playbackResumeListenable != widget.playbackResumeListenable) {
|
||||
oldWidget.playbackResumeListenable?.removeListener(_onPlaybackResumeSignal);
|
||||
widget.playbackResumeListenable?.addListener(_onPlaybackResumeSignal);
|
||||
}
|
||||
if (oldWidget.isActive && !widget.isActive) {
|
||||
_releaseVideoDecoder();
|
||||
} else if (!oldWidget.isActive && widget.isActive) {
|
||||
|
||||
@ -16,9 +16,7 @@ import 'features/recharge/google_play_purchase_service.dart';
|
||||
void main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
_initAdjust();
|
||||
// Facebook:安装/启动由 App 内生命周期手动 activateApp(见 app.dart),关闭 AutoLog 后依赖此项
|
||||
// 等待 Adjust 归因(ReferrerService 会调用 Adjust.getAttributionWithTimeout)
|
||||
await ReferrerService.init();
|
||||
// 勿在此 await ReferrerService.init():会阻塞首帧(Adjust 最长可达十余秒)。[AuthService.init] 内已 await getReferrer()
|
||||
SystemChrome.setSystemUIOverlayStyle(
|
||||
const SystemUiOverlayStyle(
|
||||
statusBarColor: AppColors.surface,
|
||||
|
||||
12
pubspec.yaml
@ -53,14 +53,20 @@ flutter_launcher_icons:
|
||||
image_path: "assets/images/logo.png"
|
||||
remove_alpha_ios: true
|
||||
|
||||
# 启动屏:与 Android 12+ 统一深色底,避免系统用默认 launcher 图标导致「中间图标过小」;
|
||||
# android_12 必须单独配 image,否则 12+ 只用 ic_launcher,圆内显得很小且易有白边感。
|
||||
flutter_native_splash:
|
||||
background_image: "assets/images/splash.png"
|
||||
color: "#0a0a12"
|
||||
image: assets/images/logo.png
|
||||
android: true
|
||||
ios: true
|
||||
# Android 12+ 使用不同 API,不支持全屏背景图,需单独配置
|
||||
android_gravity: fill|clip_vertical
|
||||
ios_content_mode: scaleAspectFill
|
||||
fullscreen: true
|
||||
android_12:
|
||||
color: "#0a0a12"
|
||||
image: "assets/images/logo.png"
|
||||
image: assets/images/logo.png
|
||||
icon_background_color: "#0a0a12"
|
||||
|
||||
flutter:
|
||||
uses-material-design: true
|
||||
|
||||