// ad_reward_dialog.dart — 免费额度耗尽后的「加时」入口(占位广告流程 + 桌面升级提示) // // 移动端:弹占位广告(「广告播放中…」→ 3s 假播放 → 调 /v1/ads/unlock 加时 → 显示奖励)。 // 桌面端(Windows/macOS):免费版硬 10 分钟/天不可延,无广告——弹「去移动端看广告或升级」。 // 接真广告 SDK(AdMob 激励视频)时,只需把 onWatch 换成「真播完再回调」即可,UI 不变。 import 'dart:async'; import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import '../l10n/app_text.dart'; import '../pangolin_theme.dart'; import '../state/app_providers.dart'; import '../state/quota_provider.dart'; import 'pangolin_icons.dart'; /// 展示加时流程。isDesktop=true 走升级提示(无广告),否则走占位广告加时。 Future showQuotaAdFlow(BuildContext context, WidgetRef ref, {required bool isDesktop}) async { final t = ref.read(appTextProvider); if (isDesktop) { await showDialog(context: context, builder: (_) => _DesktopUpgradeDialog(t: t)); return; } await showDialog( context: context, barrierDismissible: false, builder: (_) => _PlaceholderAdDialog( t: t, onWatch: () => ref.read(quotaProvider.notifier).watchAd(), ), ); } /// 桌面版:免费硬 10 分钟不可延,提示去移动端加时或升级会员。 class _DesktopUpgradeDialog extends StatelessWidget { const _DesktopUpgradeDialog({required this.t}); final AppText t; @override Widget build(BuildContext context) { final c = context.pangolin; return AlertDialog( backgroundColor: c.surface, title: Text(t.quotaDesktopTitle, style: PangolinText.body.copyWith(color: c.fg1, fontWeight: FontWeight.w700)), content: Text(t.quotaDesktopBody, style: PangolinText.sm.copyWith(color: c.fg2)), actions: [ TextButton( onPressed: () => Navigator.of(context).pop(), child: Text(t.gotIt, style: PangolinText.sm.copyWith(color: c.accent, fontWeight: FontWeight.w700)), ), ], ); } } enum _AdPhase { playing, done, failed } /// 移动端占位激励广告:播放中 → 加时成功/失败。成功后短暂展示奖励再自动关闭。 class _PlaceholderAdDialog extends StatefulWidget { const _PlaceholderAdDialog({required this.t, required this.onWatch}); final AppText t; final Future Function() onWatch; @override State<_PlaceholderAdDialog> createState() => _PlaceholderAdDialogState(); } class _PlaceholderAdDialogState extends State<_PlaceholderAdDialog> { _AdPhase _phase = _AdPhase.playing; int _granted = 0; Timer? _closeTimer; @override void initState() { super.initState(); _run(); } Future _run() async { // 占位「播放」3s(接真 SDK 后由激励视频完成回调替代)。 await Future.delayed(const Duration(seconds: 3)); if (!mounted) return; final granted = await widget.onWatch(); if (!mounted) return; setState(() { if (granted != null && granted > 0) { _phase = _AdPhase.done; _granted = granted; } else { _phase = _AdPhase.failed; } }); if (_phase == _AdPhase.done) { _closeTimer = Timer(const Duration(milliseconds: 1300), () { if (mounted) Navigator.of(context).pop(); }); } } @override void dispose() { _closeTimer?.cancel(); super.dispose(); } @override Widget build(BuildContext context) { final c = context.pangolin; final t = widget.t; Widget body; switch (_phase) { case _AdPhase.playing: body = Column(mainAxisSize: MainAxisSize.min, children: [ SizedBox( width: 34, height: 34, child: CircularProgressIndicator(color: c.accent, strokeWidth: 3), ), const SizedBox(height: 16), Text(t.adPlaying, style: PangolinText.sm.copyWith(color: c.fg2, fontWeight: FontWeight.w600)), ]); case _AdPhase.done: body = Column(mainAxisSize: MainAxisSize.min, children: [ Icon(PangolinIcons.checkCircle, size: 40, color: c.success), const SizedBox(height: 14), Text(t.adRewarded(_granted), style: PangolinText.body.copyWith(color: c.success, fontWeight: FontWeight.w700)), ]); case _AdPhase.failed: body = Column(mainAxisSize: MainAxisSize.min, children: [ Icon(PangolinIcons.alertTriangle, size: 36, color: c.danger), const SizedBox(height: 14), Text(t.adFailed, style: PangolinText.sm.copyWith(color: c.fg2, fontWeight: FontWeight.w600)), const SizedBox(height: 14), TextButton( onPressed: () => Navigator.of(context).pop(), child: Text(t.gotIt, style: PangolinText.sm.copyWith(color: c.accent, fontWeight: FontWeight.w700)), ), ]); } return Dialog( backgroundColor: c.surface, shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(PangolinRadius.lg)), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 28, vertical: 28), child: AnimatedSize( duration: PangolinMotion.base, child: body, ), ), ); } }