Implemented popup & confetti

This commit is contained in:
2025-07-20 22:36:55 +02:00
parent ddc2d68e9b
commit 5099dafbe9
2 changed files with 276 additions and 206 deletions

View File

@@ -8,6 +8,7 @@ import 'package:cabo_counter/presentation/views/mode_selection_view.dart';
import 'package:cabo_counter/presentation/views/points_view.dart'; import 'package:cabo_counter/presentation/views/points_view.dart';
import 'package:cabo_counter/presentation/views/round_view.dart'; import 'package:cabo_counter/presentation/views/round_view.dart';
import 'package:cabo_counter/services/local_storage_service.dart'; import 'package:cabo_counter/services/local_storage_service.dart';
import 'package:confetti/confetti.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
@@ -21,6 +22,9 @@ class ActiveGameView extends StatefulWidget {
} }
class _ActiveGameViewState extends State<ActiveGameView> { class _ActiveGameViewState extends State<ActiveGameView> {
final confettiController = ConfettiController(
duration: const Duration(seconds: 10),
);
late final GameSession gameSession; late final GameSession gameSession;
late List<int> denseRanks; late List<int> denseRanks;
late List<int> sortedPlayerIndices; late List<int> sortedPlayerIndices;
@@ -33,7 +37,9 @@ class _ActiveGameViewState extends State<ActiveGameView> {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return ListenableBuilder( return Stack(
children: [
ListenableBuilder(
listenable: gameSession, listenable: gameSession,
builder: (context, _) { builder: (context, _) {
sortedPlayerIndices = _getSortedPlayerIndices(); sortedPlayerIndices = _getSortedPlayerIndices();
@@ -76,7 +82,8 @@ class _ActiveGameViewState extends State<ActiveGameView> {
trailing: Row( trailing: Row(
children: [ children: [
const SizedBox(width: 5), const SizedBox(width: 5),
Text('${gameSession.playerScores[playerIndex]} ' Text(
'${gameSession.playerScores[playerIndex]} '
'${AppLocalizations.of(context).points}') '${AppLocalizations.of(context).points}')
], ],
), ),
@@ -103,15 +110,15 @@ class _ActiveGameViewState extends State<ActiveGameView> {
title: Text( title: Text(
'${AppLocalizations.of(context).round} ${index + 1}', '${AppLocalizations.of(context).round} ${index + 1}',
), ),
trailing: trailing: index + 1 !=
index + 1 != gameSession.roundNumber || gameSession.roundNumber ||
gameSession.isGameFinished == true gameSession.isGameFinished == true
? (const Text('\u{2705}', ? (const Text('\u{2705}',
style: TextStyle(fontSize: 22))) style: TextStyle(fontSize: 22)))
: const Text('\u{23F3}', : const Text('\u{23F3}',
style: TextStyle(fontSize: 22)), style: TextStyle(fontSize: 22)),
onTap: () async { onTap: () async {
_openRoundView(index + 1); _openRoundView(context, index + 1);
}, },
)); ));
}, },
@@ -127,7 +134,8 @@ class _ActiveGameViewState extends State<ActiveGameView> {
children: [ children: [
CupertinoListTile( CupertinoListTile(
title: Text( title: Text(
AppLocalizations.of(context).scoring_history, AppLocalizations.of(context)
.scoring_history,
), ),
backgroundColorActivated: backgroundColorActivated:
CustomTheme.backgroundColor, CustomTheme.backgroundColor,
@@ -168,7 +176,8 @@ class _ActiveGameViewState extends State<ActiveGameView> {
style: gameSession.roundNumber > 1 && style: gameSession.roundNumber > 1 &&
!gameSession.isGameFinished !gameSession.isGameFinished
? const TextStyle(color: Colors.white) ? const TextStyle(color: Colors.white)
: const TextStyle(color: Colors.white30), : const TextStyle(
color: Colors.white30),
), ),
backgroundColorActivated: backgroundColorActivated:
CustomTheme.backgroundColor, CustomTheme.backgroundColor,
@@ -205,7 +214,8 @@ class _ActiveGameViewState extends State<ActiveGameView> {
context, context,
CupertinoPageRoute( CupertinoPageRoute(
builder: (_) => CreateGameView( builder: (_) => CreateGameView(
gameTitle: gameSession.gameTitle, gameTitle:
gameSession.gameTitle,
gameMode: widget.gameSession gameMode: widget.gameSession
.isPointsLimitEnabled == .isPointsLimitEnabled ==
true true
@@ -228,15 +238,19 @@ class _ActiveGameViewState extends State<ActiveGameView> {
if (!success && context.mounted) { if (!success && context.mounted) {
showCupertinoDialog( showCupertinoDialog(
context: context, context: context,
builder: (context) => CupertinoAlertDialog( builder: (context) =>
title: Text(AppLocalizations.of(context) CupertinoAlertDialog(
title: Text(
AppLocalizations.of(context)
.export_error_title), .export_error_title),
content: Text(AppLocalizations.of(context) content: Text(
AppLocalizations.of(context)
.export_error_message), .export_error_message),
actions: [ actions: [
CupertinoDialogAction( CupertinoDialogAction(
child: Text( child: Text(
AppLocalizations.of(context).ok), AppLocalizations.of(context)
.ok),
onPressed: () => onPressed: () =>
Navigator.pop(context), Navigator.pop(context),
), ),
@@ -245,13 +259,36 @@ class _ActiveGameViewState extends State<ActiveGameView> {
); );
} }
}), }),
CupertinoListTile(
title: const Text('Konfetti'),
onTap: () => confettiController.play(),
)
], ],
) )
], ],
), ),
), ),
)); ));
}); }),
Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Center(
child: ConfettiWidget(
blastDirectionality: BlastDirectionality.explosive,
particleDrag: 0.07,
emissionFrequency: 0.1,
numberOfParticles: 10,
minBlastForce: 5,
maxBlastForce: 20,
confettiController: confettiController,
),
),
],
),
],
);
} }
/// Shows a dialog to confirm ending the game. /// Shows a dialog to confirm ending the game.
@@ -403,7 +440,7 @@ class _ActiveGameViewState extends State<ActiveGameView> {
/// Recursively opens the RoundView for the specified round number. /// Recursively opens the RoundView for the specified round number.
/// It starts with the given [roundNumber] and continues to open the next round /// It starts with the given [roundNumber] and continues to open the next round
/// until the user navigates back or the round number is invalid. /// until the user navigates back or the round number is invalid.
void _openRoundView(int roundNumber) async { void _openRoundView(BuildContext context, int roundNumber) async {
final val = await Navigator.of(context, rootNavigator: true).push( final val = await Navigator.of(context, rootNavigator: true).push(
CupertinoPageRoute( CupertinoPageRoute(
fullscreenDialog: true, fullscreenDialog: true,
@@ -413,10 +450,42 @@ class _ActiveGameViewState extends State<ActiveGameView> {
), ),
), ),
); );
if (widget.gameSession.isGameFinished && mounted) {
String winner = widget.gameSession.winner;
int winnerIndex = widget.gameSession.players.indexOf(winner);
int points = widget.gameSession.playerScores[winnerIndex];
confettiController.play();
await Future.delayed(const Duration(milliseconds: 300));
if (context.mounted) {
showCupertinoDialog(
context: context,
builder: (BuildContext context) {
return CupertinoAlertDialog(
title: Text(AppLocalizations.of(context).end_of_game_title),
content: Text(AppLocalizations.of(context)
.end_of_game_message(1, winner, points)),
actions: [
CupertinoDialogAction(
child: Text(AppLocalizations.of(context).ok),
onPressed: () {
confettiController.stop();
Navigator.pop(context);
},
),
],
);
});
}
}
if (val != null && val >= 0) { if (val != null && val >= 0) {
WidgetsBinding.instance.addPostFrameCallback((_) async { WidgetsBinding.instance.addPostFrameCallback((_) async {
await Future.delayed(const Duration(milliseconds: 600)); await Future.delayed(const Duration(milliseconds: 600));
_openRoundView(val); if (context.mounted) {
_openRoundView(context, val);
}
}); });
} }
} }

View File

@@ -2,7 +2,7 @@ name: cabo_counter
description: "Mobile app for the card game Cabo" description: "Mobile app for the card game Cabo"
publish_to: 'none' publish_to: 'none'
version: 0.5.1+568 version: 0.5.2+579
environment: environment:
sdk: ^3.5.4 sdk: ^3.5.4
@@ -30,6 +30,7 @@ dependencies:
rate_my_app: ^2.3.2 rate_my_app: ^2.3.2
reorderables: ^0.4.2 reorderables: ^0.4.2
collection: ^1.18.0 collection: ^1.18.0
confetti: ^0.6.0
dev_dependencies: dev_dependencies:
flutter_test: flutter_test: