b239ae3e5f
Replaces Firebase with a self-hosted PHP/MySQL API served from winded.prymsolutions.com. Includes full backend (schema, auth, events, teams, brackets, suggestions, stats, media, file upload) and updated Flutter repositories and domain models. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
177 lines
5.9 KiB
Dart
177 lines
5.9 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:go_router/go_router.dart';
|
|
|
|
import '../../features/auth/application/auth_notifier.dart';
|
|
import '../../features/profile/application/profile_notifier.dart';
|
|
import '../../features/profile/domain/user_profile.dart';
|
|
import '../admin/admin_guard.dart';
|
|
|
|
/// Root shell holding the bottom navigation bar that switches between the
|
|
/// six top-level tabs of the app. Labels are uppercase to match the
|
|
/// aggressive, jersey-inspired visual language; a thin purple accent line
|
|
/// sits above the nav bar for an edgy soccer-badge feel.
|
|
///
|
|
/// Admins (per [isAdmin]) see a small gear button overlaid in the top-right
|
|
/// of the AppBar band that links into the admin panel.
|
|
class MainShell extends ConsumerWidget {
|
|
const MainShell({super.key, required this.child});
|
|
|
|
final Widget child;
|
|
|
|
static const _tabs = [
|
|
(
|
|
label: 'EVENTS',
|
|
icon: Icons.event_outlined,
|
|
activeIcon: Icons.event,
|
|
path: '/events',
|
|
),
|
|
(
|
|
label: 'BRACKETS',
|
|
icon: Icons.account_tree_outlined,
|
|
activeIcon: Icons.account_tree,
|
|
path: '/brackets',
|
|
),
|
|
(
|
|
label: 'TEAMS',
|
|
icon: Icons.groups_outlined,
|
|
activeIcon: Icons.groups,
|
|
path: '/teams',
|
|
),
|
|
(
|
|
label: 'STATS',
|
|
icon: Icons.bar_chart_outlined,
|
|
activeIcon: Icons.bar_chart,
|
|
path: '/stats',
|
|
),
|
|
(
|
|
label: 'MEDIA',
|
|
icon: Icons.play_circle_outline,
|
|
activeIcon: Icons.play_circle,
|
|
path: '/media',
|
|
),
|
|
(
|
|
label: 'SUGGEST',
|
|
icon: Icons.lightbulb_outline,
|
|
activeIcon: Icons.lightbulb,
|
|
path: '/suggestions',
|
|
),
|
|
];
|
|
|
|
int _currentIndex(BuildContext context) {
|
|
final location = GoRouterState.of(context).uri.path;
|
|
final idx = _tabs.indexWhere((t) => location.startsWith(t.path));
|
|
return idx < 0 ? 0 : idx;
|
|
}
|
|
|
|
@override
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
|
final currentIndex = _currentIndex(context);
|
|
final user = ref.watch(authNotifierProvider).valueOrNull;
|
|
final showAdmin = isAdmin(user);
|
|
final role = ref.watch(currentUserRoleProvider);
|
|
final showProfile = user != null;
|
|
final showManager = role == UserRole.manager;
|
|
|
|
return Scaffold(
|
|
body: Stack(
|
|
children: <Widget>[
|
|
Positioned.fill(child: child),
|
|
Positioned(
|
|
top: 0,
|
|
right: 0,
|
|
child: SafeArea(
|
|
child: Row(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: <Widget>[
|
|
if (showManager)
|
|
Material(
|
|
color: Colors.transparent,
|
|
child: IconButton(
|
|
icon: const Icon(Icons.shield_outlined),
|
|
tooltip: 'Manager dashboard',
|
|
onPressed: () => context.go('/manager'),
|
|
),
|
|
),
|
|
if (showAdmin)
|
|
Material(
|
|
color: Colors.transparent,
|
|
child: IconButton(
|
|
icon: const Icon(Icons.settings_outlined),
|
|
tooltip: 'Admin panel',
|
|
onPressed: () => context.go('/admin/events'),
|
|
),
|
|
),
|
|
if (showProfile)
|
|
Material(
|
|
color: Colors.transparent,
|
|
child: IconButton(
|
|
icon: const Icon(Icons.person_outline),
|
|
tooltip: 'My profile',
|
|
onPressed: () => context.go('/profile'),
|
|
),
|
|
),
|
|
Material(
|
|
color: Colors.transparent,
|
|
child: PopupMenuButton<_UserMenuAction>(
|
|
icon: const Icon(Icons.account_circle_outlined),
|
|
tooltip: 'Account',
|
|
onSelected: (action) async {
|
|
if (action == _UserMenuAction.signOut) {
|
|
await ref.read(authNotifierProvider.notifier).signOut();
|
|
}
|
|
},
|
|
itemBuilder: (context) => <PopupMenuEntry<_UserMenuAction>>[
|
|
PopupMenuItem<_UserMenuAction>(
|
|
enabled: false,
|
|
child: Text(
|
|
user?.email ?? '',
|
|
style: Theme.of(context).textTheme.bodySmall,
|
|
),
|
|
),
|
|
const PopupMenuDivider(),
|
|
const PopupMenuItem<_UserMenuAction>(
|
|
value: _UserMenuAction.signOut,
|
|
child: Row(
|
|
children: <Widget>[
|
|
Icon(Icons.logout, size: 18),
|
|
SizedBox(width: 8),
|
|
Text('Sign out'),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
bottomNavigationBar: Column(
|
|
mainAxisSize: MainAxisSize.min,
|
|
children: [
|
|
// Sharp purple accent line above the nav bar for an edgy look.
|
|
Container(height: 1, color: const Color(0xFF8B30C8)),
|
|
NavigationBar(
|
|
selectedIndex: currentIndex,
|
|
onDestinationSelected: (i) => context.go(_tabs[i].path),
|
|
destinations: _tabs
|
|
.map(
|
|
(t) => NavigationDestination(
|
|
icon: Icon(t.icon),
|
|
selectedIcon: Icon(t.activeIcon),
|
|
label: t.label,
|
|
),
|
|
)
|
|
.toList(),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
enum _UserMenuAction { signOut }
|