MotorSignal-App/lib/features/social/clubs/widgets/club_card.dart

299 lines
10 KiB
Dart

import 'package:flutter/material.dart';
import '../../../../core/theme/app_typography.dart';
import '../../../../core/theme/theme_extensions.dart';
import '../../../../services/clubs/club_models.dart';
import 'club_monogram.dart';
/// Automotive styled card representing a MotorSignal Club in list views,
/// discovery grids, and membership directories.
class ClubCard extends StatelessWidget {
final Club club;
final ClubRole? role;
final VoidCallback? onTap;
final bool inGrid;
const ClubCard({
super.key,
required this.club,
this.role,
this.onTap,
this.inGrid = false,
});
@override
Widget build(BuildContext context) {
final colors = context.motorColors;
final theme = Theme.of(context);
return Container(
margin: inGrid ? EdgeInsets.zero : const EdgeInsets.symmetric(vertical: 6.0),
decoration: BoxDecoration(
color: colors.surfaceContainer,
borderRadius: BorderRadius.circular(14),
border: Border.all(color: colors.outlineVariant, width: 0.8),
),
child: Material(
color: Colors.transparent,
child: InkWell(
borderRadius: BorderRadius.circular(14),
onTap: onTap,
child: Padding(
padding: const EdgeInsets.all(14.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header row: Monogram + Name/Slug + Optional Role Badge
Row(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
ClubMonogram(name: club.name, size: 44),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
club.name,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontFamily: AppTypography.fontHeadline,
fontSize: 15,
fontWeight: FontWeight.w700,
),
),
),
if (role != null && (role == ClubRole.owner || role == ClubRole.admin)) ...[
const SizedBox(width: 8),
_buildRolePill(context, role!, colors),
],
],
),
const SizedBox(height: 2),
Text(
'@${club.slug}',
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontFamily: AppTypography.fontTelemetry,
fontSize: 12,
fontWeight: FontWeight.w600,
color: colors.cyberCyan,
),
),
],
),
),
const SizedBox(width: 8),
Icon(
Icons.chevron_right,
color: theme.colorScheme.onSurfaceVariant.withValues(alpha: 0.6),
size: 20,
),
],
),
// Optional description preview if present
if (club.description.isNotEmpty) ...[
const SizedBox(height: 10),
Text(
club.description,
maxLines: 2,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontFamily: AppTypography.fontBody,
fontSize: 12.5,
color: theme.colorScheme.onSurfaceVariant,
height: 1.3,
),
),
],
const SizedBox(height: 12),
// Badges row: Member count, admission policy, region, focus
Wrap(
spacing: 8,
runSpacing: 6,
crossAxisAlignment: WrapCrossAlignment.center,
children: [
// Member count badge
_buildPill(
icon: Icons.people_outline,
label: '${club.memberCount} ${club.memberCount == 1 ? 'member' : 'members'}',
colors: colors,
theme: theme,
),
// Admission policy badge
if (role == null)
_buildAdmissionBadge(context, club.admissionPolicy, colors),
// Region badge if available
if (club.homeRegion != null && club.homeRegion!.trim().isNotEmpty)
_buildPill(
icon: Icons.location_on_outlined,
label: club.homeRegion!.trim(),
colors: colors,
theme: theme,
),
// Motorcycle focus badge if available
if (club.motorcycleFocus != null && club.motorcycleFocus!.trim().isNotEmpty)
_buildPill(
icon: Icons.two_wheeler_outlined,
label: club.motorcycleFocus!.trim(),
colors: colors,
theme: theme,
),
// Non-public visibility badge
if (club.visibility != ClubVisibility.public)
_buildPill(
icon: club.visibility == ClubVisibility.private
? Icons.lock_outline
: Icons.visibility_off_outlined,
label: club.visibility.label,
colors: colors,
theme: theme,
tintColor: colors.warningAmber,
),
],
),
],
),
),
),
),
);
}
Widget _buildRolePill(BuildContext context, ClubRole role, MotorSignalThemeColors colors) {
Color badgeColor;
Color textColor = Colors.black;
switch (role) {
case ClubRole.owner:
badgeColor = colors.warningAmber;
break;
case ClubRole.admin:
badgeColor = colors.cyberCyan;
break;
case ClubRole.member:
badgeColor = colors.outlineVariant;
textColor = Theme.of(context).colorScheme.onSurface;
break;
}
return Container(
padding: const EdgeInsets.symmetric(horizontal: 7, vertical: 2),
decoration: BoxDecoration(
color: badgeColor,
borderRadius: BorderRadius.circular(6),
),
child: Text(
role.label.toUpperCase(),
style: TextStyle(
fontFamily: AppTypography.fontTelemetry,
fontSize: 9.5,
fontWeight: FontWeight.w800,
letterSpacing: 0.4,
color: textColor,
),
),
);
}
Widget _buildAdmissionBadge(
BuildContext context,
ClubAdmissionPolicy policy,
MotorSignalThemeColors colors,
) {
Color color;
IconData icon;
switch (policy) {
case ClubAdmissionPolicy.open:
color = colors.launchGreen;
icon = Icons.group_add_outlined;
break;
case ClubAdmissionPolicy.request:
color = colors.cyberCyan;
icon = Icons.how_to_reg_outlined;
break;
case ClubAdmissionPolicy.inviteOnly:
color = colors.outline;
icon = Icons.mail_outline;
break;
}
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: color.withValues(alpha: 0.12),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: color.withValues(alpha: 0.35), width: 0.8),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, size: 12, color: color),
const SizedBox(width: 4),
Text(
policy.label,
style: TextStyle(
fontFamily: AppTypography.fontHeadline,
fontSize: 11,
fontWeight: FontWeight.w600,
color: color,
),
),
],
),
);
}
Widget _buildPill({
required IconData icon,
required String label,
required MotorSignalThemeColors colors,
required ThemeData theme,
Color? tintColor,
}) {
final effectiveColor = tintColor ?? theme.colorScheme.onSurfaceVariant;
return Container(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
decoration: BoxDecoration(
color: colors.surfaceContainerHighest.withValues(alpha: 0.5),
borderRadius: BorderRadius.circular(8),
border: Border.all(color: colors.outlineVariant.withValues(alpha: 0.5), width: 0.6),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Icon(icon, size: 12, color: effectiveColor),
const SizedBox(width: 4),
ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 160),
child: Text(
label,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontFamily: AppTypography.fontHeadline,
fontSize: 11,
fontWeight: FontWeight.w500,
color: effectiveColor,
),
),
),
],
),
);
}
}