434 lines
14 KiB
Dart
434 lines
14 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import '../../../core/responsive/responsive_layout.dart';
|
|
import '../../../core/theme/app_typography.dart';
|
|
import '../../../core/theme/theme_extensions.dart';
|
|
import '../../../services/clubs/club_models.dart';
|
|
import '../../../services/clubs/club_providers.dart';
|
|
import 'widgets/club_monogram.dart';
|
|
|
|
/// Read-only preview destination for a MotorSignal Club.
|
|
/// Displays authoritative identity and metadata without CLUB-2C membership mutations.
|
|
class ClubPreviewScreen extends ConsumerWidget {
|
|
final String clubId;
|
|
|
|
const ClubPreviewScreen({
|
|
super.key,
|
|
required this.clubId,
|
|
});
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
final colors = context.motorColors;
|
|
final theme = Theme.of(context);
|
|
final clubAsync = ref.watch(clubDetailProvider(clubId));
|
|
final memberAsync = ref.watch(currentMemberProvider(clubId));
|
|
|
|
return Scaffold(
|
|
appBar: AppBar(
|
|
title: const Text(
|
|
'Club',
|
|
style: TextStyle(
|
|
fontFamily: AppTypography.fontHeadline,
|
|
fontWeight: FontWeight.w700,
|
|
),
|
|
),
|
|
),
|
|
body: SafeArea(
|
|
child: clubAsync.when(
|
|
data: (club) {
|
|
if (club == null) {
|
|
return Center(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(24.0),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Icon(Icons.shield_outlined, size: 48, color: colors.warningAmber),
|
|
const SizedBox(height: 16),
|
|
const Text(
|
|
'Club Not Found',
|
|
style: TextStyle(
|
|
fontFamily: AppTypography.fontHeadline,
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.w700,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
'This club may have been archived or does not exist.',
|
|
textAlign: TextAlign.center,
|
|
style: TextStyle(
|
|
fontFamily: AppTypography.fontBody,
|
|
fontSize: 13,
|
|
color: theme.colorScheme.onSurfaceVariant,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
if (club.isArchived) {
|
|
return Center(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(24.0),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Icon(Icons.archive_outlined, size: 48, color: colors.warningAmber),
|
|
const SizedBox(height: 16),
|
|
const Text(
|
|
'Club Archived',
|
|
style: TextStyle(
|
|
fontFamily: AppTypography.fontHeadline,
|
|
fontSize: 18,
|
|
fontWeight: FontWeight.w700,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
'This club has been archived by leadership and is no longer active.',
|
|
textAlign: TextAlign.center,
|
|
style: TextStyle(
|
|
fontFamily: AppTypography.fontBody,
|
|
fontSize: 13,
|
|
color: theme.colorScheme.onSurfaceVariant,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
final myMembership = memberAsync.value;
|
|
|
|
return ResponsiveLayout(
|
|
builder: (context, responsive) {
|
|
return Center(
|
|
child: ConstrainedBox(
|
|
constraints: const BoxConstraints(maxWidth: 750),
|
|
child: SingleChildScrollView(
|
|
padding: const EdgeInsets.all(18.0),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.stretch,
|
|
children: [
|
|
// Club Hero Identity Card
|
|
_buildHeroCard(context, club, myMembership, colors, theme),
|
|
const SizedBox(height: 18),
|
|
|
|
// Description Card
|
|
_buildDescriptionCard(context, club, colors, theme),
|
|
const SizedBox(height: 18),
|
|
|
|
// Metadata Card (Region, Focus, Dates)
|
|
_buildMetadataCard(context, club, colors, theme),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
},
|
|
);
|
|
},
|
|
loading: () => Center(
|
|
child: CircularProgressIndicator(
|
|
valueColor: AlwaysStoppedAnimation<Color>(colors.cyberCyan),
|
|
),
|
|
),
|
|
error: (err, _) => Center(
|
|
child: Padding(
|
|
padding: const EdgeInsets.all(24.0),
|
|
child: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Icon(Icons.shield_outlined, size: 36, color: colors.warningAmber),
|
|
const SizedBox(height: 12),
|
|
const Text(
|
|
'Club Unavailable',
|
|
style: TextStyle(
|
|
fontFamily: AppTypography.fontHeadline,
|
|
fontWeight: FontWeight.w700,
|
|
),
|
|
),
|
|
const SizedBox(height: 8),
|
|
Text(
|
|
'Unable to display this club at this time.',
|
|
textAlign: TextAlign.center,
|
|
style: TextStyle(
|
|
fontFamily: AppTypography.fontBody,
|
|
fontSize: 13,
|
|
color: theme.colorScheme.onSurfaceVariant,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildHeroCard(
|
|
BuildContext context,
|
|
Club club,
|
|
ClubMember? member,
|
|
MotorSignalThemeColors colors,
|
|
ThemeData theme,
|
|
) {
|
|
return Container(
|
|
padding: const EdgeInsets.all(20.0),
|
|
decoration: BoxDecoration(
|
|
color: colors.surfaceContainer,
|
|
borderRadius: BorderRadius.circular(16),
|
|
border: Border.all(color: colors.outlineVariant, width: 0.8),
|
|
),
|
|
child: Column(
|
|
children: [
|
|
ClubMonogram(name: club.name, size: 68, fontSize: 26),
|
|
const SizedBox(height: 14),
|
|
Text(
|
|
club.name,
|
|
textAlign: TextAlign.center,
|
|
style: const TextStyle(
|
|
fontFamily: AppTypography.fontHeadline,
|
|
fontSize: 20,
|
|
fontWeight: FontWeight.w800,
|
|
),
|
|
),
|
|
const SizedBox(height: 4),
|
|
Text(
|
|
'@${club.slug}',
|
|
style: TextStyle(
|
|
fontFamily: AppTypography.fontTelemetry,
|
|
fontSize: 14,
|
|
fontWeight: FontWeight.w600,
|
|
color: colors.cyberCyan,
|
|
),
|
|
),
|
|
const SizedBox(height: 14),
|
|
Wrap(
|
|
spacing: 8,
|
|
runSpacing: 6,
|
|
alignment: WrapAlignment.center,
|
|
children: [
|
|
_buildPill(
|
|
icon: Icons.people_outline,
|
|
label: '${club.memberCount} ${club.memberCount == 1 ? 'member' : 'members'}',
|
|
colors: colors,
|
|
theme: theme,
|
|
),
|
|
_buildPill(
|
|
icon: club.admissionPolicy == ClubAdmissionPolicy.open
|
|
? Icons.lock_open_outlined
|
|
: Icons.how_to_reg_outlined,
|
|
label: club.admissionPolicy.label,
|
|
colors: colors,
|
|
theme: theme,
|
|
tintColor: colors.cyberCyan,
|
|
),
|
|
_buildPill(
|
|
icon: club.visibility == ClubVisibility.public
|
|
? Icons.public
|
|
: Icons.visibility_off_outlined,
|
|
label: club.visibility.label,
|
|
colors: colors,
|
|
theme: theme,
|
|
),
|
|
if (member != null &&
|
|
member.isActive &&
|
|
(member.role == ClubRole.owner || member.role == ClubRole.admin))
|
|
_buildPill(
|
|
icon: Icons.verified_user_outlined,
|
|
label: 'ROLE: ${member.role.label.toUpperCase()}',
|
|
colors: colors,
|
|
theme: theme,
|
|
tintColor: colors.launchGreen,
|
|
),
|
|
],
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildDescriptionCard(
|
|
BuildContext context,
|
|
Club club,
|
|
MotorSignalThemeColors colors,
|
|
ThemeData theme,
|
|
) {
|
|
return Container(
|
|
padding: const EdgeInsets.all(18.0),
|
|
decoration: BoxDecoration(
|
|
color: colors.surfaceContainer,
|
|
borderRadius: BorderRadius.circular(14),
|
|
border: Border.all(color: colors.outlineVariant, width: 0.8),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'About the Club',
|
|
style: TextStyle(
|
|
fontFamily: AppTypography.fontHeadline,
|
|
fontSize: 13,
|
|
fontWeight: FontWeight.w700,
|
|
letterSpacing: 0.2,
|
|
color: colors.cyberCyan,
|
|
),
|
|
),
|
|
const SizedBox(height: 10),
|
|
Text(
|
|
club.description.isNotEmpty
|
|
? club.description
|
|
: 'No club description provided by leadership.',
|
|
style: TextStyle(
|
|
fontFamily: AppTypography.fontBody,
|
|
fontSize: 13.5,
|
|
color: club.description.isNotEmpty
|
|
? theme.colorScheme.onSurface
|
|
: theme.colorScheme.onSurfaceVariant,
|
|
height: 1.4,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildMetadataCard(
|
|
BuildContext context,
|
|
Club club,
|
|
MotorSignalThemeColors colors,
|
|
ThemeData theme,
|
|
) {
|
|
return Container(
|
|
padding: const EdgeInsets.all(18.0),
|
|
decoration: BoxDecoration(
|
|
color: colors.surfaceContainer,
|
|
borderRadius: BorderRadius.circular(14),
|
|
border: Border.all(color: colors.outlineVariant, width: 0.8),
|
|
),
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
'Club Details',
|
|
style: TextStyle(
|
|
fontFamily: AppTypography.fontHeadline,
|
|
fontSize: 13,
|
|
fontWeight: FontWeight.w700,
|
|
letterSpacing: 0.2,
|
|
color: colors.cyberCyan,
|
|
),
|
|
),
|
|
const SizedBox(height: 12),
|
|
_buildDetailRow(
|
|
icon: Icons.location_on_outlined,
|
|
title: 'Home Region',
|
|
value: club.homeRegion?.trim().isNotEmpty == true ? club.homeRegion! : 'Global / Not Specified',
|
|
colors: colors,
|
|
theme: theme,
|
|
),
|
|
const Divider(height: 18),
|
|
_buildDetailRow(
|
|
icon: Icons.two_wheeler_outlined,
|
|
title: 'Motorcycle Focus',
|
|
value: club.motorcycleFocus?.trim().isNotEmpty == true
|
|
? club.motorcycleFocus!
|
|
: 'All Motorcycles & Disciplines',
|
|
colors: colors,
|
|
theme: theme,
|
|
),
|
|
const Divider(height: 18),
|
|
_buildDetailRow(
|
|
icon: Icons.calendar_today_outlined,
|
|
title: 'Established',
|
|
value: '${club.createdAt.year}-${club.createdAt.month.toString().padLeft(2, '0')}-${club.createdAt.day.toString().padLeft(2, '0')}',
|
|
colors: colors,
|
|
theme: theme,
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
Widget _buildDetailRow({
|
|
required IconData icon,
|
|
required String title,
|
|
required String value,
|
|
required MotorSignalThemeColors colors,
|
|
required ThemeData theme,
|
|
}) {
|
|
return Row(
|
|
children: [
|
|
Icon(icon, size: 18, color: colors.cyberCyan),
|
|
const SizedBox(width: 12),
|
|
Expanded(
|
|
child: Column(
|
|
crossAxisAlignment: CrossAxisAlignment.start,
|
|
children: [
|
|
Text(
|
|
title,
|
|
style: TextStyle(
|
|
fontFamily: AppTypography.fontBody,
|
|
fontSize: 11.5,
|
|
color: theme.colorScheme.onSurfaceVariant,
|
|
),
|
|
),
|
|
const SizedBox(height: 2),
|
|
Text(
|
|
value,
|
|
style: const TextStyle(
|
|
fontFamily: AppTypography.fontHeadline,
|
|
fontSize: 13.5,
|
|
fontWeight: FontWeight.w600,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
);
|
|
}
|
|
|
|
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: 10, vertical: 5),
|
|
decoration: BoxDecoration(
|
|
color: colors.surfaceContainerHighest.withValues(alpha: 0.6),
|
|
borderRadius: BorderRadius.circular(8),
|
|
border: Border.all(color: colors.outlineVariant.withValues(alpha: 0.6), width: 0.6),
|
|
),
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
Icon(icon, size: 13, color: effectiveColor),
|
|
const SizedBox(width: 5),
|
|
Text(
|
|
label,
|
|
style: TextStyle(
|
|
fontFamily: AppTypography.fontHeadline,
|
|
fontSize: 11.5,
|
|
fontWeight: FontWeight.w600,
|
|
color: effectiveColor,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|