Files
BlockSeasons/lib/ui/widgets/daily_reward_sheet.dart
T
airkjw 1a028b9852 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>
2026-06-18 19:26:43 +09:00

148 lines
4.2 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// lib/ui/widgets/daily_reward_sheet.dart
import 'package:flutter/material.dart';
import '../../game/daily/daily_reward.dart';
import '../../game/models/booster.dart';
import '../../l10n/gen/app_localizations.dart';
/// Presentational 7-day login calendar. [day] is today's calendar position
/// (1..7); past days read as claimed, the future stays locked. [onClaim] fires
/// with `false` for a plain claim and `true` for the watch-an-ad "2×" claim.
class DailyRewardSheet extends StatelessWidget {
const DailyRewardSheet({super.key, required this.day, required this.onClaim});
final int day;
final void Function(bool doubled) onClaim;
static const _cal = DailyRewardCalendar();
static const _icons = {
BoosterType.hammer: Icons.gavel,
BoosterType.shuffle: Icons.shuffle,
BoosterType.lineBomb: Icons.clear_all,
};
@override
Widget build(BuildContext context) {
final l10n = AppLocalizations.of(context)!;
return Padding(
padding: const EdgeInsets.all(20),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
l10n.dailyRewardTitle,
style: Theme.of(context).textTheme.titleLarge,
),
const SizedBox(height: 16),
Wrap(
spacing: 8,
runSpacing: 8,
alignment: WrapAlignment.center,
children: [
for (var d = 1; d <= DailyRewardCalendar.cycle; d++)
_DayCell(
key: ValueKey('daily_day_$d'),
day: d,
reward: _cal.rewardFor(d),
past: d < day,
today: d == day,
),
],
),
const SizedBox(height: 20),
FilledButton(
key: const ValueKey('daily_claim'),
onPressed: () => onClaim(false),
child: Text(l10n.dailyClaim),
),
const SizedBox(height: 4),
TextButton(
key: const ValueKey('daily_double'),
onPressed: () => onClaim(true),
child: Text(l10n.dailyDoubleWithAd),
),
],
),
);
}
}
class _DayCell extends StatelessWidget {
const _DayCell({
super.key,
required this.day,
required this.reward,
required this.past,
required this.today,
});
final int day;
final Map<BoosterType, int> reward;
final bool past;
final bool today;
@override
Widget build(BuildContext context) {
final scheme = Theme.of(context).colorScheme;
return Opacity(
opacity: past ? 0.45 : 1,
child: Container(
width: 66,
padding: const EdgeInsets.symmetric(vertical: 8, horizontal: 4),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
color: today
? scheme.primary.withValues(alpha: 0.25)
: Colors.white.withValues(alpha: 0.06),
border: today
? Border.all(color: scheme.primary, width: 2)
: null,
),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('$day', style: Theme.of(context).textTheme.labelMedium),
const SizedBox(height: 4),
Wrap(
spacing: 2,
runSpacing: 2,
alignment: WrapAlignment.center,
children: [
for (final entry in reward.entries)
_RewardIcon(
icon: DailyRewardSheet._icons[entry.key]!,
count: entry.value,
),
],
),
if (past)
const Padding(
padding: EdgeInsets.only(top: 2),
child: Icon(Icons.check_circle,
size: 14, color: Colors.greenAccent),
),
],
),
),
);
}
}
class _RewardIcon extends StatelessWidget {
const _RewardIcon({required this.icon, required this.count});
final IconData icon;
final int count;
@override
Widget build(BuildContext context) {
return Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, size: 14),
Text('$count', style: const TextStyle(fontSize: 11)),
],
);
}
}