This commit is contained in:
lz_db
2026-10-25 20:30:33 +08:00
parent 1d9cd1f9c7
commit 074229f417
14 changed files with 222 additions and 19 deletions
+24 -7
View File
@@ -8,6 +8,7 @@ import '../../core/log/action_logger.dart';
import '../../core/storage/feed_progress_store.dart';
import '../../core/theme/app_theme.dart';
import '../../models/video_item.dart';
import '../../providers/auth_provider.dart';
import '../../providers/feed_provider.dart';
import '../../router.dart';
import 'widgets/comment_sheet.dart';
@@ -85,6 +86,8 @@ class _FeedScreenState extends ConsumerState<FeedScreen>
void didPopNext() {
if (!mounted) return;
if (!ref.read(feedTabVisibleProvider)) return;
// 游客额度已用尽:保持锁定,不恢复播放
if (ref.read(guestLockedProvider)) return;
ActionLogger.I.log('feed_did_pop_next', page: '/home', detail: {
'strategy': _strategy.name,
});
@@ -124,8 +127,8 @@ class _FeedScreenState extends ConsumerState<FeedScreen>
notifier.pool.pauseCurrent();
} else if (state == AppLifecycleState.resumed) {
_appActive = true;
// 仅在首页 Tab 可见时恢复(在"我的"等页面回前台不播)
if (ref.read(feedTabVisibleProvider)) {
// 仅在首页 Tab 可见时恢复(在"我的"等页面回前台不播);游客锁定时不恢复
if (ref.read(feedTabVisibleProvider) && !ref.read(guestLockedProvider)) {
unawaited(() async {
await notifier.pool.resume();
await notifier.rebindCurrent();
@@ -183,11 +186,13 @@ class _FeedScreenState extends ConsumerState<FeedScreen>
}
// jumpToPage 后 resume + rebindCurrent:suspend 已 stop 槽位,仅 play
// 播不起来,必须 rebind 重新 open(有断点记录则 seek 续播)
unawaited(() async {
final n = ref.read(feedProvider(_strategy).notifier);
await n.pool.resume();
await n.rebindCurrent();
}());
if (!ref.read(guestLockedProvider)) {
unawaited(() async {
final n = ref.read(feedProvider(_strategy).notifier);
await n.pool.resume();
await n.rebindCurrent();
}());
}
});
}
@@ -208,6 +213,15 @@ class _FeedScreenState extends ConsumerState<FeedScreen>
Widget build(BuildContext context) {
final state = ref.watch(feedProvider(_strategy));
final notifier = ref.read(feedProvider(_strategy).notifier);
final guestLocked = ref.watch(guestLockedProvider);
// 登录成功后解除游客锁并恢复当前视频播放(登录态下改为积分/会员计费)
ref.listen<AuthState>(authProvider, (previous, next) {
if (next.isLoggedIn && ref.read(guestLockedProvider)) {
ref.read(guestLockedProvider.notifier).state = false;
unawaited(ref.read(feedProvider(_strategy).notifier).rebindCurrent());
}
});
// 冷启动续播:列表加载完成后把 PageView 定位到恢复的下标(每个策略一次)。
// Notifier 会在 refresh 时按上次 videoId 恢复 currentIndex,此处只负责跳页,
@@ -304,6 +318,9 @@ class _FeedScreenState extends ConsumerState<FeedScreen>
bottom: 0,
child: FreeQuotaBanner(remaining: state.freeRemainingToday),
),
// 游客免费额度已用尽:全屏锁(必须登录,点「再逛逛」也无法继续观看)
if (guestLocked) const Positioned.fill(child: GuestLockOverlay()),
],
),
);
@@ -59,6 +59,50 @@ void showGuestLimitDialog(BuildContext context) {
);
}
/// 游客免费额度用完后的全屏锁:盖住播放区并吞掉手势,
/// 用户点「再逛逛」关闭弹窗后依然无法继续观看(必须登录)
class GuestLockOverlay extends StatelessWidget {
const GuestLockOverlay({super.key});
@override
Widget build(BuildContext context) {
return Container(
color: Colors.black.withValues(alpha: 0.92),
child: Center(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 32),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
const Icon(Icons.lock_outline, size: 48, color: AppColors.gold),
const SizedBox(height: 16),
const Text(
'今日免费次数已用完',
style: TextStyle(
fontSize: 18,
fontWeight: FontWeight.w700,
color: Colors.white),
),
const SizedBox(height: 8),
const Text(
'注册或登录后继续观看,登录即可解锁全部视频',
textAlign: TextAlign.center,
style: TextStyle(
fontSize: 13, color: AppColors.textSecondary, height: 1.6),
),
const SizedBox(height: 24),
ElevatedButton(
onPressed: () => context.go('/login'),
child: const Text('去登录/注册'),
),
],
),
),
),
);
}
}
/// 积分不足:引导充值 / 开通会员(2001)
void showPointsInsufficientDialog(BuildContext context, int points) {
showDialog<void>(