slrpg-app/lib/src/core/routing/app_router.dart
Patryk Hegenberg a2067b5f9b feat: add multiplayer workout
Added a lobbyScreen to generate a unique Code and invite other Players
and the possibility to workout together, by using the pocketbase
realtime feature.
2026-01-13 07:56:08 +01:00

270 lines
8.4 KiB
Dart

import 'package:flutter/material.dart';
import 'package:flutter_riverpod/flutter_riverpod.dart';
import 'package:go_router/go_router.dart';
import 'package:slrpg_app/src/features/multiplayer/presentation/screens/leaderboard_screen.dart';
import 'package:slrpg_app/src/features/multiplayer/presentation/screens/lobby_screen.dart';
import '../../features/authentication/presentation/screens/login_screen.dart';
import '../../features/authentication/presentation/screens/profile_screen.dart';
import '../../features/authentication/presentation/screens/register_screen.dart';
import '../../features/gamification/presentation/screens/quest_log.dart';
import '../../features/onboarding/presentation/screens/avatar_setup_screen.dart';
import '../../features/onboarding/presentation/screens/welcome_screen.dart';
import '../../features/onboarding/presentation/screens/bodyweight_input_screen.dart';
import '../../features/onboarding/presentation/screens/strength_test_screen.dart';
import '../../features/onboarding/presentation/screens/inventory_setup_screen.dart';
import '../../features/dashboard/presentation/screens/hub_screen.dart';
import '../../features/workout_runner/presentation/screens/battle_screen.dart';
import '../../features/inventory/presentation/screens/inventory_screen.dart';
import '../../features/history/presentation/screens/history_screen.dart';
import '../../shared/data/repositories/user_repository.dart';
import '../../features/stats/presentation/screens/stats_screen.dart';
import '../constants/asset_paths.dart';
import '../../features/gamification/presentation/screens/codex_screen.dart';
final routerProvider = Provider<GoRouter>((ref) {
final userRepo = ref.watch(userRepositoryProvider);
return GoRouter(
initialLocation: '/splash',
redirect: (context, state) async {
final user = await userRepo.getLocalUser();
final isAuthenticated = user != null;
final isOnAuthPage = state.matchedLocation == '/login' ||
state.matchedLocation == '/register' ||
state.matchedLocation.startsWith('/onboarding');
if (!isAuthenticated &&
!isOnAuthPage &&
state.matchedLocation != '/splash') {
return '/login';
}
return null;
},
routes: [
// Splash / Initial Route
GoRoute(
path: '/splash',
builder: (context, state) => const SplashScreen(),
),
// Authentication
GoRoute(
path: '/login',
name: 'login',
builder: (context, state) => const LoginScreen(),
),
GoRoute(
path: '/register',
name: 'register',
builder: (context, state) => const RegisterScreen(),
),
// Onboarding Flow
GoRoute(
path: '/onboarding/welcome',
name: 'welcome',
builder: (context, state) => const WelcomeScreen(),
),
GoRoute(
path: '/onboarding/bodyweight',
name: 'bodyweight',
builder: (context, state) => const BodyweightInputScreen(),
),
GoRoute(
path: '/onboarding/strength-test',
name: 'strength-test',
builder: (context, state) => const StrengthTestScreen(),
),
GoRoute(
path: '/onboarding/inventory',
name: 'inventory-setup',
builder: (context, state) => const InventorySetupScreen(),
),
GoRoute(
path: '/onboarding/avatar',
name: 'avatar-setup',
builder: (context, state) => const AvatarSetupScreen(),
),
// Main App
GoRoute(
path: '/hub',
name: 'hub',
builder: (context, state) => const HubScreen(),
),
GoRoute(
path: '/battle',
name: 'battle',
builder: (context, state) {
final extra = state.extra as Map<String, dynamic>?;
return BattleScreen(
week: extra?['week'] ?? 1,
day: extra?['day'] ?? 1,
workoutId: extra?['workoutId'],
);
},
),
GoRoute(
path: '/inventory',
name: 'inventory',
builder: (context, state) => const InventoryScreen(),
),
GoRoute(
path: '/history',
name: 'history',
builder: (context, state) => const HistoryScreen(),
),
GoRoute(
path: '/stats',
name: 'stats',
builder: (context, state) => const StatsScreen(),
),
GoRoute(
path: '/profile',
name: 'profile',
builder: (context, state) => const ProfileScreen(),
),
GoRoute(
path: '/codex',
name: 'codex',
builder: (context, state) => const CodexScreen(),
),
GoRoute(
path: '/quests',
name: 'quests',
builder: (context, state) => const QuestLogScreen(),
),
GoRoute(
path: '/leaderboard',
builder: (context, state) => const LeaderboardScreen(),
),
GoRoute(
path: '/lobby/:partyId',
builder: (context, state) {
final partyId = state.pathParameters['partyId']!;
return LobbyScreen(partyId: partyId);
},
),
GoRoute(
path: '/battle/:partyId',
builder: (context, state) {
final partyId = state.pathParameters['partyId']!;
final extra = state.extra as Map<String, dynamic>?;
return BattleScreen(
week: extra?['week'] ?? 1,
day: extra?['day'] ?? 1,
workoutId: extra?['workoutId'],
partyId: partyId);
},
),
],
);
});
class SplashScreen extends ConsumerStatefulWidget {
const SplashScreen({super.key});
@override
ConsumerState<SplashScreen> createState() => _SplashScreenState();
}
class _SplashScreenState extends ConsumerState<SplashScreen> {
@override
void initState() {
super.initState();
_checkInitialRoute();
}
Future<void> _checkInitialRoute() async {
await Future.delayed(const Duration(seconds: 1));
if (!mounted) return;
final userRepo = ref.read(userRepositoryProvider);
final user = await userRepo.getLocalUser();
if (user == null) {
context.go('/login');
} else {
context.go('/hub');
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: [
Positioned.fill(
child: Image.asset(
AssetPaths.bgSplash,
fit: BoxFit.cover,
),
),
Positioned.fill(
child: Container(
color: Colors.black.withValues(alpha: 0.5),
),
),
Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: 120,
height: 120,
decoration: BoxDecoration(
color: const Color(0xFF00E5FF).withValues(alpha: 0.9),
borderRadius: BorderRadius.circular(24),
boxShadow: [
BoxShadow(
color: const Color(0xFF00E5FF).withValues(alpha: 0.6),
blurRadius: 20,
spreadRadius: 5,
),
],
),
child: const Icon(
Icons.fitness_center,
size: 64,
color: Colors.black,
),
),
const SizedBox(height: 24),
Text(
'S.L.R.P.G.',
style: Theme.of(context).textTheme.displayLarge?.copyWith(
color: Colors.white,
shadows: [
const Shadow(
color: Colors.black,
blurRadius: 10,
offset: Offset(0, 4)),
],
),
),
const SizedBox(height: 8),
Text(
'Streetlifting RPG',
style: Theme.of(context).textTheme.bodyLarge?.copyWith(
color: Colors.white,
fontWeight: FontWeight.bold,
letterSpacing: 1.2,
),
),
const SizedBox(height: 48),
const CircularProgressIndicator(
color: Color(0xFF00E5FF),
),
],
),
),
],
),
);
}
}