import 'dart:async'; 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/env.dart'; import '../core/models.dart'; import '../data/user_library.dart'; import '../features/artist/artist_screen.dart'; import '../features/auth/login_screen.dart'; import '../features/downloads/downloads_screen.dart'; import '../features/explore/explore_screen.dart'; import '../features/explore/occasion_screen.dart'; import '../features/home/home_screen.dart'; import '../features/library/library_screen.dart'; import '../features/profile/profile_screen.dart'; import '../features/player/now_playing_screen.dart'; import '../features/about/about_screen.dart'; import '../features/album/album_screen.dart'; import '../features/search/search_screen.dart'; import '../features/settings/document_screen.dart'; import '../features/settings/help_screen.dart'; import '../features/settings/report_screen.dart'; import '../features/settings/settings_screen.dart'; import '../features/support/support_screen.dart'; import '../features/welcome/splash_screen.dart'; import '../shared/widgets/app_shell.dart'; /// Whether the welcome + sign-in screens have been seen on this device. class Onboarding extends Notifier { static const _key = 'onboarding_done'; @override bool build() => ref.watch(sharedPrefsProvider).getBool(_key) ?? false; void complete() { state = true; ref.read(sharedPrefsProvider).setBool(_key, true); } } final onboardingProvider = NotifierProvider(Onboarding.new); /// Re-runs the router redirect when someone signs in or out. class _AuthRefresh extends ChangeNotifier { _AuthRefresh() { if (Env.hasSupabase) { _sub = Supabase.instance.client.auth.onAuthStateChange.listen((_) => notifyListeners()); } } StreamSubscription? _sub; @override void dispose() { _sub?.cancel(); super.dispose(); } } /// Signed in to the server. Guests (and demo builds with no server) are not. bool get isSignedIn => Env.hasSupabase && Supabase.instance.client.auth.currentSession != null; final routerProvider = Provider((ref) { final onboarded = ref.read(onboardingProvider); final authRefresh = _AuthRefresh(); ref.onDispose(authRefresh.dispose); return GoRouter( initialLocation: onboarded ? '/home' : '/welcome', refreshListenable: authRefresh, // Guests can browse (with 30-second previews); the profile needs an account. redirect: (_, state) { final at = state.matchedLocation; if (!isSignedIn && at == '/profile') return '/login'; if (isSignedIn && at == '/login') return '/home'; return null; }, routes: [ GoRoute(path: '/welcome', builder: (_, _) => const SplashScreen()), GoRoute( path: '/login', builder: (_, state) => LoginScreen(startWithSignUp: state.uri.queryParameters['mode'] == 'signup'), ), GoRoute(path: '/profile', builder: (_, _) => const ProfileScreen()), GoRoute(path: '/about', builder: (_, _) => const AboutScreen()), GoRoute(path: '/settings', builder: (_, _) => const SettingsScreen()), GoRoute(path: '/help', builder: (_, _) => const HelpScreen()), GoRoute(path: '/support', builder: (_, _) => const SupportScreen()), GoRoute( path: '/privacy', builder: (_, _) => const DocumentScreen(title: 'Privacy Policy', asset: 'assets/legal/privacy.md'), ), GoRoute( path: '/terms', builder: (_, _) => const DocumentScreen(title: 'Terms of Use', asset: 'assets/legal/terms.md'), ), GoRoute( path: '/report', builder: (_, state) => ReportScreen(trackId: state.uri.queryParameters['track']), ), StatefulShellRoute.indexedStack( builder: (_, _, shell) => AppShell(shell: shell), branches: [ StatefulShellBranch( routes: [ GoRoute(path: '/home', builder: (_, _) => const HomeScreen()), ], ), StatefulShellBranch( routes: [ GoRoute( path: '/explore', builder: (_, _) => const ExploreScreen(), ), ], ), StatefulShellBranch( routes: [ GoRoute( path: '/library', builder: (_, _) => const LibraryScreen(), ), ], ), StatefulShellBranch( routes: [ GoRoute( path: '/downloads', builder: (_, _) => const DownloadsScreen(), ), ], ), ], ), GoRoute(path: '/search', builder: (_, _) => const SearchScreen()), GoRoute( path: '/artist/:id', builder: (_, state) => ArtistScreen(artistId: state.pathParameters['id']!), ), GoRoute( path: '/album/:id', builder: (_, state) => AlbumScreen(albumId: state.pathParameters['id']!), ), GoRoute( path: '/occasion/:id', builder: (_, state) => OccasionScreen( occasion: Occasion.fromId(state.pathParameters['id']), ), ), GoRoute( path: '/player', pageBuilder: (_, state) => CustomTransitionPage( key: state.pageKey, fullscreenDialog: true, child: const NowPlayingScreen(), transitionsBuilder: (_, animation, _, child) => SlideTransition( position: Tween( begin: const Offset(0, 1), end: Offset.zero, ).chain(CurveTween(curve: Curves.easeOutCubic)).animate(animation), child: child, ), ), ), ], ); });