// lib/features/reviews/widgets/review_card.dart import 'package:flutter/material.dart'; import 'package:cached_network_image/cached_network_image.dart'; import '../models/review_models.dart'; import 'star_display.dart'; class ReviewCard extends StatefulWidget { final ReviewModel review; final String? currentUsername; final Future Function(int reviewId) onHelpful; final Future Function(int reviewId) onFlag; const ReviewCard({ Key? key, required this.review, this.currentUsername, required this.onHelpful, required this.onFlag, }) : super(key: key); @override State createState() => _ReviewCardState(); } class _ReviewCardState extends State { late ReviewModel _review; bool _expanded = false; @override void initState() { super.initState(); _review = widget.review; } @override void didUpdateWidget(covariant ReviewCard oldWidget) { super.didUpdateWidget(oldWidget); if (oldWidget.review.id != widget.review.id) _review = widget.review; } bool get _isOwnReview => widget.currentUsername != null && widget.currentUsername!.isNotEmpty && _review.username == widget.currentUsername; String _timeAgo(DateTime dt) { final diff = DateTime.now().difference(dt); if (diff.inSeconds < 60) return 'Just now'; if (diff.inMinutes < 60) return '${diff.inMinutes}m ago'; if (diff.inHours < 24) return '${diff.inHours}h ago'; if (diff.inDays < 30) return '${diff.inDays}d ago'; if (diff.inDays < 365) return '${(diff.inDays / 30).floor()}mo ago'; return '${(diff.inDays / 365).floor()}y ago'; } Color _avatarColor(String name) { final colors = [ const Color(0xFF0F45CF), const Color(0xFF7C3AED), const Color(0xFFEC4899), const Color(0xFFF59E0B), const Color(0xFF10B981), const Color(0xFFEF4444), const Color(0xFF06B6D4), const Color(0xFF8B5CF6), ]; return colors[name.hashCode.abs() % colors.length]; } Future _handleHelpful() async { if (_isOwnReview) return; try { final newCount = await widget.onHelpful(_review.id); if (mounted) { setState(() { _review = _review.copyWith( helpfulCount: newCount, userMarkedHelpful: !_review.userMarkedHelpful, ); }); } } catch (_) {} } Future _handleFlag() async { if (_isOwnReview || _review.userFlagged) return; final confirmed = await showDialog( context: context, builder: (ctx) => AlertDialog( title: const Text('Report Review'), content: const Text('Are you sure you want to report this review as inappropriate?'), actions: [ TextButton(onPressed: () => Navigator.pop(ctx, false), child: const Text('Cancel')), TextButton( onPressed: () => Navigator.pop(ctx, true), child: const Text('Report', style: TextStyle(color: Color(0xFFEF4444))), ), ], ), ); if (confirmed != true) return; try { await widget.onFlag(_review.id); if (mounted) setState(() => _review = _review.copyWith(userFlagged: true)); } catch (_) {} } @override Widget build(BuildContext context) { final comment = _review.comment ?? ''; final isLong = comment.length > 150; final displayComment = isLong && !_expanded ? '${comment.substring(0, 150)}...' : comment; final initial = _review.username.isNotEmpty ? _review.username[0].toUpperCase() : '?'; return Container( padding: const EdgeInsets.all(14), decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(12), border: Border.all(color: const Color(0xFFF1F5F9)), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ // Header row Row( children: [ ClipOval( child: CachedNetworkImage( imageUrl: 'https://api.dicebear.com/9.x/notionists/svg?seed=${Uri.encodeComponent(_review.username)}', width: 36, height: 36, memCacheWidth: 72, memCacheHeight: 72, maxWidthDiskCache: 144, maxHeightDiskCache: 144, placeholder: (_, __) => CircleAvatar( radius: 18, backgroundColor: _avatarColor(_review.username), child: Text(initial, style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 15)), ), errorWidget: (_, __, ___) => CircleAvatar( radius: 18, backgroundColor: _avatarColor(_review.username), child: Text(initial, style: const TextStyle(color: Colors.white, fontWeight: FontWeight.bold, fontSize: 15)), ), ), ), const SizedBox(width: 10), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( children: [ Flexible( child: Text( _review.username.split('@').first, style: const TextStyle(fontWeight: FontWeight.w600, fontSize: 14, color: Color(0xFF1E293B)), overflow: TextOverflow.ellipsis, ), ), if (_review.isVerified) ...[ const SizedBox(width: 4), const Icon(Icons.verified, size: 14, color: Color(0xFF22C55E)), ], ], ), const SizedBox(height: 2), Text(_timeAgo(_review.createdAt), style: const TextStyle(fontSize: 11, color: Color(0xFF94A3B8))), ], ), ), StarDisplay(rating: _review.rating.toDouble(), size: 14), ], ), // Comment if (comment.isNotEmpty) ...[ const SizedBox(height: 10), Text(displayComment, style: const TextStyle(fontSize: 13, color: Color(0xFF334155), height: 1.4)), if (isLong) GestureDetector( onTap: () => setState(() => _expanded = !_expanded), child: Padding( padding: const EdgeInsets.only(top: 2), child: Text( _expanded ? 'Show less' : 'Read more', style: const TextStyle(fontSize: 12, color: Color(0xFF0F45CF), fontWeight: FontWeight.w600), ), ), ), ], // Footer actions const SizedBox(height: 10), Row( children: [ // Helpful button InkWell( onTap: _isOwnReview ? null : _handleHelpful, borderRadius: BorderRadius.circular(8), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), child: Row( mainAxisSize: MainAxisSize.min, children: [ Icon( _review.userMarkedHelpful ? Icons.thumb_up : Icons.thumb_up_outlined, size: 15, color: _review.userMarkedHelpful ? const Color(0xFF0F45CF) : const Color(0xFF94A3B8), ), if (_review.helpfulCount > 0) ...[ const SizedBox(width: 4), Text( '${_review.helpfulCount}', style: TextStyle( fontSize: 12, color: _review.userMarkedHelpful ? const Color(0xFF0F45CF) : const Color(0xFF94A3B8), ), ), ], ], ), ), ), const SizedBox(width: 8), // Flag button if (!_isOwnReview) InkWell( onTap: _review.userFlagged ? null : _handleFlag, borderRadius: BorderRadius.circular(8), child: Padding( padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4), child: Icon( _review.userFlagged ? Icons.flag : Icons.flag_outlined, size: 15, color: _review.userFlagged ? const Color(0xFFEF4444) : const Color(0xFF94A3B8), ), ), ), ], ), ], ), ); } }