87 lines
3.1 KiB
Dart
87 lines
3.1 KiB
Dart
import 'package:flutter/material.dart';
|
|
import 'package:flutter_riverpod/flutter_riverpod.dart';
|
|
import 'package:go_router/go_router.dart';
|
|
import 'package:supabase_flutter/supabase_flutter.dart';
|
|
|
|
import '../../../core/network/supabase_status.dart';
|
|
import '../../features/auth/presentation/controllers/auth_controller.dart';
|
|
import '../../features/auth/presentation/screens/login_screen.dart';
|
|
import '../../features/medication/presentation/screens/medication_screen.dart';
|
|
import '../../features/nutrition/presentation/screens/nutrition_screen.dart';
|
|
import '../../features/meal_planner/presentation/screens/meal_planner_screen.dart';
|
|
import '../../features/pantry/presentation/screens/pantry_screen.dart';
|
|
import '../scaffold/adaptive_home_scaffold.dart';
|
|
|
|
/// Locations that may be visited without an authenticated session.
|
|
const Set<String> _publicPaths = {'/login'};
|
|
|
|
final _rootNavigatorKey = GlobalKey<NavigatorState>();
|
|
final _shellNavigatorKey = GlobalKey<NavigatorState>();
|
|
|
|
/// Application router with a reactive authentication guard.
|
|
///
|
|
/// The guard re-evaluates on every GoTrue auth transition (bridged into
|
|
/// [refreshListenable] below), so a successful magic-link or OAuth round trip
|
|
/// immediately navigates into the protected shell, and signing out bounces the
|
|
/// user back to `/login`.
|
|
final routerProvider = Provider<GoRouter>((ref) {
|
|
final authRefreshNotifier = ValueNotifier<int>(0);
|
|
ref.onDispose(authRefreshNotifier.dispose);
|
|
ref.listen(authStateProvider, (_, __) => authRefreshNotifier.value++);
|
|
|
|
return GoRouter(
|
|
navigatorKey: _rootNavigatorKey,
|
|
initialLocation: '/medication',
|
|
refreshListenable: authRefreshNotifier,
|
|
redirect: _authGuard,
|
|
routes: [
|
|
GoRoute(
|
|
path: '/login',
|
|
builder: (context, state) => const LoginScreen(),
|
|
),
|
|
ShellRoute(
|
|
navigatorKey: _shellNavigatorKey,
|
|
builder: (context, state, child) {
|
|
return AdaptiveHomeScaffold(child: child);
|
|
},
|
|
routes: [
|
|
GoRoute(
|
|
path: '/medication',
|
|
builder: (context, state) => const MedicationScreen(),
|
|
),
|
|
GoRoute(
|
|
path: '/nutrition',
|
|
builder: (context, state) => const NutritionScreen(),
|
|
),
|
|
GoRoute(
|
|
path: '/meal-planner',
|
|
builder: (context, state) => const MealPlannerScreen(),
|
|
),
|
|
GoRoute(
|
|
path: '/pantry',
|
|
builder: (context, state) => const PantryScreen(),
|
|
),
|
|
],
|
|
),
|
|
],
|
|
);
|
|
});
|
|
|
|
/// Redirect guard enforcing the passwordless session lifecycle:
|
|
/// * Unauthenticated sessions on a protected route -> `/login`.
|
|
/// * Authenticated sessions on `/login` -> `/medication`.
|
|
String? _authGuard(BuildContext context, GoRouterState state) {
|
|
final location = state.matchedLocation;
|
|
final isAuthenticated = isSupabaseAvailable &&
|
|
Supabase.instance.client.auth.currentSession != null;
|
|
|
|
if (!isAuthenticated && !_publicPaths.contains(location)) {
|
|
return '/login';
|
|
}
|
|
if (isAuthenticated && location == '/login') {
|
|
return '/medication';
|
|
}
|
|
return null;
|
|
}
|
|
|