import 'package:flutter/material.dart'; import 'package:flutter_riverpod/flutter_riverpod.dart'; import 'package:go_router/go_router.dart'; import '../../auth/application/auth_notifier.dart'; import '../../profile/application/profile_notifier.dart'; import '../../profile/domain/user_profile.dart'; import '../application/teams_notifier.dart'; import '../domain/join_request.dart'; import '../domain/team.dart'; import '../infrastructure/teams_repository.dart'; import 'widgets/player_tile.dart'; import 'widgets/team_record_badge.dart'; /// Full-screen view of a single team: header (logo + record), summary stats, /// and roster. class TeamDetailScreen extends ConsumerWidget { const TeamDetailScreen({super.key, required this.teamId}); final String teamId; /// Web reads better when long content is centered in a column; ~760px is /// the same max we use across other detail screens. static const double _maxContentWidth = 760; @override Widget build(BuildContext context, WidgetRef ref) { final team = ref.watch(teamByIdProvider(teamId)); if (team == null) { return Scaffold( appBar: AppBar( title: const Text('Team'), leading: IconButton( icon: const Icon(Icons.arrow_back), onPressed: () => context.go('/teams'), ), ), body: const Center(child: Text('Team not found.')), ); } return Scaffold( appBar: AppBar( leading: IconButton( icon: const Icon(Icons.arrow_back), onPressed: () => context.go('/teams'), ), title: Text(team.name), ), body: Center( child: ConstrainedBox( constraints: const BoxConstraints(maxWidth: _maxContentWidth), child: _TeamDetailBody(team: team), ), ), ); } } class _TeamDetailBody extends StatelessWidget { const _TeamDetailBody({required this.team}); final Team team; @override Widget build(BuildContext context) { return CustomScrollView( slivers: [ SliverToBoxAdapter(child: _TeamHeader(team: team)), SliverToBoxAdapter(child: _JoinTeamSection(team: team)), SliverToBoxAdapter(child: _StatsRow(team: team)), SliverToBoxAdapter(child: _ContactSection(team: team)), const SliverToBoxAdapter(child: _SectionDivider(title: 'Roster')), if (team.players.isEmpty) const SliverToBoxAdapter(child: _EmptyRoster()) else SliverList.separated( itemCount: team.players.length, separatorBuilder: (_, _) => const Divider(height: 1), itemBuilder: (context, index) => PlayerTile(player: team.players[index]), ), const SliverToBoxAdapter(child: SizedBox(height: 24)), ], ); } } /// Renders one of four states for a logged-in player viewing a team: /// * already on this team → 'YOUR TEAM' chip /// * already on another team → no CTA /// * pending request out → disabled 'Request pending' button /// * no request yet → primary 'Request to join' OutlinedButton /// /// Returns SizedBox.shrink for managers, admins, viewers, or while role is /// resolving — they have no use for the action. class _JoinTeamSection extends ConsumerWidget { const _JoinTeamSection({required this.team}); final Team team; @override Widget build(BuildContext context, WidgetRef ref) { final user = ref.watch(authNotifierProvider).valueOrNull; final role = ref.watch(currentUserRoleProvider); final profile = ref.watch(currentProfileProvider).valueOrNull; if (user == null || role != UserRole.player || profile == null) { return const SizedBox.shrink(); } // Player is already on this team. if (profile.teamId == team.id) { return Padding( padding: const EdgeInsets.fromLTRB(16, 4, 16, 4), child: Align( alignment: Alignment.center, child: Container( padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 6), decoration: BoxDecoration( color: Colors.green.withValues(alpha: 0.18), borderRadius: BorderRadius.circular(20), ), child: Text( 'YOUR TEAM', style: Theme.of(context).textTheme.labelMedium?.copyWith( color: Colors.green.shade300, fontWeight: FontWeight.w800, letterSpacing: 1.2, ), ), ), ), ); } // Player is on a different team — no join CTA shown. if (profile.hasTeam) { return const SizedBox.shrink(); } final requestsAsync = ref.watch( joinRequestsForPlayerProvider(user.uid), ); return requestsAsync.when( loading: () => const SizedBox(height: 0), error: (_, _) => const SizedBox.shrink(), data: (requests) { final hasPendingForThisTeam = requests.any( (r) => r.teamId == team.id && r.status == JoinRequestStatus.pending, ); return Padding( padding: const EdgeInsets.fromLTRB(20, 4, 20, 8), child: hasPendingForThisTeam ? OutlinedButton.icon( onPressed: null, icon: const Icon(Icons.hourglass_bottom, size: 18), label: const Text('REQUEST PENDING'), ) : OutlinedButton.icon( onPressed: () => _submit(context, ref, profile), icon: const Icon(Icons.person_add_alt_1, size: 18), label: const Text('REQUEST TO JOIN'), ), ); }, ); } Future _submit( BuildContext context, WidgetRef ref, UserProfile profile, ) async { final messenger = ScaffoldMessenger.of(context); try { await ref.read(teamsRepositoryProvider).submitJoinRequest( teamId: team.id, teamName: team.name, playerId: profile.uid, playerName: profile.displayName.isEmpty ? profile.email : profile.displayName, playerEmail: profile.email, ); messenger.showSnackBar( const SnackBar(content: Text('Request sent!')), ); } catch (e) { messenger.showSnackBar( SnackBar(content: Text('Could not send request: $e')), ); } } } class _TeamHeader extends StatelessWidget { const _TeamHeader({required this.team}); final Team team; @override Widget build(BuildContext context) { final theme = Theme.of(context); final scheme = theme.colorScheme; final initial = team.name.isEmpty ? '?' : team.name.characters.first; final hasLogo = team.logoUrl != null && team.logoUrl!.isNotEmpty; return Padding( padding: const EdgeInsets.fromLTRB(20, 20, 20, 12), child: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ Container( width: 96, height: 96, alignment: Alignment.center, decoration: BoxDecoration( color: scheme.primaryContainer, shape: BoxShape.circle, boxShadow: [ BoxShadow( color: scheme.primary.withValues(alpha: 0.25), blurRadius: 24, spreadRadius: 2, ), ], ), child: hasLogo ? CircleAvatar( radius: 48, backgroundColor: scheme.primaryContainer, backgroundImage: NetworkImage(team.logoUrl!), ) : Text( initial.toUpperCase(), style: TextStyle( color: scheme.onPrimaryContainer, fontWeight: FontWeight.w800, fontSize: 44, ), ), ), const SizedBox(height: 16), Text( team.name, textAlign: TextAlign.center, style: theme.textTheme.headlineSmall?.copyWith( fontWeight: FontWeight.w800, ), ), if (team.description != null && team.description!.isNotEmpty) ...[ const SizedBox(height: 8), Text( team.description!, textAlign: TextAlign.center, style: theme.textTheme.bodyMedium?.copyWith( color: scheme.onSurfaceVariant, ), ), ], const SizedBox(height: 16), TeamRecordBadge( wins: team.wins, draws: team.draws, losses: team.losses, ), ], ), ); } } class _StatsRow extends StatelessWidget { const _StatsRow({required this.team}); final Team team; @override Widget build(BuildContext context) { final theme = Theme.of(context); final scheme = theme.colorScheme; final winPct = (team.winPercentage * 100).round(); final topScorer = team.topScorer; return Padding( padding: const EdgeInsets.fromLTRB(16, 8, 16, 8), child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 14), decoration: BoxDecoration( color: scheme.surfaceContainerHighest, borderRadius: BorderRadius.circular(14), border: Border.all(color: scheme.outlineVariant), ), child: Row( children: [ Expanded( child: _StatColumn(label: 'Games', value: '${team.totalGames}'), ), _VerticalDivider(color: scheme.outlineVariant), Expanded( child: _StatColumn(label: 'Win %', value: '$winPct%'), ), _VerticalDivider(color: scheme.outlineVariant), Expanded( child: _StatColumn( label: 'Top scorer', value: topScorer == null ? '—' : '${topScorer.name.split(' ').first} ' '(${topScorer.goalsScored})', small: true, ), ), ], ), ), ); } } class _ContactSection extends StatelessWidget { const _ContactSection({required this.team}); final Team team; @override Widget build(BuildContext context) { final hasEmail = team.managerEmail.isNotEmpty; final hasPhone = team.managerPhone != null && team.managerPhone!.isNotEmpty; if (!hasEmail && !hasPhone) return const SizedBox.shrink(); final theme = Theme.of(context); final scheme = theme.colorScheme; return Padding( padding: const EdgeInsets.fromLTRB(16, 8, 16, 8), child: Container( padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 12), decoration: BoxDecoration( color: scheme.surfaceContainerHighest, borderRadius: BorderRadius.circular(14), border: Border.all(color: scheme.outlineVariant), ), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( 'CONTACT', style: theme.textTheme.labelSmall?.copyWith( color: scheme.onSurfaceVariant, letterSpacing: 0.8, fontWeight: FontWeight.w700, ), ), const SizedBox(height: 8), if (hasEmail) _ContactRow(icon: Icons.mail_outline, label: team.managerEmail), if (hasEmail && hasPhone) const SizedBox(height: 6), if (hasPhone) _ContactRow( icon: Icons.phone_outlined, label: team.managerPhone!, ), ], ), ), ); } } class _ContactRow extends StatelessWidget { const _ContactRow({required this.icon, required this.label}); final IconData icon; final String label; @override Widget build(BuildContext context) { final theme = Theme.of(context); final scheme = theme.colorScheme; return Row( children: [ Icon(icon, size: 18, color: scheme.primary), const SizedBox(width: 10), Expanded( child: Text( label, style: theme.textTheme.bodyMedium?.copyWith( color: scheme.onSurface, ), overflow: TextOverflow.ellipsis, ), ), ], ); } } class _StatColumn extends StatelessWidget { const _StatColumn({ required this.label, required this.value, this.small = false, }); final String label; final String value; final bool small; @override Widget build(BuildContext context) { final theme = Theme.of(context); final scheme = theme.colorScheme; return Column( mainAxisSize: MainAxisSize.min, children: [ Text( value, maxLines: 1, overflow: TextOverflow.ellipsis, textAlign: TextAlign.center, style: (small ? theme.textTheme.titleMedium : theme.textTheme.headlineSmall) ?.copyWith( fontWeight: FontWeight.w800, color: scheme.onSurface, ), ), const SizedBox(height: 2), Text( label, style: theme.textTheme.labelSmall?.copyWith( color: scheme.onSurfaceVariant, letterSpacing: 0.4, ), ), ], ); } } class _VerticalDivider extends StatelessWidget { const _VerticalDivider({required this.color}); final Color color; @override Widget build(BuildContext context) { return Container( width: 1, height: 36, margin: const EdgeInsets.symmetric(horizontal: 8), color: color, ); } } class _SectionDivider extends StatelessWidget { const _SectionDivider({required this.title}); final String title; @override Widget build(BuildContext context) { final theme = Theme.of(context); return Padding( padding: const EdgeInsets.fromLTRB(20, 16, 20, 8), child: Row( children: [ Text( title, style: theme.textTheme.titleMedium?.copyWith( fontWeight: FontWeight.w700, ), ), const SizedBox(width: 12), Expanded(child: Divider(color: theme.colorScheme.outlineVariant)), ], ), ); } } class _EmptyRoster extends StatelessWidget { const _EmptyRoster(); @override Widget build(BuildContext context) { final theme = Theme.of(context); return Padding( padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 24), child: Text( 'No players on the roster yet.', style: theme.textTheme.bodyMedium?.copyWith( color: theme.colorScheme.onSurfaceVariant, ), ), ); } }