import 'package:flutter/material.dart'; import 'package:go_router/go_router.dart'; import '../../../teams/domain/team.dart'; import '../../application/stats_notifier.dart'; /// Ranked row used in every leaderboard list on the Stats screen. /// /// Use either [LeaderboardTile.player] for the player leaderboards or /// [LeaderboardTile.team] for the league standings — both share the rank /// medal styling and tap affordances. class LeaderboardTile extends StatelessWidget { const LeaderboardTile._({ required this.rank, required this.title, required this.subtitle, required this.trailingValue, required this.trailingLabel, this.onTap, }); /// Player leaderboard variant. Tapping navigates to the player's team page. factory LeaderboardTile.player({ Key? key, required int rank, required PlayerWithTeam entry, required int statValue, required String statLabel, BuildContext? navContext, }) { return LeaderboardTile._( rank: rank, title: entry.player.name, subtitle: entry.team.name, trailingValue: statValue, trailingLabel: statLabel, onTap: navContext == null ? null : () => navContext.go('/teams/${entry.team.id}'), ); } /// Team standings variant. Tapping navigates to the team detail page. factory LeaderboardTile.team({ Key? key, required int rank, required Team team, BuildContext? navContext, }) { final points = team.wins * 3 + team.draws; return LeaderboardTile._( rank: rank, title: team.name, subtitle: '${team.wins}W · ${team.draws}D · ${team.losses}L', trailingValue: points, trailingLabel: 'pts', onTap: navContext == null ? null : () => navContext.go('/teams/${team.id}'), ); } final int rank; final String title; final String subtitle; final int trailingValue; final String trailingLabel; final VoidCallback? onTap; @override Widget build(BuildContext context) { final theme = Theme.of(context); final scheme = theme.colorScheme; return Card( clipBehavior: Clip.antiAlias, margin: const EdgeInsets.symmetric(horizontal: 12, vertical: 4), child: InkWell( onTap: onTap, child: Padding( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 10), child: Row( children: [ _RankMedal(rank: rank), const SizedBox(width: 12), Expanded( child: Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, children: [ Text( title, maxLines: 1, overflow: TextOverflow.ellipsis, style: theme.textTheme.titleSmall?.copyWith( fontWeight: FontWeight.w700, ), ), const SizedBox(height: 2), Text( subtitle, maxLines: 1, overflow: TextOverflow.ellipsis, style: theme.textTheme.bodySmall?.copyWith( color: scheme.onSurfaceVariant, ), ), ], ), ), const SizedBox(width: 12), _TrailingStat(value: trailingValue, label: trailingLabel), ], ), ), ), ); } } class _RankMedal extends StatelessWidget { const _RankMedal({required this.rank}); final int rank; // Podium medal colors — only hardcoded colors in the feature. static const _gold = Color(0xFFFFD700); static const _silver = Color(0xFFC0C0C0); static const _bronze = Color(0xFFCD7F32); @override Widget build(BuildContext context) { final scheme = Theme.of(context).colorScheme; final isPodium = rank >= 1 && rank <= 3; final medalColor = switch (rank) { 1 => _gold, 2 => _silver, 3 => _bronze, _ => scheme.surfaceContainerHighest, }; final textColor = isPodium ? Colors.black : scheme.onSurfaceVariant; return Container( width: 34, height: 34, alignment: Alignment.center, decoration: BoxDecoration( color: medalColor, shape: BoxShape.circle, border: isPodium ? null : Border.all(color: scheme.outlineVariant, width: 1), ), child: Text( '$rank', style: TextStyle( color: textColor, fontWeight: FontWeight.w800, fontSize: 13, ), ), ); } } class _TrailingStat extends StatelessWidget { const _TrailingStat({required this.value, required this.label}); final int value; final String label; @override Widget build(BuildContext context) { final theme = Theme.of(context); final scheme = theme.colorScheme; return Column( crossAxisAlignment: CrossAxisAlignment.end, mainAxisSize: MainAxisSize.min, children: [ Text( '$value', style: theme.textTheme.titleMedium?.copyWith( color: scheme.primary, fontWeight: FontWeight.w800, ), ), Text( label, style: theme.textTheme.labelSmall?.copyWith( color: scheme.onSurfaceVariant, letterSpacing: 0.4, ), ), ], ); } }