111 lines
3.3 KiB
Dart
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,
|
|
);
|
|
});
|