111 lines
3.3 KiB
Dart

import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../core/api/api_client.dart';
import '../core/api/api_exception.dart';
import '../core/auth/auth_state.dart';
import '../core/storage/secure_storage.dart';
import '../repositories/auth_repository.dart';
// Core Service Providers
final secureStorageProvider = Provider<SecureStorage>((ref) {
return SecureStorage();
});
final apiClientProvider = Provider<ApiClient>((ref) {
final storage = ref.watch(secureStorageProvider);
return ApiClient(storage: storage);
});
final authRepositoryProvider = Provider<AuthRepository>((ref) {
final apiClient = ref.watch(apiClientProvider);
return AuthRepository(apiClient: apiClient);
});
// Auth State Notifier
class AuthNotifier extends StateNotifier<AuthState> {
final AuthRepository _authRepository;
final SecureStorage _storage;
final ApiClient _apiClient;
AuthNotifier({
required AuthRepository authRepository,
required SecureStorage storage,
required ApiClient apiClient,
}) : _authRepository = authRepository,
_storage = storage,
_apiClient = apiClient,
super(const AuthState(status: AuthStatus.initial)) {
// Hook into session expiration cleanly without cyclic provider dependency
_apiClient.onSessionExpired = handleSessionExpired;
}
/// Checks if a valid session exists in storage on app startup.
Future<void> restoreSession() async {
state = AuthState.loading();
try {
final sessionCookie = await _storage.getSessionCookie();
if (sessionCookie == null || sessionCookie.isEmpty) {
state = AuthState.unauthenticated();
return;
}
// Validate session with backend
final authResponse = await _authRepository.getMe();
state = AuthState.authenticated(
user: authResponse.user,
student: authResponse.student,
);
} catch (e) {
// If server returns AUTH_REQUIRED or network fails, unauthenticate
await _storage.clearSessionCookie();
state = AuthState.unauthenticated();
}
}
Future<void> login(String email, String password) async {
state = AuthState.loading();
try {
final authResponse = await _authRepository.login(
email: email,
password: password,
);
state = AuthState.authenticated(
user: authResponse.user,
student: authResponse.student,
);
} on ApiException catch (e) {
state = AuthState.unauthenticated(e.message);
rethrow;
} catch (e) {
state = AuthState.unauthenticated('An unexpected error occurred during login.');
rethrow;
}
}
Future<void> logout() async {
try {
await _authRepository.logout();
} catch (_) {
// Even if network logout fails, clear local session
} finally {
await _storage.clearSessionCookie();
state = AuthState.unauthenticated();
}
}
void handleSessionExpired() {
state = AuthState.unauthenticated('Your session has expired. Please login again.');
}
}
final authProvider = StateNotifierProvider<AuthNotifier, AuthState>((ref) {
final authRepo = ref.watch(authRepositoryProvider);
final storage = ref.watch(secureStorageProvider);
final client = ref.watch(apiClientProvider);
return AuthNotifier(
authRepository: authRepo,
storage: storage,
apiClient: client,
);
});