// lib/features/reviews/widgets/review_summary.dart import 'dart:math' show pi; import 'package:flutter/material.dart'; import '../models/review_models.dart'; class _RatingRingPainter extends CustomPainter { final double rating; const _RatingRingPainter({required this.rating}); @override void paint(Canvas canvas, Size size) { final center = Offset(size.width / 2, size.height / 2); final radius = size.width / 2 - 6; // Background track canvas.drawArc( Rect.fromCircle(center: center, radius: radius), -pi / 2, 2 * pi, false, Paint() ..color = Colors.white12 ..style = PaintingStyle.stroke ..strokeWidth = 7 ..strokeCap = StrokeCap.round, ); // Filled arc if (rating > 0) { canvas.drawArc( Rect.fromCircle(center: center, radius: radius), -pi / 2, (rating.clamp(0.0, 5.0) / 5.0) * 2 * pi, false, Paint() ..color = const Color(0xFFFBBF24) ..style = PaintingStyle.stroke ..strokeWidth = 7 ..strokeCap = StrokeCap.round, ); } } @override bool shouldRepaint(_RatingRingPainter old) => old.rating != rating; } class _RatingRingWidget extends StatelessWidget { final double rating; final int reviewCount; const _RatingRingWidget({required this.rating, required this.reviewCount}); @override Widget build(BuildContext context) { return Column( mainAxisSize: MainAxisSize.min, children: [ SizedBox( width: 84, height: 84, child: CustomPaint( painter: _RatingRingPainter(rating: rating), child: Center( child: Column( mainAxisSize: MainAxisSize.min, children: [ Text( rating.toStringAsFixed(1), style: const TextStyle( fontSize: 22, fontWeight: FontWeight.w800, color: Colors.white, ), ), const Text( '/5', style: TextStyle(fontSize: 10, color: Color(0xFF94A3B8)), ), ], ), ), ), ), const SizedBox(height: 4), Text( '$reviewCount ${reviewCount == 1 ? 'review' : 'reviews'}', style: const TextStyle(fontSize: 11, color: Color(0xFF94A3B8)), ), ], ); } } class ReviewSummary extends StatelessWidget { final ReviewStatsModel stats; const ReviewSummary({Key? key, required this.stats}) : super(key: key); @override Widget build(BuildContext context) { final maxCount = stats.distribution.values.fold(0, (a, b) => a > b ? a : b); return Container( padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(16), boxShadow: [ BoxShadow(color: Colors.black.withValues(alpha: 0.06), blurRadius: 12, offset: const Offset(0, 4)), ], ), child: Row( crossAxisAlignment: CrossAxisAlignment.center, children: [ // Left: circular rating ring _RatingRingWidget( rating: stats.averageRating, reviewCount: stats.reviewCount, ), const SizedBox(width: 24), // Right: distribution bars Expanded( child: Column( mainAxisSize: MainAxisSize.min, children: List.generate(5, (i) { final star = 5 - i; final count = stats.distribution[star] ?? 0; final fraction = maxCount > 0 ? count / maxCount : 0.0; return Padding( padding: const EdgeInsets.symmetric(vertical: 2), child: Row( children: [ SizedBox( width: 18, child: Text('$star', style: const TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xFF64748B))), ), const Icon(Icons.star_rounded, size: 12, color: Color(0xFFFBBF24)), const SizedBox(width: 6), Expanded( child: ClipRRect( borderRadius: BorderRadius.circular(4), child: AnimatedContainer( duration: const Duration(milliseconds: 300), height: 8, child: LinearProgressIndicator( value: fraction, backgroundColor: const Color(0xFFF1F5F9), valueColor: const AlwaysStoppedAnimation(Color(0xFFFBBF24)), minHeight: 8, ), ), ), ), const SizedBox(width: 8), SizedBox( width: 24, child: Text('$count', style: const TextStyle(fontSize: 11, color: Color(0xFF94A3B8))), ), ], ), ); }), ), ), ], ), ); } }