188 lines
7.0 KiB
Dart
188 lines
7.0 KiB
Dart
import 'package:flutter_bloc/flutter_bloc.dart';
|
|
import '../../core/services/storage_service.dart';
|
|
import '../../core/utils/app_logger.dart';
|
|
import '../../data/models/user_model.dart';
|
|
import '../../data/repositories/app_repositories.dart';
|
|
|
|
abstract class AuthState {}
|
|
|
|
class AuthInitial extends AuthState {}
|
|
class AuthLoading extends AuthState {}
|
|
class AuthOtpSent extends AuthState {
|
|
final String phoneNumber;
|
|
final String role;
|
|
AuthOtpSent({required this.phoneNumber, required this.role});
|
|
}
|
|
class AuthNeedsNationalId extends AuthState {
|
|
final String identityToken;
|
|
final String phoneNumber;
|
|
AuthNeedsNationalId({required this.identityToken, required this.phoneNumber});
|
|
}
|
|
class AuthNeedsOnboarding extends AuthState {
|
|
final String identityToken;
|
|
final String nationalId;
|
|
AuthNeedsOnboarding({required this.identityToken, required this.nationalId});
|
|
}
|
|
class Authenticated extends AuthState {
|
|
final UserModel user;
|
|
final String activeRole;
|
|
Authenticated({required this.user, required this.activeRole});
|
|
}
|
|
class Unauthenticated extends AuthState {}
|
|
class AuthError extends AuthState {
|
|
final String message;
|
|
AuthError(this.message);
|
|
}
|
|
|
|
class AuthCubit extends Cubit<AuthState> {
|
|
final AuthRepository _authRepo;
|
|
final StorageService _storage;
|
|
|
|
AuthCubit({AuthRepository? authRepo, StorageService? storage})
|
|
: _authRepo = authRepo ?? AuthRepository(),
|
|
_storage = storage ?? StorageService(),
|
|
super(AuthInitial());
|
|
|
|
Future<void> checkSession() async {
|
|
AppLogger.log('Checking local session...', tag: 'AUTH_CUBIT');
|
|
emit(AuthLoading());
|
|
try {
|
|
final token = await _storage.getToken();
|
|
if (token == null || token.isEmpty) {
|
|
AppLogger.log('No token found -> Unauthenticated', tag: 'AUTH_CUBIT');
|
|
emit(Unauthenticated());
|
|
return;
|
|
}
|
|
|
|
AppLogger.log('Token found -> Validating with /api/auth/me...', tag: 'AUTH_CUBIT');
|
|
final user = await _authRepo.getMe();
|
|
if (user != null) {
|
|
final role = await _storage.getActiveRole() ?? user.role;
|
|
AppLogger.log('User authenticated: ${user.name} (Role: $role)', tag: 'AUTH_CUBIT');
|
|
emit(Authenticated(user: user, activeRole: role));
|
|
} else {
|
|
AppLogger.log('Failed to fetch user -> Unauthenticated', tag: 'AUTH_CUBIT');
|
|
emit(Unauthenticated());
|
|
}
|
|
} catch (e) {
|
|
AppLogger.log('Session check error: $e', tag: 'AUTH_CUBIT');
|
|
emit(Unauthenticated());
|
|
}
|
|
}
|
|
|
|
Future<void> sendOtp(String phoneNumber, {String role = 'student'}) async {
|
|
AppLogger.log('Requesting OTP for $phoneNumber (Role: $role)...', tag: 'AUTH_CUBIT');
|
|
emit(AuthLoading());
|
|
try {
|
|
final cleanPhone = phoneNumber.trim();
|
|
final res = await _authRepo.requestOtp(cleanPhone, role: role);
|
|
if (res['status'] == 'success') {
|
|
AppLogger.log('OTP sent successfully to $cleanPhone', tag: 'AUTH_CUBIT');
|
|
emit(AuthOtpSent(phoneNumber: cleanPhone, role: role));
|
|
} else {
|
|
final msg = res['message']?.toString() ?? 'فشل إرسال رمز التحقق';
|
|
AppLogger.log('OTP request rejected: $msg', tag: 'AUTH_CUBIT');
|
|
emit(AuthError(msg));
|
|
}
|
|
} catch (e) {
|
|
AppLogger.error('OTP Send Exception', error: e, tag: 'AUTH_CUBIT');
|
|
emit(AuthError(e.toString()));
|
|
}
|
|
}
|
|
|
|
Future<void> verifyOtp(String phoneNumber, String otp, {String role = 'student'}) async {
|
|
AppLogger.log('Verifying OTP for $phoneNumber...', tag: 'AUTH_CUBIT');
|
|
emit(AuthLoading());
|
|
try {
|
|
final res = await _authRepo.verifyOtp(phoneNumber, otp.trim(), role: role);
|
|
final data = res['data'];
|
|
|
|
if (data != null && data['token'] != null) {
|
|
final user = UserModel.fromJson(Map<String, dynamic>.from(data['user']));
|
|
AppLogger.log('OTP verified -> Authenticated: ${user.name}', tag: 'AUTH_CUBIT');
|
|
emit(Authenticated(user: user, activeRole: role));
|
|
} else if (data != null && data['identity_token'] != null) {
|
|
AppLogger.log('OTP verified -> Requires National ID', tag: 'AUTH_CUBIT');
|
|
emit(AuthNeedsNationalId(
|
|
identityToken: data['identity_token'].toString(),
|
|
phoneNumber: phoneNumber,
|
|
));
|
|
} else {
|
|
final msg = res['message']?.toString() ?? 'رمز التحقق غير صحيح';
|
|
AppLogger.log('OTP Verification Failed: $msg', tag: 'AUTH_CUBIT');
|
|
emit(AuthError(msg));
|
|
}
|
|
} catch (e) {
|
|
AppLogger.error('OTP Verify Exception', error: e, tag: 'AUTH_CUBIT');
|
|
emit(AuthError(e.toString()));
|
|
}
|
|
}
|
|
|
|
Future<void> verifyNationalId(String identityToken, String nationalId) async {
|
|
AppLogger.log('Verifying National ID: $nationalId...', tag: 'AUTH_CUBIT');
|
|
emit(AuthLoading());
|
|
try {
|
|
final res = await _authRepo.verifyNationalId(identityToken, nationalId.trim());
|
|
final data = res['data'];
|
|
|
|
if (data != null && data['requires_onboarding'] == true) {
|
|
AppLogger.log('National ID not registered -> Requires Onboarding', tag: 'AUTH_CUBIT');
|
|
emit(AuthNeedsOnboarding(
|
|
identityToken: identityToken,
|
|
nationalId: nationalId.trim(),
|
|
));
|
|
} else if (data != null && data['user'] != null) {
|
|
final user = UserModel.fromJson(Map<String, dynamic>.from(data['user']));
|
|
AppLogger.log('National ID verified -> Student authenticated: ${user.name}', tag: 'AUTH_CUBIT');
|
|
emit(Authenticated(user: user, activeRole: 'student'));
|
|
} else {
|
|
emit(AuthError(res['message']?.toString() ?? 'فشل التحقق من الرقم الوطني'));
|
|
}
|
|
} catch (e) {
|
|
AppLogger.error('National ID Exception', error: e, tag: 'AUTH_CUBIT');
|
|
emit(AuthError(e.toString()));
|
|
}
|
|
}
|
|
|
|
Future<void> setupProfile({
|
|
required String identityToken,
|
|
required String fullName,
|
|
required String nationalId,
|
|
String gradeLevel = 'grade_10',
|
|
String stream = 'scientific',
|
|
}) async {
|
|
AppLogger.log('Setting up profile for $fullName ($nationalId)...', tag: 'AUTH_CUBIT');
|
|
emit(AuthLoading());
|
|
try {
|
|
final user = await _authRepo.setupStudentProfile(
|
|
identityToken: identityToken,
|
|
fullName: fullName.trim(),
|
|
nationalId: nationalId.trim(),
|
|
gradeLevel: gradeLevel,
|
|
stream: stream,
|
|
);
|
|
AppLogger.log('Profile setup complete -> Student authenticated: ${user.name}', tag: 'AUTH_CUBIT');
|
|
emit(Authenticated(user: user, activeRole: 'student'));
|
|
} catch (e) {
|
|
AppLogger.error('Setup Profile Exception', error: e, tag: 'AUTH_CUBIT');
|
|
emit(AuthError(e.toString()));
|
|
}
|
|
}
|
|
|
|
Future<void> switchRole(String newRole) async {
|
|
final currentState = state;
|
|
if (currentState is Authenticated) {
|
|
AppLogger.log('Switching role from ${currentState.activeRole} to $newRole', tag: 'AUTH_CUBIT');
|
|
await _storage.saveActiveRole(newRole);
|
|
emit(Authenticated(user: currentState.user, activeRole: newRole));
|
|
}
|
|
}
|
|
|
|
Future<void> logout() async {
|
|
AppLogger.log('Logging out user...', tag: 'AUTH_CUBIT');
|
|
emit(AuthLoading());
|
|
await _authRepo.logout();
|
|
emit(Unauthenticated());
|
|
}
|
|
}
|