From 88275c4ae61433776da6ca194dd84bcc2f106f4d Mon Sep 17 00:00:00 2001 From: JulianAlcala Date: Tue, 3 Mar 2026 12:15:50 +0100 Subject: [PATCH] confetti animation --- apps/mobile_app/pubspec.lock | 8 + .../activity/.dart_tool/package_config.json | 6 + .../activity/.dart_tool/package_graph.json | 9 + modules/activity/pubspec.lock | 8 + .../child_wallet/child_wallet_screen.dart | 422 ++++++++++-------- .../src/features/deposit/deposit_screen.dart | 102 +---- .../deposit/deposit_success_provider.dart | 22 + packages/design_system/lib/design_system.dart | 1 + .../lib/src/confetti/confetti_overlay.dart | 91 ++++ packages/design_system/pubspec.yaml | 1 + .../antelop/antelop/maven-metadata.xml | 2 +- .../antelop/antelop/maven-metadata.xml.md5 | 2 +- .../antelop/antelop/maven-metadata.xml.sha1 | 2 +- .../payments/.dart_tool/package_config.json | 6 + .../payments/.dart_tool/package_graph.json | 9 + packages/payments/pubspec.lock | 8 + 16 files changed, 401 insertions(+), 298 deletions(-) create mode 100644 modules/home/lib/src/features/deposit/deposit_success_provider.dart create mode 100644 packages/design_system/lib/src/confetti/confetti_overlay.dart diff --git a/apps/mobile_app/pubspec.lock b/apps/mobile_app/pubspec.lock index 721dc4e4..ea22164a 100644 --- a/apps/mobile_app/pubspec.lock +++ b/apps/mobile_app/pubspec.lock @@ -199,6 +199,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.1" + confetti: + dependency: transitive + description: + name: confetti + sha256: "979aafde2428c53947892c95eb244466c109c129b7eee9011f0a66caaca52267" + url: "https://pub.dev" + source: hosted + version: "0.7.0" convert: dependency: transitive description: diff --git a/modules/activity/.dart_tool/package_config.json b/modules/activity/.dart_tool/package_config.json index 64f824a3..416e3072 100644 --- a/modules/activity/.dart_tool/package_config.json +++ b/modules/activity/.dart_tool/package_config.json @@ -121,6 +121,12 @@ "packageUri": "lib/", "languageVersion": "3.4" }, + { + "name": "confetti", + "rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/confetti-0.7.0", + "packageUri": "lib/", + "languageVersion": "2.17" + }, { "name": "convert", "rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/convert-3.1.2", diff --git a/modules/activity/.dart_tool/package_graph.json b/modules/activity/.dart_tool/package_graph.json index 95920285..4ab3c460 100644 --- a/modules/activity/.dart_tool/package_graph.json +++ b/modules/activity/.dart_tool/package_graph.json @@ -104,6 +104,7 @@ "name": "design_system", "version": "0.0.1", "dependencies": [ + "confetti", "country_code_picker", "flutter", "flutter_riverpod", @@ -356,6 +357,14 @@ "meta" ] }, + { + "name": "confetti", + "version": "0.7.0", + "dependencies": [ + "flutter", + "vector_math" + ] + }, { "name": "lottie", "version": "3.3.2", diff --git a/modules/activity/pubspec.lock b/modules/activity/pubspec.lock index 1fc3502f..dbaaf8a5 100644 --- a/modules/activity/pubspec.lock +++ b/modules/activity/pubspec.lock @@ -161,6 +161,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.1" + confetti: + dependency: transitive + description: + name: confetti + sha256: "979aafde2428c53947892c95eb244466c109c129b7eee9011f0a66caaca52267" + url: "https://pub.dev" + source: hosted + version: "0.7.0" convert: dependency: transitive description: diff --git a/modules/home/lib/src/features/child_wallet/child_wallet_screen.dart b/modules/home/lib/src/features/child_wallet/child_wallet_screen.dart index 4f9c6da5..adbc5172 100644 --- a/modules/home/lib/src/features/child_wallet/child_wallet_screen.dart +++ b/modules/home/lib/src/features/child_wallet/child_wallet_screen.dart @@ -8,10 +8,11 @@ import 'package:sf_localizations/sf_localizations.dart'; import '../../card_colors.dart'; import '../../presentation/state/home_view_model.dart'; +import '../deposit/deposit_success_provider.dart'; import 'child_wallet_view_model.dart'; import 'wallet_actions_bar.dart'; -class ChildWalletScreen extends ConsumerWidget { +class ChildWalletScreen extends ConsumerStatefulWidget { final String childId; final NavigationContract navigation; @@ -22,16 +23,21 @@ class ChildWalletScreen extends ConsumerWidget { }); @override - Widget build(BuildContext context, WidgetRef ref) { - final theme = ref.watch(themePortProvider); - final viewState = ref.watch(childWalletViewModelProvider(childId)); + ConsumerState createState() => _ChildWalletScreenState(); +} - ref.listen(childWalletViewModelProvider(childId), (prev, next) { +class _ChildWalletScreenState extends ConsumerState { + @override + Widget build(BuildContext context) { + final theme = ref.watch(themePortProvider); + final viewState = ref.watch(childWalletViewModelProvider(widget.childId)); + final depositSuccess = ref.watch(depositSuccessProvider(widget.childId)); + + ref.listen(childWalletViewModelProvider(widget.childId), (prev, next) { if (next.cardStatusSuccess && !(prev?.cardStatusSuccess ?? false)) { - ref.read(homeViewModelProvider.notifier).updateChildCardStatus( - childId, - next.cardStatus, - ); + ref + .read(homeViewModelProvider.notifier) + .updateChildCardStatus(widget.childId, next.cardStatus); showTopSnackbar( context, message: context.translate(I18n.cardStatusSuccess), @@ -50,7 +56,7 @@ class ChildWalletScreen extends ConsumerWidget { if (viewState.showPin) { final viewModel = ref.read( - childWalletViewModelProvider(childId).notifier, + childWalletViewModelProvider(widget.childId).notifier, ); return Scaffold( backgroundColor: theme.getColorFor(ThemeCode.backgroundSecondary), @@ -117,197 +123,219 @@ class ChildWalletScreen extends ConsumerWidget { cardStatus: viewState.cardStatus, ); - return Scaffold( - backgroundColor: theme.getColorFor(ThemeCode.backgroundSecondary), - body: Stack( - children: [ - DecoratedBox( - decoration: BoxDecoration( - borderRadius: const BorderRadius.only( - bottomRight: Radius.circular(24), - bottomLeft: Radius.circular(24), - ), - gradient: LinearGradient( - begin: Alignment.topLeft, - end: Alignment.bottomRight, - colors: locked ? theme.getDisabledCardColors() : cardColors, + return ConfettiOverlay( + play: depositSuccess, + onPlayed: () => + ref.read(depositSuccessProvider(widget.childId).notifier).reset(), + child: Scaffold( + backgroundColor: theme.getColorFor(ThemeCode.backgroundSecondary), + body: Stack( + children: [ + DecoratedBox( + decoration: BoxDecoration( + borderRadius: const BorderRadius.only( + bottomRight: Radius.circular(24), + bottomLeft: Radius.circular(24), + ), + gradient: LinearGradient( + begin: Alignment.topLeft, + end: Alignment.bottomRight, + colors: locked ? theme.getDisabledCardColors() : cardColors, + ), ), + child: SizedBox(width: double.infinity, height: 420), ), - child: SizedBox(width: double.infinity, height: 420), - ), - SingleChildScrollView( - padding: EdgeInsets.symmetric(vertical: 50, horizontal: 20), - child: Column( - spacing: 24, - children: [ - Row( - spacing: 7, - children: [ - IconButton( - onPressed: () => navigation.goBack(), - icon: Icon( - Icons.arrow_back_ios_new_outlined, - color: theme.getColorFor(ThemeCode.backgroundPrimary), - size: 24, - ), - ), - _buildGenderAvatar(device?.carrierGenre, 50), - Text( - childName, - style: TextStyle( - color: theme.getColorFor(ThemeCode.backgroundPrimary), - fontWeight: FontWeight.bold, - fontSize: 20, - ), - ), - ], - ), - Column( - spacing: 16, - children: [ - MoneyText( - text: "${availableBalance.toString()}€", - size: 60, - secondarySize: 24, - color: theme.getColorFor(ThemeCode.textSecondary), - ), - Text( - context.translate(I18n.childWalletAvailableBalance), - style: TextStyle( - color: theme.getColorFor(ThemeCode.backgroundPrimary), - ), - ), - LinearProgressIndicator( - value: 0.7, - color: theme.getColorFor(ThemeCode.backgroundPrimary), - backgroundColor: theme - .getColorFor(ThemeCode.backgroundPrimary) - .withAlpha(0x4C), - minHeight: 10, - borderRadius: BorderRadius.all(Radius.circular(5)), - ), - if (CardStatus.fromString(viewState.cardStatus) == CardStatus.lost || - CardStatus.fromString(viewState.cardStatus) == CardStatus.stolen) - TextButton( - style: ButtonStyle( - padding: WidgetStatePropertyAll(EdgeInsets.all(0)), - ), - onPressed: viewState.isUpdatingCard - ? null - : () => _showDeleteConfirmation(context, ref), - child: Row( - spacing: 10, - children: [ - Icon( - Icons.delete_outline, - size: 24, - color: theme.getColorFor(ThemeCode.textSecondary), - ), - Text( - context.translate(I18n.deleteDevice), - style: TextStyle( - fontWeight: FontWeight.w500, - fontSize: 16, - color: theme.getColorFor(ThemeCode.textSecondary), - ), - ), - ], - ), - ) - else - TextButton( - style: ButtonStyle( - padding: WidgetStatePropertyAll(EdgeInsets.all(0)), - ), - onPressed: () => - _showCardStatusSheet(context, ref, theme), - child: Row( - spacing: 10, - children: [ - Icon( - Icons.lock_outline, - size: 24, - color: theme.getColorFor(ThemeCode.textSecondary), - ), - Text( - locked - ? context.translate(I18n.childWalletUnlockCard) - : context.translate(I18n.childWalletLockCard), - style: TextStyle( - fontWeight: FontWeight.w500, - fontSize: 16, - color: theme.getColorFor(ThemeCode.textSecondary), - ), - ), - ], + SingleChildScrollView( + padding: EdgeInsets.symmetric(vertical: 50, horizontal: 20), + child: Column( + spacing: 24, + children: [ + Row( + spacing: 7, + children: [ + IconButton( + onPressed: () => widget.navigation.goBack(), + icon: Icon( + Icons.arrow_back_ios_new_outlined, + color: theme.getColorFor(ThemeCode.backgroundPrimary), + size: 24, ), ), - ], - ), - Column( - spacing: 16, - children: [ - if (!locked) - WalletActionsBar(childId: childId, navigation: navigation), - Container( - padding: EdgeInsets.all(15), - decoration: BoxDecoration( - color: theme.getColorFor(ThemeCode.backgroundPrimary), - borderRadius: BorderRadius.all(Radius.circular(20)), + _buildGenderAvatar(device?.carrierGenre, 50), + Text( + childName, + style: TextStyle( + color: theme.getColorFor(ThemeCode.backgroundPrimary), + fontWeight: FontWeight.bold, + fontSize: 20, + ), ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - context.translate( - I18n.childWalletRecentTransactions, - ), - style: TextStyle( - fontSize: 20, - fontWeight: FontWeight.w500, - ), + ], + ), + Column( + spacing: 16, + children: [ + MoneyText( + text: "${availableBalance.toString()}€", + size: 60, + secondarySize: 24, + color: theme.getColorFor(ThemeCode.textSecondary), + ), + Text( + context.translate(I18n.childWalletAvailableBalance), + style: TextStyle( + color: theme.getColorFor(ThemeCode.backgroundPrimary), + ), + ), + LinearProgressIndicator( + value: 0.7, + color: theme.getColorFor(ThemeCode.backgroundPrimary), + backgroundColor: theme + .getColorFor(ThemeCode.backgroundPrimary) + .withAlpha(0x4C), + minHeight: 10, + borderRadius: BorderRadius.all(Radius.circular(5)), + ), + if (CardStatus.fromString(viewState.cardStatus) == + CardStatus.lost || + CardStatus.fromString(viewState.cardStatus) == + CardStatus.stolen) + TextButton( + style: ButtonStyle( + padding: WidgetStatePropertyAll(EdgeInsets.all(0)), ), - const SizedBox(height: 16), - if (viewState.isLoadingTransactions) - const Center( - child: Padding( - padding: EdgeInsets.all(24), - child: AppLoadingIndicator(size: 48), + onPressed: viewState.isUpdatingCard + ? null + : () => _showDeleteConfirmation(context, ref), + child: Row( + spacing: 10, + children: [ + Icon( + Icons.delete_outline, + size: 24, + color: theme.getColorFor( + ThemeCode.textSecondary, + ), ), - ) - else if (viewState.transactions.isEmpty) - Padding( - padding: const EdgeInsets.all(24), - child: Center( - child: Text( - context.translate( - I18n.activityNoTransactions, - ), - style: TextStyle( - fontSize: 14, - color: theme.getColorFor( - ThemeCode.textPrimary, - ), + Text( + context.translate(I18n.deleteDevice), + style: TextStyle( + fontWeight: FontWeight.w500, + fontSize: 16, + color: theme.getColorFor( + ThemeCode.textSecondary, ), ), ), - ) - else - ...viewState.transactions.map( - (tx) => Padding( - padding: const EdgeInsets.only(bottom: 12), - child: TransactionTile(transaction: tx), + ], + ), + ) + else + TextButton( + style: ButtonStyle( + padding: WidgetStatePropertyAll(EdgeInsets.all(0)), + ), + onPressed: () => + _showCardStatusSheet(context, ref, theme), + child: Row( + spacing: 10, + children: [ + Icon( + Icons.lock_outline, + size: 24, + color: theme.getColorFor( + ThemeCode.textSecondary, + ), + ), + Text( + locked + ? context.translate( + I18n.childWalletUnlockCard, + ) + : context.translate( + I18n.childWalletLockCard, + ), + style: TextStyle( + fontWeight: FontWeight.w500, + fontSize: 16, + color: theme.getColorFor( + ThemeCode.textSecondary, + ), + ), + ), + ], + ), + ), + ], + ), + Column( + spacing: 16, + children: [ + if (!locked) + WalletActionsBar( + childId: widget.childId, + navigation: widget.navigation, + ), + Container( + padding: EdgeInsets.all(15), + decoration: BoxDecoration( + color: theme.getColorFor(ThemeCode.backgroundPrimary), + borderRadius: BorderRadius.all(Radius.circular(20)), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + context.translate( + I18n.childWalletRecentTransactions, + ), + style: TextStyle( + fontSize: 20, + fontWeight: FontWeight.w500, ), ), - ], + const SizedBox(height: 16), + if (viewState.isLoadingTransactions) + const Center( + child: Padding( + padding: EdgeInsets.all(24), + child: AppLoadingIndicator(size: 48), + ), + ) + else if (viewState.transactions.isEmpty) + Padding( + padding: const EdgeInsets.all(24), + child: Center( + child: Text( + context.translate( + I18n.activityNoTransactions, + ), + style: TextStyle( + fontSize: 14, + color: theme.getColorFor( + ThemeCode.textPrimary, + ), + ), + ), + ), + ) + else + ...viewState.transactions.map( + (tx) => Padding( + padding: const EdgeInsets.only(bottom: 12), + child: TransactionTile(transaction: tx), + ), + ), + ], + ), ), - ), - ], - ), - ], + ], + ), + ], + ), ), - ), - ], + ], + ), ), ); } @@ -344,7 +372,7 @@ class ChildWalletScreen extends ConsumerWidget { borderRadius: BorderRadius.vertical(top: Radius.circular(24)), ), backgroundColor: theme.getColorFor(ThemeCode.backgroundPrimary), - builder: (_) => _CardStatusSheet(childId: childId), + builder: (_) => _CardStatusSheet(childId: widget.childId), ); } @@ -354,9 +382,7 @@ class ChildWalletScreen extends ConsumerWidget { context: context, builder: (_) => AlertDialog( backgroundColor: theme.getColorFor(ThemeCode.backgroundPrimary), - shape: RoundedRectangleBorder( - borderRadius: BorderRadius.circular(16), - ), + shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)), title: Text(context.translate(I18n.deleteDeviceConfirmTitle)), content: Text(context.translate(I18n.deleteDeviceConfirmMessage)), actions: [ @@ -368,17 +394,19 @@ class ChildWalletScreen extends ConsumerWidget { onPressed: () async { Navigator.of(context).pop(); final viewModel = ref.read( - childWalletViewModelProvider(childId).notifier, + childWalletViewModelProvider(widget.childId).notifier, ); final success = await viewModel.deleteDevice(); if (success && context.mounted) { - ref.read(homeViewModelProvider.notifier).removeChild(childId); + ref + .read(homeViewModelProvider.notifier) + .removeChild(widget.childId); showTopSnackbar( context, message: context.translate(I18n.deleteDeviceSuccess), type: MessageType.success, ); - navigation.goBack(); + widget.navigation.goBack(); } }, child: Text( @@ -417,7 +445,11 @@ class _CardStatusSheetState extends ConsumerState<_CardStatusSheet> { final viewState = ref.watch(childWalletViewModelProvider(widget.childId)); final currentStatus = viewState.cardStatus; final statuses = CardStatus.fromString(currentStatus) == CardStatus.unlock - ? [CardStatus.lock.value, CardStatus.lost.value, CardStatus.stolen.value] + ? [ + CardStatus.lock.value, + CardStatus.lost.value, + CardStatus.stolen.value, + ] : [CardStatus.unlock.value]; _selected ??= statuses.first; diff --git a/modules/home/lib/src/features/deposit/deposit_screen.dart b/modules/home/lib/src/features/deposit/deposit_screen.dart index 95677c02..11c71dc7 100644 --- a/modules/home/lib/src/features/deposit/deposit_screen.dart +++ b/modules/home/lib/src/features/deposit/deposit_screen.dart @@ -7,6 +7,7 @@ import 'package:sf_localizations/sf_localizations.dart'; import '../../card_colors.dart'; import '../child_wallet/child_data_provider.dart'; +import 'deposit_success_provider.dart'; import 'deposit_view_model.dart'; class DepositScreen extends ConsumerWidget { @@ -33,6 +34,7 @@ class DepositScreen extends ConsumerWidget { message: context.translate(I18n.walletMoveSuccess), type: MessageType.success, ); + ref.read(depositSuccessProvider(childId).notifier).trigger(); navigation.goBack(); } if (next.errorMessage.isNotEmpty && @@ -122,106 +124,6 @@ class DepositScreen extends ConsumerWidget { ), ], ), - - // Container( - // decoration: BoxDecoration( - // color: theme.getColorFor(ThemeCode.backgroundPrimary), - // borderRadius: const BorderRadius.all(Radius.circular(20)), - // ), - // padding: const EdgeInsets.all(10), - // child: Column( - // spacing: 10, - // children: [ - // Text( - // context.translate(I18n.depositReason), - // style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20), - // ), - // Text(context.translate(I18n.watchInfo)), - // CheckboxListTile( - // contentPadding: EdgeInsets.zero, - // title: Text(context.translate(I18n.depositReasonWeekly)), - // controlAffinity: ListTileControlAffinity.leading, - // value: viewState.reason == 'weekly', - // onChanged: (_) => viewModel.selectReason('weekly'), - // activeColor: theme.getColorFor(ThemeCode.buttonPrimary), - // ), - // CheckboxListTile( - // contentPadding: EdgeInsets.zero, - // title: Text(context.translate(I18n.depositReasonGoalMet)), - // controlAffinity: ListTileControlAffinity.leading, - // value: viewState.reason == 'goal', - // onChanged: (_) => viewModel.selectReason('goal'), - // activeColor: theme.getColorFor(ThemeCode.buttonPrimary), - // ), - // CheckboxListTile( - // contentPadding: EdgeInsets.zero, - // title: Text(context.translate(I18n.depositReasonExtraordinary)), - // controlAffinity: ListTileControlAffinity.leading, - // value: viewState.reason == 'extraordinary', - // onChanged: (_) => viewModel.selectReason('extraordinary'), - // activeColor: theme.getColorFor(ThemeCode.buttonPrimary), - // ), - // CheckboxListTile( - // contentPadding: EdgeInsets.zero, - // title: Text(context.translate(I18n.depositReasonOther)), - // controlAffinity: ListTileControlAffinity.leading, - // value: viewState.reason == 'other', - // onChanged: (_) => viewModel.selectReason('other'), - // activeColor: theme.getColorFor(ThemeCode.buttonPrimary), - // ), - // CustomTextField( - // controller: viewModel.messageController, - // lines: 3, - // length: 150, - // label: context.translate(I18n.depositMessageLabel, args: {'name': childName}), - // hint: context.translate(I18n.allowanceMessageHint), - // ), - // Align( - // alignment: Alignment.topLeft, - // child: Text(context.translate(I18n.allowanceMaxChars, args: {'count': '150'})), - // ), - // ], - // ), - // ), - - // Container( - // decoration: BoxDecoration( - // color: theme.getColorFor(ThemeCode.backgroundPrimary), - // borderRadius: const BorderRadius.all(Radius.circular(20)), - // ), - // padding: const EdgeInsets.all(10), - // child: Column( - // spacing: 10, - // children: [ - // Text( - // context.translate(I18n.depositWhenSend), - // style: TextStyle(fontWeight: FontWeight.bold, fontSize: 20), - // ), - // Text(context.translate(I18n.watchInfo)), - // CheckboxListTile( - // contentPadding: EdgeInsets.zero, - // title: Text(context.translate(I18n.depositNow)), - // controlAffinity: ListTileControlAffinity.leading, - // value: !viewState.program, - // onChanged: (_) { - // if (viewState.program) viewModel.toggleProgram(); - // }, - // activeColor: theme.getColorFor(ThemeCode.buttonPrimary), - // ), - // CheckboxListTile( - // contentPadding: EdgeInsets.zero, - // title: Text(context.translate(I18n.depositSchedule)), - // controlAffinity: ListTileControlAffinity.leading, - // value: viewState.program, - // onChanged: (_) { - // if (!viewState.program) viewModel.toggleProgram(); - // }, - // activeColor: theme.getColorFor(ThemeCode.buttonPrimary), - // ), - // if (viewState.program) CustomTextField(), - // ], - // ), - // ), ], ); } diff --git a/modules/home/lib/src/features/deposit/deposit_success_provider.dart b/modules/home/lib/src/features/deposit/deposit_success_provider.dart new file mode 100644 index 00000000..2f7eac2b --- /dev/null +++ b/modules/home/lib/src/features/deposit/deposit_success_provider.dart @@ -0,0 +1,22 @@ +import 'package:flutter_riverpod/flutter_riverpod.dart'; + +final depositSuccessProvider = + NotifierProvider.family( + DepositSuccessNotifier.new, +); + +class DepositSuccessNotifier extends Notifier { + final String childId; + DepositSuccessNotifier(this.childId); + + @override + bool build() => false; + + void trigger() { + state = true; + } + + void reset() { + state = false; + } +} diff --git a/packages/design_system/lib/design_system.dart b/packages/design_system/lib/design_system.dart index c07d2a32..790012a0 100644 --- a/packages/design_system/lib/design_system.dart +++ b/packages/design_system/lib/design_system.dart @@ -15,3 +15,4 @@ export 'src/containers/section_container.dart'; export 'src/containers/footer_container.dart'; export 'src/rows/editable_row.dart'; export 'src/loading/app_loading_indicator.dart'; +export 'src/confetti/confetti_overlay.dart'; diff --git a/packages/design_system/lib/src/confetti/confetti_overlay.dart b/packages/design_system/lib/src/confetti/confetti_overlay.dart new file mode 100644 index 00000000..e1260eff --- /dev/null +++ b/packages/design_system/lib/src/confetti/confetti_overlay.dart @@ -0,0 +1,91 @@ +import 'dart:math'; + +import 'package:confetti/confetti.dart'; +import 'package:flutter/material.dart'; + +class ConfettiOverlay extends StatefulWidget { + final bool play; + final Widget child; + final VoidCallback? onPlayed; + final Duration duration; + final List colors; + + const ConfettiOverlay({ + super.key, + required this.play, + required this.child, + this.onPlayed, + this.duration = const Duration(seconds: 3), + this.colors = const [ + Color(0xFFFF69B4), + Color(0xFF4169E1), + Color(0xFFFF8C00), + Color(0xFF8A2BE2), + Color(0xFFFFD700), + ], + }); + + @override + State createState() => _ConfettiOverlayState(); +} + +class _ConfettiOverlayState extends State { + late final ConfettiController _controller; + + @override + void initState() { + super.initState(); + _controller = ConfettiController(duration: widget.duration); + if (widget.play) { + WidgetsBinding.instance.addPostFrameCallback((_) { + _controller.play(); + widget.onPlayed?.call(); + }); + } + } + + @override + void didUpdateWidget(covariant ConfettiOverlay oldWidget) { + super.didUpdateWidget(oldWidget); + if (widget.play && !oldWidget.play) { + _controller.play(); + if (widget.onPlayed != null) { + WidgetsBinding.instance.addPostFrameCallback((_) { + widget.onPlayed!(); + }); + } + } + } + + @override + void dispose() { + _controller.dispose(); + super.dispose(); + } + + @override + Widget build(BuildContext context) { + return Stack( + children: [ + widget.child, + Align( + alignment: Alignment.topCenter, + child: ConfettiWidget( + confettiController: _controller, + blastDirectionality: BlastDirectionality.explosive, + gravity: 0.1, + numberOfParticles: 10, + emissionFrequency: 0.2, + colors: widget.colors, + createParticlePath: (size) { + final random = Random(); + final w = 10.0 + random.nextDouble() * 5.0; + final h = 5.0 + random.nextDouble() * 2.5; + return Path()..addRect(Rect.fromLTWH(0, 0, w, h)); + }, + ), + ), + ], + ); + } +} diff --git a/packages/design_system/pubspec.yaml b/packages/design_system/pubspec.yaml index 2bb79c2e..a079002f 100644 --- a/packages/design_system/pubspec.yaml +++ b/packages/design_system/pubspec.yaml @@ -21,6 +21,7 @@ dependencies: path: ../../packages/fonts top_snackbar_flutter: ^3.3.0 lottie: ^3.3.1 + confetti: ^0.7.0 dev_dependencies: flutter_test: diff --git a/packages/flutter_treezor_entrust_sdk_bridge/android/build/com/entrust/antelop/antelop/maven-metadata.xml b/packages/flutter_treezor_entrust_sdk_bridge/android/build/com/entrust/antelop/antelop/maven-metadata.xml index b032a5c5..2b5e4c3d 100644 --- a/packages/flutter_treezor_entrust_sdk_bridge/android/build/com/entrust/antelop/antelop/maven-metadata.xml +++ b/packages/flutter_treezor_entrust_sdk_bridge/android/build/com/entrust/antelop/antelop/maven-metadata.xml @@ -7,6 +7,6 @@ 2.6.4 - 20260301000000 + 20260302000000 diff --git a/packages/flutter_treezor_entrust_sdk_bridge/android/build/com/entrust/antelop/antelop/maven-metadata.xml.md5 b/packages/flutter_treezor_entrust_sdk_bridge/android/build/com/entrust/antelop/antelop/maven-metadata.xml.md5 index fab503a1..0137847f 100644 --- a/packages/flutter_treezor_entrust_sdk_bridge/android/build/com/entrust/antelop/antelop/maven-metadata.xml.md5 +++ b/packages/flutter_treezor_entrust_sdk_bridge/android/build/com/entrust/antelop/antelop/maven-metadata.xml.md5 @@ -1 +1 @@ -ab3c3fa378ac166364ea7cfd739ddb6f \ No newline at end of file +9728121fa548f85a30f9c665eaf069c7 \ No newline at end of file diff --git a/packages/flutter_treezor_entrust_sdk_bridge/android/build/com/entrust/antelop/antelop/maven-metadata.xml.sha1 b/packages/flutter_treezor_entrust_sdk_bridge/android/build/com/entrust/antelop/antelop/maven-metadata.xml.sha1 index e6b26a9b..72e6f8d3 100644 --- a/packages/flutter_treezor_entrust_sdk_bridge/android/build/com/entrust/antelop/antelop/maven-metadata.xml.sha1 +++ b/packages/flutter_treezor_entrust_sdk_bridge/android/build/com/entrust/antelop/antelop/maven-metadata.xml.sha1 @@ -1 +1 @@ -8a764ee335f810b5ecc8b687de8da58391118e7d \ No newline at end of file +995b4bcedd19cee87123779a9acc8835b2eac2c6 \ No newline at end of file diff --git a/packages/payments/.dart_tool/package_config.json b/packages/payments/.dart_tool/package_config.json index 444a4b94..74790dad 100644 --- a/packages/payments/.dart_tool/package_config.json +++ b/packages/payments/.dart_tool/package_config.json @@ -145,6 +145,12 @@ "packageUri": "lib/", "languageVersion": "3.4" }, + { + "name": "confetti", + "rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/confetti-0.7.0", + "packageUri": "lib/", + "languageVersion": "2.17" + }, { "name": "convert", "rootUri": "file:///Users/juliandalcalaf/.pub-cache/hosted/pub.dev/convert-3.1.2", diff --git a/packages/payments/.dart_tool/package_graph.json b/packages/payments/.dart_tool/package_graph.json index 97e21d72..5bcbf308 100644 --- a/packages/payments/.dart_tool/package_graph.json +++ b/packages/payments/.dart_tool/package_graph.json @@ -80,6 +80,7 @@ "name": "design_system", "version": "0.0.1", "dependencies": [ + "confetti", "country_code_picker", "flutter", "flutter_riverpod", @@ -349,6 +350,14 @@ "dio" ] }, + { + "name": "confetti", + "version": "0.7.0", + "dependencies": [ + "flutter", + "vector_math" + ] + }, { "name": "lottie", "version": "3.3.2", diff --git a/packages/payments/pubspec.lock b/packages/payments/pubspec.lock index 194d4046..cf1fca23 100644 --- a/packages/payments/pubspec.lock +++ b/packages/payments/pubspec.lock @@ -193,6 +193,14 @@ packages: url: "https://pub.dev" source: hosted version: "1.19.1" + confetti: + dependency: transitive + description: + name: confetti + sha256: "979aafde2428c53947892c95eb244466c109c129b7eee9011f0a66caaca52267" + url: "https://pub.dev" + source: hosted + version: "0.7.0" convert: dependency: transitive description: