import 'package:flutter/material.dart'; import 'package:lba/res/colors.dart'; import '../models/hunt_card.dart'; class LeaderboardWidget extends StatefulWidget { final List entries; final int userPoints; final VoidCallback onClose; const LeaderboardWidget({ super.key, required this.entries, required this.userPoints, required this.onClose, }); @override State createState() => _LeaderboardWidgetState(); } class _LeaderboardWidgetState extends State with TickerProviderStateMixin { late AnimationController _slideController; late AnimationController _itemController; late Animation _slideAnimation; late List> _itemAnimations; @override void initState() { super.initState(); _slideController = AnimationController( duration: const Duration(milliseconds: 600), vsync: this, ); _itemController = AnimationController( duration: const Duration(milliseconds: 1200), vsync: this, ); _slideAnimation = Tween( begin: const Offset(0, 1), end: Offset.zero, ).animate(CurvedAnimation( parent: _slideController, curve: Curves.easeOutCubic, )); _itemAnimations = List.generate( widget.entries.length, (index) => Tween( begin: 0.0, end: 1.0, ).animate(CurvedAnimation( parent: _itemController, curve: Interval( (index * 0.1).clamp(0.0, 0.4), ((index * 0.1) + 0.6).clamp(0.1, 1.0), curve: Curves.easeOutBack, ), )), ); _slideController.forward(); _itemController.forward(); } @override void dispose() { _slideController.dispose(); _itemController.dispose(); super.dispose(); } Future _close() async { await _slideController.reverse(); widget.onClose(); } @override Widget build(BuildContext context) { return SlideTransition( position: _slideAnimation, child: Container( height: MediaQuery.of(context).size.height * 0.7, decoration: BoxDecoration( color: AppColors.surface, borderRadius: const BorderRadius.vertical(top: Radius.circular(24)), boxShadow: [ BoxShadow( color: AppColors.shadowColor, blurRadius: 20, offset: const Offset(0, -10), ), ], ), child: Column( children: [ // Handle bar Container( margin: const EdgeInsets.symmetric(vertical: 12), height: 4, width: 40, decoration: BoxDecoration( color: AppColors.divider, borderRadius: BorderRadius.circular(2), ), ), // Header Padding( padding: const EdgeInsets.fromLTRB(24, 8, 24, 16), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Text( 'Leaderboard', style: TextStyle( color: AppColors.textPrimary, fontSize: 24, fontWeight: FontWeight.bold, ), ), IconButton( onPressed: _close, icon: Icon( Icons.close_rounded, color: AppColors.textSecondary, ), ), ], ), ), // Leaderboard list Expanded( child: ListView.builder( padding: const EdgeInsets.symmetric(horizontal: 24), itemCount: widget.entries.length, itemBuilder: (context, index) { final entry = widget.entries[index]; return AnimatedBuilder( animation: _itemAnimations[index], builder: (context, child) { return Transform.translate( offset: Offset( 0, 50 * (1 - _itemAnimations[index].value), ), child: Opacity( opacity: _itemAnimations[index].value.clamp(0.0, 1.0), child: _buildLeaderboardItem(entry, index), ), ); }, ); }, ), ), ], ), ), ); } Widget _buildLeaderboardItem(LeaderboardEntry entry, int index) { final isCurrentUser = entry.isCurrentUser; final isTopThree = entry.rank <= 3; return Container( margin: const EdgeInsets.only(bottom: 12), padding: const EdgeInsets.all(16), decoration: BoxDecoration( color: isCurrentUser ? AppColors.primary.withOpacity(0.1) : AppColors.cardBackground, borderRadius: BorderRadius.circular(16), border: isCurrentUser ? Border.all(color: AppColors.primary, width: 2) : null, boxShadow: isTopThree ? [ BoxShadow( color: AppColors.shadowColor, blurRadius: 8, offset: const Offset(0, 4), ), ] : null, ), child: Row( children: [ // Rank Container( width: 40, height: 40, decoration: BoxDecoration( color: _getRankColor(entry.rank), shape: BoxShape.circle, ), child: Center( child: Text( '#${entry.rank}', style: TextStyle( color: entry.rank <= 3 ? Colors.white : AppColors.textPrimary, fontWeight: FontWeight.bold, fontSize: 14, ), ), ), ), const SizedBox(width: 16), // Avatar Container( width: 50, height: 50, decoration: BoxDecoration( color: AppColors.primary.withOpacity(0.1), shape: BoxShape.circle, ), child: Center( child: Text( entry.avatar, style: const TextStyle(fontSize: 24), ), ), ), const SizedBox(width: 16), // Name and points Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( entry.name, style: TextStyle( color: AppColors.textPrimary, fontSize: 16, fontWeight: isCurrentUser ? FontWeight.bold : FontWeight.w600, ), ), const SizedBox(height: 4), Row( children: [ Icon( Icons.stars_rounded, color: AppColors.confirmButton, size: 16, ), const SizedBox(width: 4), Text( '${entry.totalPoints} points', style: TextStyle( color: AppColors.textSecondary, fontSize: 14, fontWeight: FontWeight.w500, ), ), ], ), ], ), ), // Trophy for top 3 if (isTopThree) Container( padding: const EdgeInsets.all(8), decoration: BoxDecoration( color: _getRankColor(entry.rank).withOpacity(0.1), borderRadius: BorderRadius.circular(8), ), child: Icon( Icons.emoji_events_rounded, color: _getRankColor(entry.rank), size: 24, ), ), ], ), ); } Color _getRankColor(int rank) { switch (rank) { case 1: return const Color(0xFFFFD700); // Gold case 2: return const Color(0xFFC0C0C0); // Silver case 3: return const Color(0xFFCD7F32); // Bronze default: return AppColors.primary; } } }