Files

172 lines
5.3 KiB
Dart
Raw Permalink Normal View History

// lib/features/reviews/widgets/review_summary.dart
feat: Phase 3 — 26 medium-priority gaps implemented P3-A/K Profile: Eventify ID glassmorphic badge (tap-to-copy), DiceBear Notionists avatar via TierAvatarRing, district picker (14 pills) with 183-day cooldown lock, multipart photo upload to server P3-B Home: Top Events converted to PageView scroll-snap (viewportFraction 0.9 + PageScrollPhysics) P3-C Event detail: contributor widget (tier ring + name + navigation), related events horizontal row; added getEventsByCategory() to EventsService; added contributorId/Name/Tier fields to EventModel P3-D Kerala pincodes: 463-entry JSON (all 14 districts), registered as asset, async-loaded in SearchScreen replacing hardcoded 32 cities P3-E Checkout: promo code field + Apply/Remove button in Step 2, discountAmount subtracted from total, applyPromo()/resetPromo() methods in CheckoutProvider P3-F/G Gamification: reward cycle countdown + EP→RP progress bar (blue→ amber) in contribute + profile screens; TierAvatarRing in podium and all leaderboard rows; GlassCard current-user stats card at top of leaderboard tab P3-H New ContributorProfileScreen: tier ring, stats, submission grid with status chips; getDashboardForUser() in GamificationService; wired from leaderboard row taps P3-I Achievements: 11 default badges (up from 6), 6 new icon map entries; progress % labels already confirmed present P3-J Reviews: CustomPainter circular arc rating ring (amber, 84px) replaces large rating number in ReviewSummary P3-L Share rank card: RepaintBoundary → PNG capture → Share.shareXFiles; share button wired in profile header and leaderboard tab P3-M SafeArea audit: home bottom nav, contribute/achievements scroll padding, profile CustomScrollView top inset New files: tier_avatar_ring.dart, glass_card.dart, eventify_bottom_sheet.dart, contributor_profile_screen.dart, share_rank_card.dart, assets/data/kerala_pincodes.json New dep: path_provider ^2.1.0
2026-04-04 17:17:36 +05:30
import 'dart:math' show pi;
import 'package:flutter/material.dart';
import '../models/review_models.dart';
feat: Phase 3 — 26 medium-priority gaps implemented P3-A/K Profile: Eventify ID glassmorphic badge (tap-to-copy), DiceBear Notionists avatar via TierAvatarRing, district picker (14 pills) with 183-day cooldown lock, multipart photo upload to server P3-B Home: Top Events converted to PageView scroll-snap (viewportFraction 0.9 + PageScrollPhysics) P3-C Event detail: contributor widget (tier ring + name + navigation), related events horizontal row; added getEventsByCategory() to EventsService; added contributorId/Name/Tier fields to EventModel P3-D Kerala pincodes: 463-entry JSON (all 14 districts), registered as asset, async-loaded in SearchScreen replacing hardcoded 32 cities P3-E Checkout: promo code field + Apply/Remove button in Step 2, discountAmount subtracted from total, applyPromo()/resetPromo() methods in CheckoutProvider P3-F/G Gamification: reward cycle countdown + EP→RP progress bar (blue→ amber) in contribute + profile screens; TierAvatarRing in podium and all leaderboard rows; GlassCard current-user stats card at top of leaderboard tab P3-H New ContributorProfileScreen: tier ring, stats, submission grid with status chips; getDashboardForUser() in GamificationService; wired from leaderboard row taps P3-I Achievements: 11 default badges (up from 6), 6 new icon map entries; progress % labels already confirmed present P3-J Reviews: CustomPainter circular arc rating ring (amber, 84px) replaces large rating number in ReviewSummary P3-L Share rank card: RepaintBoundary → PNG capture → Share.shareXFiles; share button wired in profile header and leaderboard tab P3-M SafeArea audit: home bottom nav, contribute/achievements scroll padding, profile CustomScrollView top inset New files: tier_avatar_ring.dart, glass_card.dart, eventify_bottom_sheet.dart, contributor_profile_screen.dart, share_rank_card.dart, assets/data/kerala_pincodes.json New dep: path_provider ^2.1.0
2026-04-04 17:17:36 +05:30
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<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: [
feat: Phase 3 — 26 medium-priority gaps implemented P3-A/K Profile: Eventify ID glassmorphic badge (tap-to-copy), DiceBear Notionists avatar via TierAvatarRing, district picker (14 pills) with 183-day cooldown lock, multipart photo upload to server P3-B Home: Top Events converted to PageView scroll-snap (viewportFraction 0.9 + PageScrollPhysics) P3-C Event detail: contributor widget (tier ring + name + navigation), related events horizontal row; added getEventsByCategory() to EventsService; added contributorId/Name/Tier fields to EventModel P3-D Kerala pincodes: 463-entry JSON (all 14 districts), registered as asset, async-loaded in SearchScreen replacing hardcoded 32 cities P3-E Checkout: promo code field + Apply/Remove button in Step 2, discountAmount subtracted from total, applyPromo()/resetPromo() methods in CheckoutProvider P3-F/G Gamification: reward cycle countdown + EP→RP progress bar (blue→ amber) in contribute + profile screens; TierAvatarRing in podium and all leaderboard rows; GlassCard current-user stats card at top of leaderboard tab P3-H New ContributorProfileScreen: tier ring, stats, submission grid with status chips; getDashboardForUser() in GamificationService; wired from leaderboard row taps P3-I Achievements: 11 default badges (up from 6), 6 new icon map entries; progress % labels already confirmed present P3-J Reviews: CustomPainter circular arc rating ring (amber, 84px) replaces large rating number in ReviewSummary P3-L Share rank card: RepaintBoundary → PNG capture → Share.shareXFiles; share button wired in profile header and leaderboard tab P3-M SafeArea audit: home bottom nav, contribute/achievements scroll padding, profile CustomScrollView top inset New files: tier_avatar_ring.dart, glass_card.dart, eventify_bottom_sheet.dart, contributor_profile_screen.dart, share_rank_card.dart, assets/data/kerala_pincodes.json New dep: path_provider ^2.1.0
2026-04-04 17:17:36 +05:30
// 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>(Color(0xFFFBBF24)),
minHeight: 8,
),
),
),
),
const SizedBox(width: 8),
SizedBox(
width: 24,
child: Text('$count', style: const TextStyle(fontSize: 11, color: Color(0xFF94A3B8))),
),
],
),
);
}),
),
),
],
),
);
}
}