Update Saqel Platform: 2026-08-31 01:52:23

This commit is contained in:
Hamza-Ayed
2026-08-31 01:52:24 +03:00
parent 4e29915383
commit ccf5f13a09
419 changed files with 19777 additions and 41 deletions
@@ -0,0 +1,187 @@
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 = 'tawjihi_2008',
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());
}
}
@@ -0,0 +1,94 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../core/utils/app_logger.dart';
import '../../data/models/lesson_model.dart';
import '../../data/repositories/app_repositories.dart';
// --- STUDENT CUBIT ---
abstract class StudentState {}
class StudentInitial extends StudentState {}
class StudentLoading extends StudentState {}
class StudentLoaded extends StudentState {
final List<LessonModel> lessons;
final double readinessScore;
StudentLoaded({required this.lessons, required this.readinessScore});
}
class StudentEmpty extends StudentState {}
class StudentError extends StudentState {
final String message;
StudentError(this.message);
}
class StudentCubit extends Cubit<StudentState> {
final StudentRepository _studentRepo;
StudentCubit({StudentRepository? studentRepo})
: _studentRepo = studentRepo ?? StudentRepository(),
super(StudentInitial());
Future<void> fetchLessons({String? gradeLevel, String? stream, double initialReadiness = 0.0}) async {
AppLogger.log('Fetching lessons (Grade: $gradeLevel, Stream: $stream)...', tag: 'STUDENT_CUBIT');
emit(StudentLoading());
try {
final lessons = await _studentRepo.getLessons(gradeLevel: gradeLevel, stream: stream);
AppLogger.log('Fetched ${lessons.length} lessons from API', tag: 'STUDENT_CUBIT');
if (lessons.isEmpty) {
emit(StudentEmpty());
} else {
emit(StudentLoaded(lessons: lessons, readinessScore: initialReadiness));
}
} catch (e) {
AppLogger.error('Fetch lessons failed', error: e, tag: 'STUDENT_CUBIT');
emit(StudentError(e.toString()));
}
}
}
// --- GUARDIAN CUBIT ---
abstract class GuardianState {}
class GuardianInitial extends GuardianState {}
class GuardianLoading extends GuardianState {}
class GuardianLoaded extends GuardianState {
final List<GuardianChildModel> children;
final int selectedChildIndex;
GuardianLoaded({required this.children, this.selectedChildIndex = 0});
}
class GuardianEmpty extends GuardianState {}
class GuardianError extends GuardianState {
final String message;
GuardianError(this.message);
}
class GuardianCubit extends Cubit<GuardianState> {
final GuardianRepository _guardianRepo;
GuardianCubit({GuardianRepository? guardianRepo})
: _guardianRepo = guardianRepo ?? GuardianRepository(),
super(GuardianInitial());
Future<void> fetchDashboard() async {
AppLogger.log('Fetching guardian dashboard children from API...', tag: 'GUARDIAN_CUBIT');
emit(GuardianLoading());
try {
final children = await _guardianRepo.getDashboardChildren();
AppLogger.log('Fetched ${children.length} linked children from API', tag: 'GUARDIAN_CUBIT');
if (children.isEmpty) {
emit(GuardianEmpty());
} else {
emit(GuardianLoaded(children: children, selectedChildIndex: 0));
}
} catch (e) {
AppLogger.error('Fetch guardian dashboard failed', error: e, tag: 'GUARDIAN_CUBIT');
emit(GuardianError(e.toString()));
}
}
void selectChild(int index) {
final currentState = state;
if (currentState is GuardianLoaded && index >= 0 && index < currentState.children.length) {
AppLogger.log('Selected child: ${currentState.children[index].name}', tag: 'GUARDIAN_CUBIT');
emit(GuardianLoaded(children: currentState.children, selectedChildIndex: index));
}
}
}