proxybuy-flutter/lib/widgets/reviews.dart

596 lines
20 KiB
Dart

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<Reviews> createState() => _ReviewsState();
}
class _ReviewsState extends State<Reviews> 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<double> _likeScaleAnimation;
late Animation<double> _dislikeScaleAnimation;
late Animation<double> _particleAnimation;
late Animation<double> _pulseAnimation;
late Animation<double> _countAnimation;
late Animation<Color?> _likeColorAnimation;
late Animation<Color?> _dislikeColorAnimation;
late Animation<double> _likeIconRotationAnimation;
late Animation<double> _dislikeIconRotationAnimation;
late Animation<double> _likeIconScaleAnimation;
late Animation<double> _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<double>(begin: 1.0, end: 1.08).animate(
CurvedAnimation(parent: _likeScaleController, curve: Curves.easeOutBack),
);
_dislikeScaleAnimation = Tween<double>(begin: 1.0, end: 1.08).animate(
CurvedAnimation(
parent: _dislikeScaleController,
curve: Curves.easeOutBack,
),
);
_particleAnimation = Tween<double>(begin: 0.0, end: 1.0).animate(
CurvedAnimation(parent: _particleController, curve: Curves.easeOutCubic),
);
_pulseAnimation = Tween<double>(begin: 1.0, end: 1.15).animate(
CurvedAnimation(parent: _pulseController, curve: Curves.easeInOut),
);
_countAnimation = Tween<double>(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<double>(begin: 0.0, end: 0.1).animate(
CurvedAnimation(parent: _likeIconController, curve: Curves.elasticOut),
);
_dislikeIconRotationAnimation = Tween<double>(
begin: 0.0,
end: -0.1,
).animate(
CurvedAnimation(parent: _dislikeIconController, curve: Curves.elasticOut),
);
_likeIconScaleAnimation = Tween<double>(begin: 1.0, end: 1.3).animate(
CurvedAnimation(parent: _likeIconController, curve: Curves.elasticOut),
);
_dislikeIconScaleAnimation = Tween<double>(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;
}
}