MotorSignal-App/lib/features/social/social_hub_screen.dart

486 lines
18 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import '../../core/responsive/responsive_layout.dart';
import '../../core/theme/app_typography.dart';
import '../../core/theme/theme_extensions.dart';
import '../../services/auth/auth_providers.dart';
import '../../services/social/social_providers.dart';
import 'widgets/avatar_badge.dart';
import 'feed/social_feed_screen.dart';
enum SocialHubTab { hub, feed }
/// The authoritative landing screen for MotorSignal Social & Community.
///
/// Unifies the Community Social Feed & Kudos with Rider Profile, Friends,
/// and Group Rides under a dedicated, restrained automotive social home.
class SocialHubScreen extends ConsumerStatefulWidget {
final SocialHubTab initialTab;
const SocialHubScreen({super.key, this.initialTab = SocialHubTab.hub});
@override
ConsumerState<SocialHubScreen> createState() => _SocialHubScreenState();
}
class _SocialHubScreenState extends ConsumerState<SocialHubScreen> {
late SocialHubTab _currentTab;
@override
void initState() {
super.initState();
_currentTab = widget.initialTab;
}
@override
void didUpdateWidget(covariant SocialHubScreen oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.initialTab != widget.initialTab) {
_currentTab = widget.initialTab;
}
}
@override
Widget build(BuildContext context) {
final colors = context.motorColors;
final user = ref.watch(currentUserProvider);
final profileAsync = ref.watch(myRiderProfileProvider);
final requestsAsync = ref.watch(myIncomingRequestsProvider);
final pendingCount = requestsAsync.value?.length ?? 0;
return Scaffold(
appBar: AppBar(
title: const Text(
'Social',
style: TextStyle(fontWeight: FontWeight.w700),
),
backgroundColor: colors.surfaceContainer,
elevation: 0,
actions: [
Padding(
padding: const EdgeInsets.only(right: 12),
child: Center(
child: Container(
padding: const EdgeInsets.all(2),
decoration: BoxDecoration(
color: const Color(0xFF181C21),
borderRadius: BorderRadius.circular(10),
border: Border.all(
color: colors.outlineVariant.withValues(alpha: 0.5),
width: 0.8,
),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
_buildTabToggle(
key: const ValueKey('social_tab_feed'),
label: 'Feed',
isSelected: _currentTab == SocialHubTab.feed,
colors: colors,
onTap: () => setState(() => _currentTab = SocialHubTab.feed),
),
_buildTabToggle(
key: const ValueKey('social_tab_hub'),
label: 'Hub',
isSelected: _currentTab == SocialHubTab.hub,
colors: colors,
onTap: () => setState(() => _currentTab = SocialHubTab.hub),
),
],
),
),
),
),
],
),
body: IndexedStack(
index: _currentTab == SocialHubTab.hub ? 0 : 1,
children: [
_buildHubBody(context, colors, user, profileAsync.value, pendingCount),
const SocialFeedScreen(showAppBar: false),
],
),
);
}
Widget _buildTabToggle({
required Key key,
required String label,
required bool isSelected,
required MotorSignalThemeColors colors,
required VoidCallback onTap,
}) {
return InkWell(
key: key,
borderRadius: BorderRadius.circular(8),
onTap: onTap,
child: AnimatedContainer(
duration: const Duration(milliseconds: 160),
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
decoration: BoxDecoration(
color: isSelected ? colors.cyberCyan : Colors.transparent,
borderRadius: BorderRadius.circular(8),
),
child: Text(
label,
style: TextStyle(
fontFamily: AppTypography.fontHeadline,
fontSize: 12,
fontWeight: FontWeight.w700,
color: isSelected ? const Color(0xFF00363D) : const Color(0xFFBAC9CC),
),
),
),
);
}
Widget _buildHubBody(
BuildContext context,
MotorSignalThemeColors colors,
dynamic user,
dynamic profile,
int pendingCount,
) {
return SafeArea(
child: ResponsiveLayout(
builder: (context, responsive) {
final isWide = responsive.isExpanded || (responsive.isMedium && responsive.isLandscape);
return SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 16.0, vertical: 12.0),
child: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 800),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Rider Identity Header Card
_buildRiderHeroCard(context, profile, user, colors),
const SizedBox(height: 20),
// Section 1: Rider Network
Text(
'Rider Network',
style: TextStyle(
fontFamily: AppTypography.fontHeadline,
fontSize: 13,
fontWeight: FontWeight.w700,
letterSpacing: 0.2,
color: colors.cyberCyan,
),
),
const SizedBox(height: 10),
if (isWide) ...[
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: _buildNavigationCard(
context,
key: const ValueKey('social_card_friends'),
icon: Icons.people_outline,
title: 'Friends',
subtitle: 'Find fellow riders, manage requests & connections',
badgeCount: pendingCount,
colors: colors,
onTap: () => context.push('/social/friends'),
),
),
const SizedBox(width: 14),
Expanded(
child: _buildNavigationCard(
context,
key: const ValueKey('social_card_clubs'),
icon: Icons.shield_outlined,
title: 'Clubs',
subtitle: 'Discover motorcycle clubs, riding communities & member groups',
colors: colors,
onTap: () => context.push('/social/clubs'),
),
),
],
),
] else ...[
_buildNavigationCard(
context,
key: const ValueKey('social_card_friends'),
icon: Icons.people_outline,
title: 'Friends',
subtitle: 'Find fellow riders, manage requests & connections',
badgeCount: pendingCount,
colors: colors,
onTap: () => context.push('/social/friends'),
),
const SizedBox(height: 12),
_buildNavigationCard(
context,
key: const ValueKey('social_card_clubs'),
icon: Icons.shield_outlined,
title: 'Clubs',
subtitle: 'Discover motorcycle clubs, riding communities & member groups',
colors: colors,
onTap: () => context.push('/social/clubs'),
),
],
const SizedBox(height: 20),
// Section 2: Motorcycle Activities
Text(
'Motorcycle Activities',
style: TextStyle(
fontFamily: AppTypography.fontHeadline,
fontSize: 13,
fontWeight: FontWeight.w700,
letterSpacing: 0.2,
color: colors.cyberCyan,
),
),
const SizedBox(height: 10),
if (isWide) ...[
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: _buildNavigationCard(
context,
key: const ValueKey('social_card_feed'),
icon: Icons.dynamic_feed_outlined,
title: 'Community Feed',
subtitle: 'Squad telemetry, route snapshots, paces & kudos',
colors: colors,
onTap: () => setState(() => _currentTab = SocialHubTab.feed),
),
),
const SizedBox(width: 14),
Expanded(
child: _buildNavigationCard(
context,
key: const ValueKey('social_card_group_rides'),
icon: Icons.groups_outlined,
title: 'Group Rides',
subtitle: 'Join active sessions, explore local pack rides & track group telemetry',
colors: colors,
onTap: () => context.push('/group-rides'),
),
),
],
),
] else ...[
_buildNavigationCard(
context,
key: const ValueKey('social_card_feed'),
icon: Icons.dynamic_feed_outlined,
title: 'Community Feed',
subtitle: 'Squad telemetry, route snapshots, paces & kudos',
colors: colors,
onTap: () => setState(() => _currentTab = SocialHubTab.feed),
),
const SizedBox(height: 12),
_buildNavigationCard(
context,
key: const ValueKey('social_card_group_rides'),
icon: Icons.groups_outlined,
title: 'Group Rides',
subtitle: 'Join active sessions, explore local pack rides & track group telemetry',
colors: colors,
onTap: () => context.push('/group-rides'),
),
],
const SizedBox(height: 24),
],
),
),
),
);
},
),
);
}
Widget _buildRiderHeroCard(
BuildContext context,
dynamic profile,
dynamic user,
MotorSignalThemeColors colors,
) {
final hasProfile = profile != null;
final displayName = hasProfile ? profile.displayName : (user?.displayLabel ?? 'Rider');
final handle = hasProfile ? '@${profile.handle}' : 'Tap to setup rider profile';
final avatarPreset = hasProfile ? profile.avatarPreset : null;
return Container(
padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 12),
decoration: BoxDecoration(
color: colors.surfaceContainer,
borderRadius: BorderRadius.circular(14),
border: Border.all(color: colors.outlineVariant, width: 0.8),
),
child: Row(
children: [
AvatarBadge(
avatarPreset: avatarPreset,
displayName: displayName,
radius: 22,
),
const SizedBox(width: 12),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
Text(
displayName,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontFamily: AppTypography.fontHeadline,
fontSize: 15,
fontWeight: FontWeight.w700,
),
),
const SizedBox(height: 2),
Text(
handle,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: TextStyle(
fontFamily: AppTypography.fontBody,
fontSize: 11.5,
fontWeight: FontWeight.w500,
color: colors.cyberCyan,
),
),
],
),
),
const SizedBox(width: 8),
OutlinedButton(
style: OutlinedButton.styleFrom(
visualDensity: VisualDensity.compact,
side: BorderSide(color: colors.outlineVariant, width: 0.8),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 6),
),
onPressed: () => context.push('/social/profile'),
child: const Text(
'Edit',
style: TextStyle(
fontFamily: AppTypography.fontHeadline,
fontSize: 12,
fontWeight: FontWeight.w600,
),
),
),
],
),
);
}
Widget _buildNavigationCard(
BuildContext context, {
required Key key,
required IconData icon,
required String title,
required String subtitle,
required MotorSignalThemeColors colors,
required VoidCallback onTap,
int badgeCount = 0,
}) {
return Container(
key: key,
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(16.0),
child: Row(
children: [
Container(
width: 40,
height: 40,
decoration: BoxDecoration(
color: colors.cyberCyan.withValues(alpha: 0.10),
borderRadius: BorderRadius.circular(10),
),
child: Icon(icon, color: colors.cyberCyan, size: 22),
),
const SizedBox(width: 14),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Expanded(
child: Text(
title,
maxLines: 1,
overflow: TextOverflow.ellipsis,
style: const TextStyle(
fontFamily: AppTypography.fontHeadline,
fontSize: 14,
fontWeight: FontWeight.w600,
),
),
),
if (badgeCount > 0) ...[
const SizedBox(width: 8),
Container(
padding: const EdgeInsets.symmetric(horizontal: 6, vertical: 2),
decoration: BoxDecoration(
color: colors.cyberCyan,
borderRadius: BorderRadius.circular(10),
),
child: Text(
'$badgeCount',
style: const TextStyle(
fontFamily: AppTypography.fontTelemetry,
fontSize: 10,
fontWeight: FontWeight.w800,
color: Colors.black,
),
),
),
],
],
),
const SizedBox(height: 3),
Text(
subtitle,
style: TextStyle(
fontFamily: AppTypography.fontBody,
fontSize: 12,
color: Theme.of(context).colorScheme.onSurfaceVariant,
height: 1.3,
),
),
],
),
),
const SizedBox(width: 8),
Icon(
Icons.chevron_right,
color: Theme.of(context).colorScheme.onSurfaceVariant.withValues(alpha: 0.6),
size: 20,
),
],
),
),
),
),
);
}
}