import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; import 'package:lba/gen/assets.gen.dart'; import 'package:lba/res/colors.dart'; import 'package:lba/widgets/buildWarpedInfo.dart'; import 'package:lba/widgets/rate.dart'; enum LikeState { none, liked, disliked } class Reviews extends StatefulWidget { final String name; final String comment; final double rate; final int yesCount; final int noCount; final String date; final Function(bool isLike)? onLikeDislike; final bool? initialLikeState; const Reviews({ super.key, required this.name, required this.comment, required this.rate, required this.yesCount, required this.noCount, required this.date, this.onLikeDislike, this.initialLikeState, }); @override State createState() => _ReviewsState(); } class _ReviewsState extends State with TickerProviderStateMixin { LikeState _likeState = LikeState.none; int _currentYesCount = 0; int _currentNoCount = 0; late AnimationController _likeScaleController; late AnimationController _dislikeScaleController; late AnimationController _particleController; late AnimationController _pulseController; late AnimationController _countController; late AnimationController _likeIconController; late AnimationController _dislikeIconController; late Animation _likeScaleAnimation; late Animation _dislikeScaleAnimation; late Animation _particleAnimation; late Animation _pulseAnimation; late Animation _countAnimation; late Animation _likeColorAnimation; late Animation _dislikeColorAnimation; late Animation _likeIconRotationAnimation; late Animation _dislikeIconRotationAnimation; late Animation _likeIconScaleAnimation; late Animation _dislikeIconScaleAnimation; @override void initState() { super.initState(); _currentYesCount = widget.yesCount; _currentNoCount = widget.noCount; if (widget.initialLikeState == true) { _likeState = LikeState.liked; } else if (widget.initialLikeState == false) { _likeState = LikeState.disliked; } _likeScaleController = AnimationController( duration: const Duration(milliseconds: 600), vsync: this, ); _dislikeScaleController = AnimationController( duration: const Duration(milliseconds: 600), vsync: this, ); _particleController = AnimationController( duration: const Duration(milliseconds: 1200), vsync: this, ); _pulseController = AnimationController( duration: const Duration(milliseconds: 800), vsync: this, ); _countController = AnimationController( duration: const Duration(milliseconds: 400), vsync: this, ); _likeIconController = AnimationController( duration: const Duration(milliseconds: 500), vsync: this, ); _dislikeIconController = AnimationController( duration: const Duration(milliseconds: 500), vsync: this, ); _likeScaleAnimation = Tween(begin: 1.0, end: 1.08).animate( CurvedAnimation(parent: _likeScaleController, curve: Curves.easeOutBack), ); _dislikeScaleAnimation = Tween(begin: 1.0, end: 1.08).animate( CurvedAnimation( parent: _dislikeScaleController, curve: Curves.easeOutBack, ), ); _particleAnimation = Tween(begin: 0.0, end: 1.0).animate( CurvedAnimation(parent: _particleController, curve: Curves.easeOutCubic), ); _pulseAnimation = Tween(begin: 1.0, end: 1.15).animate( CurvedAnimation(parent: _pulseController, curve: Curves.easeInOut), ); _countAnimation = Tween(begin: 0.0, end: 1.0).animate( CurvedAnimation(parent: _countController, curve: Curves.bounceOut), ); _likeColorAnimation = ColorTween( begin: AppColors.hint, end: const Color(0xFF4CAF50), ).animate(_likeScaleController); _dislikeColorAnimation = ColorTween( begin: AppColors.hint, end: const Color(0xFFF44336), ).animate(_dislikeScaleController); _likeIconRotationAnimation = Tween(begin: 0.0, end: 0.1).animate( CurvedAnimation(parent: _likeIconController, curve: Curves.elasticOut), ); _dislikeIconRotationAnimation = Tween( begin: 0.0, end: -0.1, ).animate( CurvedAnimation(parent: _dislikeIconController, curve: Curves.elasticOut), ); _likeIconScaleAnimation = Tween(begin: 1.0, end: 1.3).animate( CurvedAnimation(parent: _likeIconController, curve: Curves.elasticOut), ); _dislikeIconScaleAnimation = Tween(begin: 1.0, end: 1.3).animate( CurvedAnimation(parent: _dislikeIconController, curve: Curves.elasticOut), ); } @override void dispose() { _likeScaleController.dispose(); _dislikeScaleController.dispose(); _particleController.dispose(); _pulseController.dispose(); _countController.dispose(); _likeIconController.dispose(); _dislikeIconController.dispose(); super.dispose(); } void _onLikeTap() async { if (_likeState == LikeState.liked) { setState(() { _likeState = LikeState.none; _currentYesCount--; }); _likeScaleController.reverse(); } else { if (_likeState == LikeState.disliked) { _currentNoCount--; _dislikeScaleController.reverse(); } setState(() { _likeState = LikeState.liked; _currentYesCount++; }); _likeScaleController.forward(); _likeIconController.forward().then((_) => _likeIconController.reverse()); _particleController.forward().then((_) => _particleController.reset()); _pulseController.forward().then((_) => _pulseController.reverse()); _countController.forward().then((_) => _countController.reset()); } widget.onLikeDislike?.call(true); } void _onDislikeTap() async { if (_likeState == LikeState.disliked) { setState(() { _likeState = LikeState.none; _currentNoCount--; }); _dislikeScaleController.reverse(); } else { if (_likeState == LikeState.liked) { _currentYesCount--; _likeScaleController.reverse(); } setState(() { _likeState = LikeState.disliked; _currentNoCount++; }); _dislikeScaleController.forward(); _dislikeIconController.forward().then( (_) => _dislikeIconController.reverse(), ); _particleController.forward().then((_) => _particleController.reset()); _pulseController.forward().then((_) => _pulseController.reverse()); _countController.forward().then((_) => _countController.reset()); } widget.onLikeDislike?.call(false); } Widget _buildParticleEffect({required bool isLike}) { return AnimatedBuilder( animation: _particleAnimation, builder: (context, child) { return CustomPaint( painter: ParticleEffectPainter( progress: _particleAnimation.value, color: isLike ? const Color(0xFF4CAF50) : const Color(0xFFF44336), ), size: const Size(10, 10), ); }, ); } Widget _buildAnimatedLikeButton() { return AnimatedBuilder( animation: Listenable.merge([ _likeScaleController, _pulseController, _countController, ]), builder: (context, child) { final isLiked = _likeState == LikeState.liked; return GestureDetector( onTap: _onLikeTap, child: Stack( clipBehavior: Clip.none, children: [ if (isLiked) Positioned( child: Transform.scale( scale: _pulseAnimation.value, child: Container( width: 30, height: 25, decoration: BoxDecoration( borderRadius: BorderRadius.circular(15), color: _likeColorAnimation.value?.withOpacity(0.2), ), ), ), ), Transform.scale( scale: _likeScaleAnimation.value, child: Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 4, ), decoration: BoxDecoration( borderRadius: BorderRadius.circular(15), color: isLiked ? _likeColorAnimation.value?.withOpacity(0.1) : null, border: isLiked ? Border.all( color: _likeColorAnimation.value ?? Colors.transparent, width: 1.5, ) : null, ), child: Row( mainAxisSize: MainAxisSize.min, children: [ AnimatedBuilder( animation: Listenable.merge([ _likeColorAnimation, _likeIconController, ]), builder: (context, child) { return Transform.rotate( angle: _likeIconRotationAnimation.value, child: Transform.scale( scale: isLiked ? _likeIconScaleAnimation.value : 1.0, child: AnimatedSwitcher( duration: const Duration(milliseconds: 200), child: SvgPicture.asset( isLiked ? Assets.icons.like.path : Assets.icons.like.path, key: ValueKey(isLiked ? 'liked' : 'normal'), color: isLiked ? _likeColorAnimation.value : AppColors.hint, width: 16, height: 16, ), ), ), ); }, ), const SizedBox(width: 4), AnimatedBuilder( animation: _countAnimation, builder: (context, child) { return Transform.scale( scale: 1.0 + (_countAnimation.value * 0.1), child: AnimatedBuilder( animation: _likeColorAnimation, builder: (context, child) { return Text( "Yes ($_currentYesCount)", style: TextStyle( color: isLiked ? _likeColorAnimation.value : AppColors.hint, fontWeight: isLiked ? FontWeight.w600 : FontWeight.normal, fontSize: 12 + (_countAnimation.value * 1), ), ); }, ), ); }, ), ], ), ), ), if (isLiked) Positioned( top: -5, right: -5, child: _buildParticleEffect(isLike: true), ), ], ), ); }, ); } Widget _buildAnimatedDislikeButton() { return AnimatedBuilder( animation: Listenable.merge([ _dislikeScaleController, _pulseController, _countController, ]), builder: (context, child) { final isDisliked = _likeState == LikeState.disliked; return GestureDetector( onTap: _onDislikeTap, child: Stack( clipBehavior: Clip.none, children: [ if (isDisliked) Positioned( child: Transform.scale( scale: _pulseAnimation.value, child: Container( width: 30, height: 25, decoration: BoxDecoration( borderRadius: BorderRadius.circular(15), color: _dislikeColorAnimation.value?.withOpacity(0.2), ), ), ), ), Transform.scale( scale: _dislikeScaleAnimation.value, child: Container( padding: const EdgeInsets.symmetric( horizontal: 8, vertical: 4, ), decoration: BoxDecoration( borderRadius: BorderRadius.circular(15), color: isDisliked ? _dislikeColorAnimation.value?.withOpacity(0.1) : null, border: isDisliked ? Border.all( color: _dislikeColorAnimation.value ?? Colors.transparent, width: 1.5, ) : null, ), child: Row( mainAxisSize: MainAxisSize.min, children: [ AnimatedBuilder( animation: Listenable.merge([ _dislikeColorAnimation, _dislikeIconController, ]), builder: (context, child) { return Transform.rotate( angle: _dislikeIconRotationAnimation.value, child: Transform.scale( scale: isDisliked ? _dislikeIconScaleAnimation.value : 1.0, child: AnimatedSwitcher( duration: const Duration(milliseconds: 200), child: SvgPicture.asset( isDisliked ? Assets.icons.dislike.path : Assets.icons.dislike.path, key: ValueKey( isDisliked ? 'disliked' : 'normal', ), color: isDisliked ? _dislikeColorAnimation.value : AppColors.hint, width: 16, height: 16, ), ), ), ); }, ), const SizedBox(width: 4), AnimatedBuilder( animation: _countAnimation, builder: (context, child) { return Transform.scale( scale: 1.0 + (_countAnimation.value * 0.1), child: AnimatedBuilder( animation: _dislikeColorAnimation, builder: (context, child) { return Text( "No ($_currentNoCount)", style: TextStyle( color: isDisliked ? _dislikeColorAnimation.value : AppColors.hint, fontWeight: isDisliked ? FontWeight.w600 : FontWeight.normal, fontSize: 12 + (_countAnimation.value * 1), ), ); }, ), ); }, ), ], ), ), ), if (isDisliked) Positioned( top: -5, right: -5, child: _buildParticleEffect(isLike: false), ), ], ), ); }, ); } @override Widget build(BuildContext context) { return Padding( padding: const EdgeInsets.all(8.0), child: Column( children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Text(widget.name), const SizedBox(width: 2), Image.asset(Assets.images.usa.path), ], ), Text( "Verified Buyer", style: TextStyle(color: AppColors.confirmButton), ), ], ), CustomStarRating(rating: widget.rate), ], ), const SizedBox(height: 7), buildWrappedInfo("", widget.comment), const SizedBox(height: 10), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Row( children: [ _buildAnimatedLikeButton(), const SizedBox(width: 7), _buildAnimatedDislikeButton(), ], ), Text(widget.date, style: TextStyle(color: AppColors.hint)), ], ), const SizedBox(height: 5), const Divider(thickness: 1.2, height: 2), ], ), ); } } class ParticleEffectPainter extends CustomPainter { final double progress; final Color color; ParticleEffectPainter({required this.progress, required this.color}); @override void paint(Canvas canvas, Size size) { if (progress == 0.0) return; final paint = Paint() ..color = color.withOpacity(1.0 - progress) ..style = PaintingStyle.fill; final particles = [ [0.2, -0.3, 3.0], [0.5, -0.5, 2.5], [0.8, -0.2, 2.0], [0.1, -0.6, 1.5], [0.9, -0.4, 2.0], ]; for (final particle in particles) { final x = size.width * particle[0] + (progress * 20 * (particle[0] - 0.5)); final y = size.height * particle[1] * progress; final particleSize = particle[2] * (1.0 - progress * 0.5); canvas.drawCircle(Offset(x, y), particleSize, paint); } } @override bool shouldRepaint(ParticleEffectPainter oldDelegate) { return oldDelegate.progress != progress; } }