1import 'package:flutter/foundation.dart'; 2import 'package:flutter/material.dart'; 3import 'package:flutter/services.dart'; 4import 'package:go_router/go_router.dart'; 5import 'package:provider/provider.dart'; 6 7import 'config/oauth_config.dart'; 8import 'providers/auth_provider.dart'; 9import 'providers/feed_provider.dart'; 10import 'providers/vote_provider.dart'; 11import 'screens/auth/login_screen.dart'; 12import 'screens/home/main_shell_screen.dart'; 13import 'screens/landing_screen.dart'; 14import 'services/vote_service.dart'; 15 16void main() async { 17 WidgetsFlutterBinding.ensureInitialized(); 18 19 // Set system UI overlay style (Android navigation bar) 20 SystemChrome.setSystemUIOverlayStyle( 21 const SystemUiOverlayStyle( 22 systemNavigationBarColor: Color(0xFF0B0F14), 23 systemNavigationBarIconBrightness: Brightness.light, 24 ), 25 ); 26 27 // Initialize auth provider 28 final authProvider = AuthProvider(); 29 await authProvider.initialize(); 30 31 // Initialize vote service with auth callbacks for direct PDS writes 32 // Uses DPoP authentication (not Bearer tokens!) 33 final voteService = VoteService( 34 sessionGetter: () async => authProvider.session, 35 didGetter: () => authProvider.did, 36 pdsUrlGetter: authProvider.getPdsUrl, 37 ); 38 39 runApp( 40 MultiProvider( 41 providers: [ 42 ChangeNotifierProvider.value(value: authProvider), 43 ChangeNotifierProvider(create: (_) => FeedProvider(authProvider)), 44 ChangeNotifierProvider( 45 create: (_) => VoteProvider( 46 voteService: voteService, 47 authProvider: authProvider, 48 ), 49 ), 50 ], 51 child: const CovesApp(), 52 ), 53 ); 54} 55 56class CovesApp extends StatelessWidget { 57 const CovesApp({super.key}); 58 59 @override 60 Widget build(BuildContext context) { 61 final authProvider = Provider.of<AuthProvider>(context, listen: false); 62 63 return MaterialApp.router( 64 title: 'Coves', 65 theme: ThemeData( 66 colorScheme: ColorScheme.fromSeed( 67 seedColor: const Color(0xFFFF6B35), 68 brightness: Brightness.dark, 69 ), 70 useMaterial3: true, 71 ), 72 routerConfig: _createRouter(authProvider), 73 debugShowCheckedModeBanner: false, 74 ); 75 } 76} 77 78// GoRouter configuration factory 79GoRouter _createRouter(AuthProvider authProvider) { 80 return GoRouter( 81 routes: [ 82 GoRoute(path: '/', builder: (context, state) => const LandingScreen()), 83 GoRoute(path: '/login', builder: (context, state) => const LoginScreen()), 84 GoRoute( 85 path: '/feed', 86 builder: (context, state) => const MainShellScreen(), 87 ), 88 ], 89 refreshListenable: authProvider, 90 redirect: (context, state) { 91 final isAuthenticated = authProvider.isAuthenticated; 92 final isLoading = authProvider.isLoading; 93 final currentPath = state.uri.path; 94 95 // Don't redirect while loading initial auth state 96 if (isLoading) { 97 return null; 98 } 99 100 // If authenticated and on landing/login screen, redirect to feed 101 if (isAuthenticated && (currentPath == '/' || currentPath == '/login')) { 102 if (kDebugMode) { 103 print('🔄 User authenticated, redirecting to /feed'); 104 } 105 return '/feed'; 106 } 107 108 // Allow anonymous users to access /feed for browsing 109 // Sign-out redirect is handled explicitly in the sign-out action 110 return null; 111 }, 112 errorBuilder: (context, state) { 113 // Check if this is an OAuth callback 114 if (state.uri.scheme == OAuthConfig.customScheme) { 115 if (kDebugMode) { 116 print( 117 '⚠️ OAuth callback in errorBuilder - ' 118 'flutter_web_auth_2 should handle it', 119 ); 120 print(' URI: ${state.uri}'); 121 } 122 // Return nothing - just stay on current screen 123 // flutter_web_auth_2 will process the callback at native level 124 return const SizedBox.shrink(); 125 } 126 127 // For other errors, show landing page 128 if (kDebugMode) { 129 print('⚠️ Router error: ${state.uri}'); 130 } 131 return const LandingScreen(); 132 }, 133 ); 134}