Main coves client
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(
44 create: (_) => VoteProvider(
45 voteService: voteService,
46 authProvider: authProvider,
47 ),
48 ),
49 ChangeNotifierProxyProvider2<AuthProvider, VoteProvider, FeedProvider>(
50 create: (context) => FeedProvider(
51 authProvider,
52 voteProvider: context.read<VoteProvider>(),
53 voteService: voteService,
54 ),
55 update: (context, auth, vote, previous) {
56 // Reuse existing provider to maintain state across rebuilds
57 return previous ??
58 FeedProvider(
59 auth,
60 voteProvider: vote,
61 voteService: voteService,
62 );
63 },
64 ),
65 ],
66 child: const CovesApp(),
67 ),
68 );
69}
70
71class CovesApp extends StatelessWidget {
72 const CovesApp({super.key});
73
74 @override
75 Widget build(BuildContext context) {
76 final authProvider = Provider.of<AuthProvider>(context, listen: false);
77
78 return MaterialApp.router(
79 title: 'Coves',
80 theme: ThemeData(
81 colorScheme: ColorScheme.fromSeed(
82 seedColor: const Color(0xFFFF6B35),
83 brightness: Brightness.dark,
84 ),
85 useMaterial3: true,
86 ),
87 routerConfig: _createRouter(authProvider),
88 debugShowCheckedModeBanner: false,
89 );
90 }
91}
92
93// GoRouter configuration factory
94GoRouter _createRouter(AuthProvider authProvider) {
95 return GoRouter(
96 routes: [
97 GoRoute(path: '/', builder: (context, state) => const LandingScreen()),
98 GoRoute(path: '/login', builder: (context, state) => const LoginScreen()),
99 GoRoute(
100 path: '/feed',
101 builder: (context, state) => const MainShellScreen(),
102 ),
103 ],
104 refreshListenable: authProvider,
105 redirect: (context, state) {
106 final isAuthenticated = authProvider.isAuthenticated;
107 final isLoading = authProvider.isLoading;
108 final currentPath = state.uri.path;
109
110 // Don't redirect while loading initial auth state
111 if (isLoading) {
112 return null;
113 }
114
115 // If authenticated and on landing/login screen, redirect to feed
116 if (isAuthenticated && (currentPath == '/' || currentPath == '/login')) {
117 if (kDebugMode) {
118 print('🔄 User authenticated, redirecting to /feed');
119 }
120 return '/feed';
121 }
122
123 // Allow anonymous users to access /feed for browsing
124 // Sign-out redirect is handled explicitly in the sign-out action
125 return null;
126 },
127 errorBuilder: (context, state) {
128 // Check if this is an OAuth callback
129 if (state.uri.scheme == OAuthConfig.customScheme) {
130 if (kDebugMode) {
131 print(
132 '⚠️ OAuth callback in errorBuilder - '
133 'flutter_web_auth_2 should handle it',
134 );
135 print(' URI: ${state.uri}');
136 }
137 // Return nothing - just stay on current screen
138 // flutter_web_auth_2 will process the callback at native level
139 return const SizedBox.shrink();
140 }
141
142 // For other errors, show landing page
143 if (kDebugMode) {
144 print('⚠️ Router error: ${state.uri}');
145 }
146 return const LandingScreen();
147 },
148 );
149}