fix: hide spent rescue to prevent StateError crash; log per-attempt starts

Expose engine.rescueUsed getter and surface it through GameViewState so
the result overlay can omit the watch-ad FilledButton after a rescue has
been consumed, preventing a second tap from calling useContinue /
addExtraMoves and hitting their StateError guard. Give-up is promoted to
FilledButton when rescue is unavailable for clear affordance.

Also emit stageStart / endlessStart analytics in restart() so every
attempt (not just the first) is bracketed by a matching start event.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-06-12 13:41:59 +09:00
parent 074a21ea2b
commit 9763968db9
4 changed files with 67 additions and 22 deletions
+36 -22
View File
@@ -391,33 +391,47 @@ class _GameScreenState extends ConsumerState<GameScreen>
(GamePhase.stuck, StuckReason.outOfMoves) => (
l10n.outOfMoves,
[
FilledButton(
onPressed: () {
ref.read(analyticsProvider).rescueUsed(type: 'extra_moves');
notifier.addExtraMoves();
},
child: Text(l10n.plusFiveMoves),
),
TextButton(
onPressed: notifier.declineAndLose,
child: Text(l10n.giveUp),
),
if (!view.rescueUsed)
FilledButton(
onPressed: () {
ref.read(analyticsProvider).rescueUsed(type: 'extra_moves');
notifier.addExtraMoves();
},
child: Text(l10n.plusFiveMoves),
),
if (view.rescueUsed)
FilledButton(
onPressed: notifier.declineAndLose,
child: Text(l10n.giveUp),
)
else
TextButton(
onPressed: notifier.declineAndLose,
child: Text(l10n.giveUp),
),
],
),
(GamePhase.stuck, _) => (
l10n.boardFull,
[
FilledButton(
onPressed: () {
ref.read(analyticsProvider).rescueUsed(type: 'continue');
notifier.useContinue();
},
child: Text(l10n.watchAdContinue),
),
TextButton(
onPressed: notifier.declineAndLose,
child: Text(l10n.giveUp),
),
if (!view.rescueUsed)
FilledButton(
onPressed: () {
ref.read(analyticsProvider).rescueUsed(type: 'continue');
notifier.useContinue();
},
child: Text(l10n.watchAdContinue),
),
if (view.rescueUsed)
FilledButton(
onPressed: notifier.declineAndLose,
child: Text(l10n.giveUp),
)
else
TextButton(
onPressed: notifier.declineAndLose,
child: Text(l10n.giveUp),
),
],
),
(GamePhase.lost, _) when view.endless => (