From a3c74692c92f2bd3ddecaf4931e00e97a7f68cf2 Mon Sep 17 00:00:00 2001 From: ggurdin <46800240+ggurdin@users.noreply.github.com> Date: Wed, 23 Apr 2025 15:13:02 -0400 Subject: [PATCH] chore: make construct notification border gold and show confetti on construct notification (#2540) --- .../analytics_misc/gain_points_animation.dart | 66 ++--- .../chat/utils/unlocked_morphs_snackbar.dart | 267 ++++++++++-------- 2 files changed, 168 insertions(+), 165 deletions(-) diff --git a/lib/pangea/analytics_misc/gain_points_animation.dart b/lib/pangea/analytics_misc/gain_points_animation.dart index 088c008b7..221828fb9 100644 --- a/lib/pangea/analytics_misc/gain_points_animation.dart +++ b/lib/pangea/analytics_misc/gain_points_animation.dart @@ -9,11 +9,13 @@ import 'package:fluffychat/widgets/matrix.dart'; class PointsGainedAnimation extends StatefulWidget { final int points; final String targetID; + final bool invert; const PointsGainedAnimation({ super.key, required this.points, required this.targetID, + this.invert = false, }); @override @@ -26,17 +28,15 @@ class PointsGainedAnimationState extends State final Color? loseColor = Colors.red; AnimationController? _controller; - Animation? _offsetAnimation; Animation? _fadeAnimation; - final List> _swayAnimation = []; - final List _initialVelocities = []; + Animation? _progressAnimation; + final List _trajectories = []; final Random _random = Random(); - static const double _particleSpeed = 50; // Base speed for particles. - static const double gravity = 15; // Gravity constant for the animation. - static const int duration = - 2000; // Duration of the animation in milliseconds. + static const double _particleSpeed = 50; + static const double gravity = 15; + static const int duration = 2000; @override void initState() { @@ -48,7 +48,7 @@ class PointsGainedAnimationState extends State vsync: this, ); - _offsetAnimation = Tween( + _progressAnimation = Tween( begin: 0.0, end: 3.0, ).animate( @@ -68,40 +68,31 @@ class PointsGainedAnimationState extends State ), ); - _showPointsGained(); + initParticleTrajectories(); + _controller?.forward().then( + (_) { + if (!mounted) return; + MatrixState.pAnyState.closeOverlay("${widget.targetID}_points"); + }, + ); } void initParticleTrajectories() { - _initialVelocities.clear(); for (int i = 0; i < widget.points.abs(); i++) { final angle = (i - widget.points.abs() / 2) / widget.points.abs() * (pi / 3) + (_random.nextDouble() - 0.5) * pi / 6 + pi / 2; + final speedMultiplier = 0.75 + _random.nextDouble() / 4; // Random speed multiplier. final speed = _particleSpeed * speedMultiplier * (widget.points > 0 ? 2 : 1); // Exponential speed. - _initialVelocities.add(Offset(speed * cos(angle), -speed * sin(angle))); - } - } - - void initSwayAnimations() { - if (_controller == null) return; - _swayAnimation.clear(); - initParticleTrajectories(); - - for (int i = 0; i < widget.points; i++) { - _swayAnimation.add( - Tween( - begin: 0.0, - end: 2 * pi, - ).animate( - CurvedAnimation( - parent: _controller!, - curve: Curves.linear, - ), + _trajectories.add( + Offset( + speed * cos(angle) * (widget.invert ? -1 : 1), + -speed * sin(angle) * (widget.invert ? -1 : 1), ), ); } @@ -113,23 +104,12 @@ class PointsGainedAnimationState extends State super.dispose(); } - void _showPointsGained() { - initSwayAnimations(); - _controller?.reset(); - _controller?.forward().then( - (_) { - if (!mounted) return; - MatrixState.pAnyState.closeOverlay("${widget.targetID}_points"); - }, - ); - } - @override Widget build(BuildContext context) { if (widget.points == 0 || _controller == null || _fadeAnimation == null || - _offsetAnimation == null) { + _progressAnimation == null) { WidgetsBinding.instance.addPostFrameCallback((_) { if (mounted) { MatrixState.pAnyState.closeOverlay("${widget.targetID}_points"); @@ -163,8 +143,8 @@ class PointsGainedAnimationState extends State return AnimatedBuilder( animation: _controller!, builder: (context, child) { - final progress = _offsetAnimation!.value; - final trajectory = _initialVelocities[index]; + final progress = _progressAnimation!.value; + final trajectory = _trajectories[index]; return Transform.translate( offset: Offset( trajectory.dx * progress, diff --git a/lib/pangea/chat/utils/unlocked_morphs_snackbar.dart b/lib/pangea/chat/utils/unlocked_morphs_snackbar.dart index fe28309e6..675676ef6 100644 --- a/lib/pangea/chat/utils/unlocked_morphs_snackbar.dart +++ b/lib/pangea/chat/utils/unlocked_morphs_snackbar.dart @@ -10,6 +10,7 @@ import 'package:fluffychat/config/app_config.dart'; import 'package:fluffychat/config/themes.dart'; import 'package:fluffychat/pangea/analytics_details_popup/analytics_details_popup.dart'; import 'package:fluffychat/pangea/analytics_misc/construct_type_enum.dart'; +import 'package:fluffychat/pangea/analytics_misc/gain_points_animation.dart'; import 'package:fluffychat/pangea/common/utils/overlay.dart'; import 'package:fluffychat/pangea/constructs/construct_identifier.dart'; import 'package:fluffychat/pangea/morphs/get_grammar_copy.dart'; @@ -110,6 +111,21 @@ class ConstructNotificationOverlayState ); _controller!.forward().then((_) { + OverlayUtil.showOverlay( + overlayKey: "${widget.construct.string}_points", + followerAnchor: Alignment.topCenter, + targetAnchor: Alignment.topCenter, + context: context, + child: PointsGainedAnimation( + points: 50, + targetID: "${widget.construct.string}_notification", + invert: true, + ), + transformTargetId: "${widget.construct.string}_notification", + closePrevOverlay: false, + backDropToDismiss: false, + ignorePointer: true, + ); Future.delayed(const Duration(seconds: 15), () { if (mounted) _close(); }); @@ -145,144 +161,151 @@ class ConstructNotificationOverlayState @override Widget build(BuildContext context) { final isColumnMode = FluffyThemes.isColumnMode(context); - return SafeArea( - child: Material( - type: MaterialType.transparency, - child: SizeTransition( - sizeFactor: _animation!, - axisAlignment: -1.0, - child: LayoutBuilder( - builder: (context, constraints) { - return GestureDetector( - onPanUpdate: (details) { - if (details.delta.dy < -10) _close(); - }, - onTap: _showDetails, - child: Container( - padding: const EdgeInsets.symmetric( - vertical: 16.0, - horizontal: 4.0, - ), - decoration: BoxDecoration( - color: Theme.of(context).colorScheme.surface, - border: Border( - bottom: BorderSide( - color: Theme.of(context) - .colorScheme - .onSurface - .withAlpha(50), - ), + return CompositedTransformTarget( + link: MatrixState.pAnyState + .layerLinkAndKey("${widget.construct.string}_notification") + .link, + child: SafeArea( + key: MatrixState.pAnyState + .layerLinkAndKey("${widget.construct.string}_notification") + .key, + child: Material( + type: MaterialType.transparency, + child: SizeTransition( + sizeFactor: _animation!, + axisAlignment: -1.0, + child: LayoutBuilder( + builder: (context, constraints) { + return GestureDetector( + onPanUpdate: (details) { + if (details.delta.dy < -10) _close(); + }, + onTap: _showDetails, + child: Container( + padding: const EdgeInsets.symmetric( + vertical: 16.0, + horizontal: 4.0, ), - borderRadius: const BorderRadius.only( - bottomLeft: Radius.circular(AppConfig.borderRadius), - bottomRight: Radius.circular(AppConfig.borderRadius), - ), - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - SizedBox( - width: constraints.maxWidth >= 600 ? 120.0 : 65.0, + decoration: BoxDecoration( + color: Theme.of(context).colorScheme.surface, + border: Border( + bottom: BorderSide( + color: AppConfig.gold.withAlpha(200), + width: 2.0, + ), ), - Expanded( - child: Padding( - padding: EdgeInsets.symmetric( - horizontal: isColumnMode ? 16.0 : 8.0, - ), - child: Wrap( - spacing: 16.0, - alignment: WrapAlignment.center, - crossAxisAlignment: WrapCrossAlignment.center, - children: [ - Text( - widget.copy ?? widget.construct.lemma, - style: TextStyle( - fontSize: FluffyThemes.isColumnMode(context) - ? 32.0 - : 16.0, - color: AppConfig.gold, - fontWeight: FontWeight.bold, + borderRadius: const BorderRadius.only( + bottomLeft: Radius.circular(AppConfig.borderRadius), + bottomRight: Radius.circular(AppConfig.borderRadius), + ), + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox( + width: constraints.maxWidth >= 600 ? 120.0 : 65.0, + ), + Expanded( + child: Padding( + padding: EdgeInsets.symmetric( + horizontal: isColumnMode ? 16.0 : 8.0, + ), + child: Wrap( + spacing: 16.0, + alignment: WrapAlignment.center, + crossAxisAlignment: WrapCrossAlignment.center, + children: [ + Text( + widget.copy ?? widget.construct.lemma, + style: TextStyle( + fontSize: FluffyThemes.isColumnMode(context) + ? 32.0 + : 16.0, + color: AppConfig.gold, + fontWeight: FontWeight.bold, + ), + overflow: TextOverflow.ellipsis, ), - overflow: TextOverflow.ellipsis, - ), - MorphIcon( - size: isColumnMode - ? null - : const Size(24.0, 24.0), - morphFeature: - MorphFeaturesEnumExtension.fromString( - widget.construct.category, + MorphIcon( + size: isColumnMode + ? null + : const Size(24.0, 24.0), + morphFeature: + MorphFeaturesEnumExtension.fromString( + widget.construct.category, + ), + morphTag: widget.construct.lemma, ), - morphTag: widget.construct.lemma, - ), - ], + ], + ), ), ), - ), - SizedBox( - width: constraints.maxWidth >= 600 ? 120.0 : 65.0, - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Tooltip( - message: L10n.of(context).details, - child: constraints.maxWidth >= 600 - ? ElevatedButton( - style: IconButton.styleFrom( - padding: const EdgeInsets.symmetric( - vertical: 4.0, - horizontal: 16.0, - ), - ), - onPressed: _showDetails, - child: Text( - L10n.of(context).details, - ), - ) - : SizedBox( - width: 32.0, - height: 32.0, - child: Center( - child: IconButton( - icon: const Icon( - Icons.info_outline, + SizedBox( + width: constraints.maxWidth >= 600 ? 120.0 : 65.0, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Tooltip( + message: L10n.of(context).details, + child: constraints.maxWidth >= 600 + ? ElevatedButton( + style: IconButton.styleFrom( + padding: const EdgeInsets.symmetric( + vertical: 4.0, + horizontal: 16.0, ), - style: IconButton.styleFrom( - padding: const EdgeInsets.all(4.0), + ), + onPressed: _showDetails, + child: Text( + L10n.of(context).details, + ), + ) + : SizedBox( + width: 32.0, + height: 32.0, + child: Center( + child: IconButton( + icon: const Icon( + Icons.info_outline, + ), + style: IconButton.styleFrom( + padding: + const EdgeInsets.all(4.0), + ), + onPressed: _showDetails, + constraints: const BoxConstraints(), ), - onPressed: _showDetails, - constraints: const BoxConstraints(), ), ), + ), + SizedBox( + width: 32.0, + height: 32.0, + child: Center( + child: Tooltip( + message: L10n.of(context).close, + child: IconButton( + icon: const Icon( + Icons.close, + ), + style: IconButton.styleFrom( + padding: const EdgeInsets.all(4.0), + ), + onPressed: _close, + constraints: const BoxConstraints(), ), - ), - SizedBox( - width: 32.0, - height: 32.0, - child: Center( - child: Tooltip( - message: L10n.of(context).close, - child: IconButton( - icon: const Icon( - Icons.close, - ), - style: IconButton.styleFrom( - padding: const EdgeInsets.all(4.0), - ), - onPressed: _close, - constraints: const BoxConstraints(), ), ), ), - ), - ], + ], + ), ), - ), - ], + ], + ), ), - ), - ); - }, + ); + }, + ), ), ), ),