feat: add complete review/rating system for events
New feature: Users can view, submit, and interact with event reviews. Components added: - ReviewModel, ReviewStatsModel, ReviewListResponse (models) - ReviewService with getReviews, submitReview, markHelpful, flagReview - StarRatingInput (interactive 5-star picker with labels) - StarDisplay (read-only fractional star display) - ReviewSummary (average rating + distribution bars) - ReviewForm (star picker + comment field + submit/update) - ReviewCard (avatar, timestamp, expandable comment, helpful/flag) - ReviewSection (main container with pagination and state mgmt) Integration: - Added to LearnMoreScreen (both mobile and desktop layouts) - Review API endpoints point to app.eventifyplus.com Node.js backend - EventModel updated with averageRating/reviewCount fields Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
98
lib/features/reviews/widgets/review_summary.dart
Normal file
98
lib/features/reviews/widgets/review_summary.dart
Normal file
@@ -0,0 +1,98 @@
|
||||
// lib/features/reviews/widgets/review_summary.dart
|
||||
import 'package:flutter/material.dart';
|
||||
import '../models/review_models.dart';
|
||||
import 'star_display.dart';
|
||||
|
||||
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<int>(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: average rating + stars + count
|
||||
Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Text(
|
||||
stats.averageRating.toStringAsFixed(1),
|
||||
style: const TextStyle(
|
||||
fontSize: 40,
|
||||
fontWeight: FontWeight.bold,
|
||||
color: Color(0xFF1E293B),
|
||||
height: 1.1,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 4),
|
||||
StarDisplay(rating: stats.averageRating, size: 18),
|
||||
const SizedBox(height: 4),
|
||||
Text(
|
||||
'${stats.reviewCount} review${stats.reviewCount == 1 ? '' : 's'}',
|
||||
style: const TextStyle(fontSize: 12, color: Color(0xFF64748B)),
|
||||
),
|
||||
],
|
||||
),
|
||||
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>(Color(0xFFFBBF24)),
|
||||
minHeight: 8,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
SizedBox(
|
||||
width: 24,
|
||||
child: Text('$count', style: const TextStyle(fontSize: 11, color: Color(0xFF94A3B8))),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user