Skip to content

Commit a39f0c6

Browse files
committed
implement auth state mgmt and navigation flow
1 parent e5d8c3e commit a39f0c6

7 files changed

Lines changed: 137 additions & 19 deletions

File tree

app/lib/main.dart

Lines changed: 4 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,21 @@
11
import 'package:ferna/providers/auth_provider.dart';
2-
import 'package:ferna/screens/login_screen.dart';
2+
import 'package:ferna/router.dart';
33
import 'package:ferna/theme/theme_data.dart';
44
import 'package:flutter/material.dart';
55
import 'package:provider/provider.dart';
66

77
void main() async {
88
WidgetsFlutterBinding.ensureInitialized();
99

10-
// Initialzie auth provider
10+
// Initialize auth provider
1111
final authProvider = await AuthProvider.initialize();
1212

1313
runApp(
1414
MultiProvider(
1515
providers: [
1616
ChangeNotifierProvider<AuthProvider>.value(value: authProvider),
1717
],
18-
child: FernaApp(),
18+
child: const FernaApp(),
1919
),
2020
);
2121
}
@@ -31,18 +31,7 @@ class FernaApp extends StatelessWidget {
3131
theme: FernaTheme.light,
3232
darkTheme: FernaTheme.dark,
3333
themeMode: ThemeMode.system,
34-
// TODO: Replace with router
35-
home: const LoginScreen(),
36-
routes: {'/home': (_) => const HomeScreen()},
34+
home: const AppNavigator(),
3735
);
3836
}
3937
}
40-
41-
class HomeScreen extends StatelessWidget {
42-
const HomeScreen({super.key});
43-
44-
@override
45-
Widget build(BuildContext context) {
46-
return const Placeholder(child: Text(":)"));
47-
}
48-
}

app/lib/providers/auth_provider.dart

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,16 @@ import '../services/storage_service.dart';
99
class AuthProvider with ChangeNotifier {
1010
final AuthService _authService = AuthService.instance;
1111
AuthProvider._();
12+
1213
bool _isLoading = false;
1314
bool get isLoading => _isLoading;
15+
16+
bool _isCheckingAuthState = false;
17+
bool get isCheckingAuthState => _isCheckingAuthState;
18+
19+
bool _isAuthenticated = false;
20+
bool get isAuthenticated => _isAuthenticated;
21+
1422
String _serverUrl = '';
1523
String get serverUrl => _serverUrl;
1624

@@ -25,9 +33,27 @@ class AuthProvider with ChangeNotifier {
2533
// Initialize AuthService (sets up Dio & loads any persisted cookies).
2634
await provider._authService.initialize(serverUrl: provider._serverUrl);
2735

36+
// Check if user is already authenticated
37+
await provider._checkAuthState();
38+
2839
return provider;
2940
}
3041

42+
/// Check authentication state by making a test API call
43+
Future<void> _checkAuthState() async {
44+
_isCheckingAuthState = true;
45+
notifyListeners();
46+
47+
try {
48+
_isAuthenticated = await _authService.checkAuthState();
49+
} catch (e) {
50+
_isAuthenticated = false;
51+
}
52+
53+
_isCheckingAuthState = false;
54+
notifyListeners();
55+
}
56+
3157
/// Change serverUrl, persist it, and re-initialize Dio so its baseUrl updates.
3258
Future<void> updateServerUrl(String newUrl) async {
3359
if (newUrl == _serverUrl) return;
@@ -50,9 +76,11 @@ class AuthProvider with ChangeNotifier {
5076

5177
try {
5278
await _authService.login(email: email, password: password);
79+
_isAuthenticated = true;
5380
_isLoading = false;
5481
notifyListeners();
5582
} catch (e) {
83+
_isAuthenticated = false;
5684
_isLoading = false;
5785
notifyListeners();
5886
rethrow;
@@ -68,13 +96,32 @@ class AuthProvider with ChangeNotifier {
6896
email: email,
6997
password: password,
7098
);
99+
_isAuthenticated = true;
71100
_isLoading = false;
72101
notifyListeners();
73102
return userId;
74103
} catch (e) {
104+
_isAuthenticated = false;
75105
_isLoading = false;
76106
notifyListeners();
77107
rethrow;
78108
}
79109
}
110+
111+
/// Logout user and clear authentication state
112+
Future<void> logout() async {
113+
_isLoading = true;
114+
notifyListeners();
115+
116+
try {
117+
await _authService.logout();
118+
_isAuthenticated = false;
119+
} catch (e) {
120+
// Even if logout fails, clear local state
121+
_isAuthenticated = false;
122+
}
123+
124+
_isLoading = false;
125+
notifyListeners();
126+
}
80127
}

app/lib/router.dart

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
import 'package:ferna/screens/home_screen.dart';
2+
import 'package:flutter/material.dart';
3+
import 'widgets/auth_wrapper.dart';
4+
5+
/// Main app navigation widget and handles auth state
6+
class AppNavigator extends StatelessWidget {
7+
const AppNavigator({super.key});
8+
9+
@override
10+
Widget build(BuildContext context) {
11+
return const AuthWrapper(
12+
authenticatedChild: HomeScreen(),
13+
);
14+
}
15+
}

app/lib/screens/home_screen.dart

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
import 'package:flutter/material.dart';
2+
3+
class HomeScreen extends StatelessWidget {
4+
const HomeScreen({super.key});
5+
6+
@override
7+
Widget build(BuildContext context) {
8+
return Placeholder(child: Text("home screen"));
9+
}
10+
}

app/lib/screens/login_screen.dart

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -105,9 +105,8 @@ class _LoginScreenState extends State<LoginScreen>
105105
await auth.signUp(email: email, password: password);
106106
}
107107

108-
// On success, navigate to your home screen:
109-
if (!mounted) return;
110-
Navigator.of(context).pushReplacementNamed('/home');
108+
// AuthWrapper will automatically handle navigation to home screen
109+
// when auth.isAuthenticated becomes true
111110
} catch (error) {
112111
if (!mounted) return;
113112
ScaffoldMessenger.of(context).showSnackBar(

app/lib/services/auth_service.dart

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ class AuthService {
3535
Future<void> login({required String email, required String password}) async {
3636
final dio = HttpClient.instance.dio;
3737
final response = await dio.post(
38-
'/auth/local/login',
38+
'/auth/local/login?session=1',
3939
data: {'user': email, 'passwd': password},
4040
);
4141

@@ -48,4 +48,27 @@ class AuthService {
4848
);
4949
}
5050
}
51+
52+
/// Check if user is currently authenticated by making a test API call
53+
Future<bool> checkAuthState() async {
54+
try {
55+
final dio = HttpClient.instance.dio;
56+
// Try to access a protected endpoint - the backend should return 401 if not authenticated
57+
// TODO: add /api/me for this pupose
58+
final response = await dio.get('/api/plants');
59+
60+
// If we get a successful response (200-299), user is authenticated
61+
return response.statusCode != null &&
62+
response.statusCode! >= 200 &&
63+
response.statusCode! < 300;
64+
} catch (e) {
65+
// Any error (network, 401, 403, etc.) means not authenticated
66+
return false;
67+
}
68+
}
69+
70+
/// Logout user by clearing cookies
71+
Future<void> logout() async {
72+
await HttpClient.instance.clearCookies();
73+
}
5174
}

app/lib/widgets/auth_wrapper.dart

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
import 'dart:developer';
2+
3+
import 'package:flutter/material.dart';
4+
import 'package:provider/provider.dart';
5+
import '../providers/auth_provider.dart';
6+
import '../screens/login_screen.dart';
7+
8+
/// Wrapper that checks authentication state and routes to appropriate screen
9+
class AuthWrapper extends StatelessWidget {
10+
final Widget authenticatedChild;
11+
12+
const AuthWrapper({super.key, required this.authenticatedChild});
13+
14+
@override
15+
Widget build(BuildContext context) {
16+
return Consumer<AuthProvider>(
17+
builder: (context, auth, _) {
18+
// Show loading indicator while checking auth state
19+
if (auth.isCheckingAuthState) {
20+
return const Scaffold(
21+
body: Center(child: CircularProgressIndicator()),
22+
);
23+
}
24+
25+
// Route based on authentication state
26+
if (auth.isAuthenticated) {
27+
log('User is authenticated, navigating to home screen');
28+
return authenticatedChild;
29+
} else {
30+
return const LoginScreen();
31+
}
32+
},
33+
);
34+
}
35+
}

0 commit comments

Comments
 (0)