Compare commits
No commits in common. "b05d3c0b0e7a1b7747c355a2a93c5b51b7348eb9" and "fb4d1b78d6ce5369b16ca4fe9eb9116def22d1e5" have entirely different histories.
b05d3c0b0e
...
fb4d1b78d6
|
Before Width: | Height: | Size: 94 KiB |
|
Before Width: | Height: | Size: 44 KiB |
|
Before Width: | Height: | Size: 69 B After Width: | Height: | Size: 247 KiB |
@ -3,7 +3,4 @@
|
|||||||
<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>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 160 KiB |
|
Before Width: | Height: | Size: 346 KiB |
|
Before Width: | Height: | Size: 601 KiB |
|
Before Width: | Height: | Size: 69 B After Width: | Height: | Size: 247 KiB |
@ -3,7 +3,4 @@
|
|||||||
<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>
|
||||||
|
|||||||
@ -4,12 +4,11 @@
|
|||||||
<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">true</item>
|
<item name="android:windowFullscreen">false</item>
|
||||||
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
|
<item name="android:windowDrawsSystemBarBackgrounds">false</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
|
||||||
|
|||||||
@ -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">true</item>
|
<item name="android:windowFullscreen">false</item>
|
||||||
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
|
<item name="android:windowDrawsSystemBarBackgrounds">false</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">
|
||||||
|
|||||||
@ -4,12 +4,11 @@
|
|||||||
<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">true</item>
|
<item name="android:windowFullscreen">false</item>
|
||||||
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
|
<item name="android:windowDrawsSystemBarBackgrounds">false</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
|
||||||
|
|||||||
@ -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">true</item>
|
<item name="android:windowFullscreen">false</item>
|
||||||
<item name="android:windowDrawsSystemBarBackgrounds">true</item>
|
<item name="android:windowDrawsSystemBarBackgrounds">false</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">
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 69 B After Width: | Height: | Size: 247 KiB |
|
Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 69 B |
|
Before Width: | Height: | Size: 160 KiB After Width: | Height: | Size: 69 B |
|
Before Width: | Height: | Size: 346 KiB After Width: | Height: | Size: 69 B |
@ -16,19 +16,14 @@
|
|||||||
<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="scaleToFill" image="LaunchBackground" translatesAutoresizingMaskIntoConstraints="NO" id="tWc-Dq-wcI"/>
|
<imageView clipsSubviews="YES" userInteractionEnabled="NO" contentMode="scaleAspectFill" 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.039215686274509803" green="0.039215686274509803" blue="0.07058823529411765" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
|
<color key="backgroundColor" red="0.04" green="0.04" blue="0.07" 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>
|
||||||
@ -38,7 +33,6 @@
|
|||||||
</scene>
|
</scene>
|
||||||
</scenes>
|
</scenes>
|
||||||
<resources>
|
<resources>
|
||||||
<image name="LaunchImage" width="512" height="512"/>
|
<image name="LaunchBackground" width="375" height="812"/>
|
||||||
<image name="LaunchBackground" width="1" height="1"/>
|
|
||||||
</resources>
|
</resources>
|
||||||
</document>
|
</document>
|
||||||
|
|||||||
@ -51,8 +51,6 @@
|
|||||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||||
</array>
|
</array>
|
||||||
<key>UIStatusBarHidden</key>
|
<key>UIStatusBarHidden</key>
|
||||||
<true/>
|
|
||||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
|
||||||
<false/>
|
<false/>
|
||||||
</dict>
|
</dict>
|
||||||
</plist>
|
</plist>
|
||||||
|
|||||||
@ -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(milliseconds: 400));
|
await Future<void>.delayed(const Duration(seconds: 2));
|
||||||
|
|
||||||
final deviceId = await _getDeviceId();
|
final deviceId = await _getDeviceId();
|
||||||
_logMsg('init: deviceId=$deviceId');
|
_logMsg('init: deviceId=$deviceId');
|
||||||
|
|||||||
@ -18,8 +18,7 @@ class ReferrerService {
|
|||||||
static final Completer<AdjustAttribution?> _attributionCallbackCompleter =
|
static final Completer<AdjustAttribution?> _attributionCallbackCompleter =
|
||||||
Completer<AdjustAttribution?>();
|
Completer<AdjustAttribution?>();
|
||||||
|
|
||||||
/// 过长会拖慢 [AuthService.init] 内首次 getReferrer;过短可能拿不到 Adjust,仍有 Play Install Referrer fallback
|
static const int _adjustTimeoutMs = 15000;
|
||||||
static const int _adjustTimeoutMs = 4500;
|
|
||||||
|
|
||||||
/// 由 Adjust attributionCallback 调用,首次安装时归因往往通过此回调返回(晚于 getAttributionWithTimeout)
|
/// 由 Adjust attributionCallback 调用,首次安装时归因往往通过此回调返回(晚于 getAttributionWithTimeout)
|
||||||
static void receiveAttributionFromCallback(AdjustAttribution attribution) {
|
static void receiveAttributionFromCallback(AdjustAttribution attribution) {
|
||||||
|
|||||||
@ -1,7 +1,4 @@
|
|||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
/// 统一信号:递增后首页多帧刷新 [VisibilityDetector],视频卡片尝试恢复 [VideoPlayer]。
|
/// 子路由从 Navigator pop 回主导航后,通知首页刷新 [VisibilityDetector](见 [_MainScaffoldState.didPopNext])
|
||||||
///
|
|
||||||
/// 递增场景包括:子路由 pop 回首页、底部导航切回首页、前后台切换回前台(首页 Tab)、
|
|
||||||
/// 列表/分类与 ext 配置就绪后需重算可见卡片等(见 `HomeScreen._requestPlaybackPipeline`)。
|
|
||||||
final ValueNotifier<int> homePlaybackResumeNonce = ValueNotifier<int>(0);
|
final ValueNotifier<int> homePlaybackResumeNonce = ValueNotifier<int>(0);
|
||||||
|
|||||||
@ -27,7 +27,7 @@ class HomeScreen extends StatefulWidget {
|
|||||||
State<HomeScreen> createState() => _HomeScreenState();
|
State<HomeScreen> createState() => _HomeScreenState();
|
||||||
}
|
}
|
||||||
|
|
||||||
class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
class _HomeScreenState extends State<HomeScreen> {
|
||||||
List<CategoryItem> _categories = [];
|
List<CategoryItem> _categories = [];
|
||||||
CategoryItem? _selectedCategory;
|
CategoryItem? _selectedCategory;
|
||||||
List<TaskItem> _tasks = [];
|
List<TaskItem> _tasks = [];
|
||||||
@ -44,13 +44,8 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
|||||||
/// 是否曾有过「可见比例 ≥ 阈值」的探测器回调(用于区分冷启动无回调 vs 已全部滑出视口)
|
/// 是否曾有过「可见比例 ≥ 阈值」的探测器回调(用于区分冷启动无回调 vs 已全部滑出视口)
|
||||||
bool _visibilityHadMeaningfulReport = false;
|
bool _visibilityHadMeaningfulReport = false;
|
||||||
|
|
||||||
/// 统一入口:递增 nonce → [VideoCard] 恢复播放 + [_onHomePlaybackResumeSignal] 内多帧刷新可见性。
|
/// IndexedStack 切回首页或 [homePlaybackResumeNonce] 递增后,让 [VisibilityDetector] 重新计算。
|
||||||
void _requestPlaybackPipeline() {
|
/// 单次 [notifyNow] 在「Modal 收起 / 子路由 pop」后首帧常过早,与 My Gallery 一致采用多帧 + 短延迟。
|
||||||
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() {
|
||||||
@ -85,7 +80,6 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
|||||||
@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);
|
||||||
@ -112,7 +106,6 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
|||||||
|
|
||||||
@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);
|
||||||
@ -127,19 +120,11 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
|||||||
_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) _requestPlaybackPipeline();
|
if (mounted && widget.isActive) _scheduleVisibilityRefresh();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,7 +139,7 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
|||||||
static const int _maxConcurrentHomeVideos = 16;
|
static const int _maxConcurrentHomeVideos = 16;
|
||||||
|
|
||||||
void _onGridCardVisibilityChanged(int index, VisibilityInfo info) {
|
void _onGridCardVisibilityChanged(int index, VisibilityInfo info) {
|
||||||
if (!mounted || !widget.isActive) return;
|
if (!mounted) return;
|
||||||
if (info.visibleFraction >= _videoVisibilityThreshold) {
|
if (info.visibleFraction >= _videoVisibilityThreshold) {
|
||||||
_cardVisibleFraction[index] = info.visibleFraction;
|
_cardVisibleFraction[index] = info.visibleFraction;
|
||||||
_visibilityHadMeaningfulReport = true;
|
_visibilityHadMeaningfulReport = true;
|
||||||
@ -170,7 +155,6 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
|||||||
}
|
}
|
||||||
|
|
||||||
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) {
|
||||||
@ -216,7 +200,7 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
|||||||
super.didUpdateWidget(oldWidget);
|
super.didUpdateWidget(oldWidget);
|
||||||
if (widget.isActive && !oldWidget.isActive) {
|
if (widget.isActive && !oldWidget.isActive) {
|
||||||
refreshAccount();
|
refreshAccount();
|
||||||
_requestPlaybackPipeline();
|
_scheduleVisibilityRefresh();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -316,7 +300,7 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
|||||||
setState(() => _categoriesLoading = false);
|
setState(() => _categoriesLoading = false);
|
||||||
// 默认选中 pets 等不会走 [_loadTasks] 的路径,也要触发一次可见性(否则预览不自动播)
|
// 默认选中 pets 等不会走 [_loadTasks] 的路径,也要触发一次可见性(否则预览不自动播)
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
if (mounted && widget.isActive) _requestPlaybackPipeline();
|
if (mounted && widget.isActive) _scheduleVisibilityRefresh();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -348,7 +332,7 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
|||||||
setState(() => _tasksLoading = false);
|
setState(() => _tasksLoading = false);
|
||||||
// 列表替换后须强制可见性重算,否则探测器有时不回调,预览一直不播
|
// 列表替换后须强制可见性重算,否则探测器有时不回调,预览一直不播
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
if (mounted && widget.isActive) _requestPlaybackPipeline();
|
if (mounted && widget.isActive) _scheduleVisibilityRefresh();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -368,7 +352,7 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
|||||||
_visibilityHadMeaningfulReport = false;
|
_visibilityHadMeaningfulReport = false;
|
||||||
});
|
});
|
||||||
WidgetsBinding.instance.addPostFrameCallback((_) {
|
WidgetsBinding.instance.addPostFrameCallback((_) {
|
||||||
if (mounted && widget.isActive) _requestPlaybackPipeline();
|
if (mounted && widget.isActive) _scheduleVisibilityRefresh();
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
_loadTasks(c.id);
|
_loadTasks(c.id);
|
||||||
@ -463,8 +447,6 @@ class _HomeScreenState extends State<HomeScreen> with WidgetsBindingObserver {
|
|||||||
_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
|
||||||
|
|||||||
@ -1,7 +1,6 @@
|
|||||||
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;
|
||||||
@ -23,8 +22,6 @@ 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,
|
||||||
@ -38,7 +35,6 @@ 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;
|
||||||
@ -61,7 +57,6 @@ 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());
|
||||||
}
|
}
|
||||||
@ -69,43 +64,13 @@ 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) {
|
||||||
|
|||||||
@ -16,7 +16,9 @@ import 'features/recharge/google_play_purchase_service.dart';
|
|||||||
void main() async {
|
void main() async {
|
||||||
WidgetsFlutterBinding.ensureInitialized();
|
WidgetsFlutterBinding.ensureInitialized();
|
||||||
_initAdjust();
|
_initAdjust();
|
||||||
// 勿在此 await ReferrerService.init():会阻塞首帧(Adjust 最长可达十余秒)。[AuthService.init] 内已 await getReferrer()
|
// Facebook:安装/启动由 App 内生命周期手动 activateApp(见 app.dart),关闭 AutoLog 后依赖此项
|
||||||
|
// 等待 Adjust 归因(ReferrerService 会调用 Adjust.getAttributionWithTimeout)
|
||||||
|
await ReferrerService.init();
|
||||||
SystemChrome.setSystemUIOverlayStyle(
|
SystemChrome.setSystemUIOverlayStyle(
|
||||||
const SystemUiOverlayStyle(
|
const SystemUiOverlayStyle(
|
||||||
statusBarColor: AppColors.surface,
|
statusBarColor: AppColors.surface,
|
||||||
|
|||||||
12
pubspec.yaml
@ -53,20 +53,14 @@ 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:
|
||||||
color: "#0a0a12"
|
background_image: "assets/images/splash.png"
|
||||||
image: assets/images/logo.png
|
|
||||||
android: true
|
android: true
|
||||||
ios: true
|
ios: true
|
||||||
android_gravity: fill|clip_vertical
|
# Android 12+ 使用不同 API,不支持全屏背景图,需单独配置
|
||||||
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
|
||||||
|
|||||||