feat(ui): 7-day daily-reward popup on home
Presentational DailyRewardSheet (7 cells, today highlighted, reward icons from the calendar table, claim + watch-ad-2x buttons). HomeScreen becomes a ConsumerStatefulWidget that shows it once per mount via a post-frame guard; the 2x path grants the doubled reward only if the rewarded ad was earned, else the base reward. Guards the throwing saveRepositoryProvider default so a repo-less mount is a no-op. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ import '../../game/models/stage.dart';
|
||||
import '../../l10n/gen/app_localizations.dart';
|
||||
import '../../state/providers.dart';
|
||||
import '../widgets/banner_ad_slot.dart';
|
||||
import '../widgets/daily_reward_sheet.dart';
|
||||
import '../widgets/fade_route.dart';
|
||||
import '../widgets/pressable_scale.dart';
|
||||
import '../widgets/season_background.dart';
|
||||
@@ -13,9 +14,14 @@ import 'game_screen.dart';
|
||||
import 'season_map_screen.dart';
|
||||
import 'settings_screen.dart';
|
||||
|
||||
class HomeScreen extends ConsumerWidget {
|
||||
class HomeScreen extends ConsumerStatefulWidget {
|
||||
const HomeScreen({super.key});
|
||||
|
||||
@override
|
||||
ConsumerState<HomeScreen> createState() => _HomeScreenState();
|
||||
}
|
||||
|
||||
class _HomeScreenState extends ConsumerState<HomeScreen> {
|
||||
static const _logoColors = [
|
||||
Color(0xFFFF7EB3),
|
||||
Color(0xFFFFD166),
|
||||
@@ -23,8 +29,52 @@ class HomeScreen extends ConsumerWidget {
|
||||
Color(0xFF7EDB9C),
|
||||
];
|
||||
|
||||
bool _dailyChecked = false;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context, WidgetRef ref) {
|
||||
void initState() {
|
||||
super.initState();
|
||||
WidgetsBinding.instance.addPostFrameCallback((_) => _maybeShowDailyReward());
|
||||
}
|
||||
|
||||
/// Once per home mount, surface the 7-day login reward if it is claimable
|
||||
/// today. Guards the deliberately-throwing [saveRepositoryProvider] default
|
||||
/// so a repo-less mount (e.g. a widget test) is a no-op rather than a crash.
|
||||
void _maybeShowDailyReward() {
|
||||
if (_dailyChecked || !mounted) return;
|
||||
_dailyChecked = true;
|
||||
try {
|
||||
ref.read(saveRepositoryProvider);
|
||||
} catch (_) {
|
||||
return;
|
||||
}
|
||||
final daily = ref.read(dailyRewardProvider);
|
||||
if (!daily.claimable) return;
|
||||
|
||||
showDialog<void>(
|
||||
context: context,
|
||||
builder: (dialogContext) => Dialog(
|
||||
child: DailyRewardSheet(
|
||||
day: daily.day,
|
||||
onClaim: (doubled) async {
|
||||
final notifier = ref.read(dailyRewardProvider.notifier);
|
||||
if (doubled) {
|
||||
// Watch an ad to double; if no ad was earned the base reward is
|
||||
// still granted so the player is never left empty-handed.
|
||||
final earned = await ref.read(adServiceProvider).showRewarded();
|
||||
await notifier.claim(doubled: earned);
|
||||
} else {
|
||||
await notifier.claim();
|
||||
}
|
||||
if (dialogContext.mounted) Navigator.of(dialogContext).pop();
|
||||
},
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context)!;
|
||||
final streak = ref.watch(streakProvider);
|
||||
final best = ref.watch(endlessBestProvider);
|
||||
|
||||
Reference in New Issue
Block a user