Deploy: 2026-05-24 23:27:32

This commit is contained in:
Hamza-Ayed
2026-05-24 23:27:32 +03:00
parent 2ceffc47d9
commit b20f457eaf
156 changed files with 8308 additions and 0 deletions

View File

@@ -0,0 +1,81 @@
import 'package:flutter_bloc/flutter_bloc.dart';
import '../../data/auth_repository.dart';
import 'auth_state.dart';
// English: AuthCubit manages authentication states.
// Arabic: يدير ملف الكيوبيت حالات المصادقة للمستخدمين.
//
// English: In GetX, you would use a GetxController and update observable variables.
// Arabic: في غيت إكس، كنت ستستخدم وحدة التحكم وتحدث المتغيرات المرصودة.
//
// English: In Bloc/Cubit, you inherit from Cubit and emit new immutable states.
// Arabic: في بلوك أو كيوبيت، ترث من الكيوبيت وترسل حالات غير قابلة للتعديل.
class AuthCubit extends Cubit<AuthState> {
final AuthRepository _authRepository;
// English: Constructor initializing the Cubit with the AuthInitial state.
// Arabic: منشئ يقوم بتهيئة الكيوبيت بالحالة الأولية.
AuthCubit(this._authRepository) : super(const AuthInitial());
// English: Check if the user is already authenticated on app launch.
// Arabic: التحقق مما إذا كان المستخدم مصدقًا عليه بالفعل عند تشغيل التطبيق.
Future<void> checkAuthStatus() async {
// English: Emit loading state while reading storage.
// Arabic: إرسال حالة التحميل أثناء قراءة وحدة التخزين.
emit(const AuthLoading());
try {
final user = await _authRepository.getCachedUser();
if (user != null) {
// English: Emit Authenticated state if user exists.
// Arabic: إرسال حالة مصادق عليه إذا كان المستخدم موجودًا.
emit(Authenticated(user));
} else {
// English: Emit Unauthenticated if no cached token is found.
// Arabic: إرسال حالة غير مصادق عليه في حال لم يتم العثور على رمز مخزن مؤقتًا.
emit(const Unauthenticated());
}
} catch (e) {
// English: Fail check, treat as unauthenticated.
// Arabic: فشل التحقق، يتم اعتباره غير مصادق عليه.
emit(const Unauthenticated());
}
}
// English: Perform login request using email and password.
// Arabic: إجراء طلب تسجيل الدخول باستخدام البريد الإلكتروني وكلمة المرور.
Future<void> login(String email, String password) async {
// English: Validate input locally before hitting the server.
// Arabic: التحقق من صحة المدخلات محليًا قبل الاتصال بالخادم.
if (email.isEmpty || password.isEmpty) {
emit(const AuthFailure('الرجاء إدخال جميع الحقول بشكل صحيح'));
return;
}
emit(const AuthLoading());
try {
final user = await _authRepository.login(email, password);
// English: Emit Authenticated state with user profile on success.
// Arabic: إرسال حالة مصادق عليه مع ملف تعريف المستخدم عند النجاح.
emit(Authenticated(user));
} catch (e) {
// English: Clean the error message to display to the user.
// Arabic: تنظيف رسالة الخطأ لعرضها على المستخدم.
final cleanError = e.toString().replaceAll('Exception: ', '');
emit(AuthFailure(cleanError));
}
}
// English: Clear user token and log out.
// Arabic: مسح رمز المستخدم وتسجيل الخروج.
Future<void> logout() async {
emit(const AuthLoading());
await _authRepository.logout();
// English: Emit Unauthenticated state to return user to login screen.
// Arabic: إرسال حالة غير مصادق عليه لإرجاع المستخدم إلى شاشة تسجيل الدخول.
emit(const Unauthenticated());
}
}

View File

@@ -0,0 +1,49 @@
import 'package:equatable/equatable.dart';
import '../../data/models/user_model.dart';
abstract class AuthState extends Equatable {
const AuthState();
@override
List<Object?> get props => [];
}
// English: Initial state when the app is checking for existing authentication tokens.
// Arabic: الحالة الأولية عندما يقوم التطبيق بالتحقق من وجود رموز مصادقة حالية.
class AuthInitial extends AuthState {
const AuthInitial();
}
// English: Loading state shown during credential verification or API login request.
// Arabic: حالة التحميل المعروضة أثناء التحقق من بيانات الاعتماد أو طلب تسجيل الدخول.
class AuthLoading extends AuthState {
const AuthLoading();
}
// English: State emitted when the user successfully authenticates. Contains user details.
// Arabic: الحالة المرسلة عندما ينجح المستخدم في المصادقة. تحتوي على تفاصيل المستخدم.
class Authenticated extends AuthState {
final UserModel user;
const Authenticated(this.user);
@override
List<Object?> get props => [user];
}
// English: State emitted when the user is not authenticated or has logged out.
// Arabic: الحالة المرسلة عندما لا يكون المستخدم مصدقًا أو قد سجل خروجه.
class Unauthenticated extends AuthState {
const Unauthenticated();
}
// English: Failure state containing the error message in case authentication fails.
// Arabic: حالة الفشل التي تحتوي على رسالة الخطأ في حالة فشل المصادقة.
class AuthFailure extends AuthState {
final String errorMessage;
const AuthFailure(this.errorMessage);
@override
List<Object?> get props => [errorMessage];
}

View File

@@ -0,0 +1,243 @@
import 'package:flutter/material.dart';
import 'package:flutter_bloc/flutter_bloc.dart';
import '../cubit/auth_cubit.dart';
import '../cubit/auth_state.dart';
class LoginScreen extends StatefulWidget {
const LoginScreen({super.key});
@override
State<LoginScreen> createState() => _LoginScreenState();
}
class _LoginScreenState extends State<LoginScreen> {
// English: Form key to manage state and invoke validation.
// Arabic: مفتاح النموذج لإدارة الحالة واستدعاء التحقق من الصحة.
final _formKey = GlobalKey<FormState>();
final _emailController = TextEditingController();
final _passwordController = TextEditingController();
bool _isPasswordVisible = false;
@override
void dispose() {
_emailController.dispose();
_passwordController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
// English: We use a dark, premium theme with purple/blue gradients.
// Arabic: نحن نستخدم سمة داكنة ومميزة مع تدرجات أرجوانية وزرقاء.
return Scaffold(
body: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
colors: [Color(0xFF0F0C20), Color(0xFF15102A), Color(0xFF0A0814)],
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
),
),
child: SafeArea(
child: Center(
child: SingleChildScrollView(
padding: const EdgeInsets.symmetric(horizontal: 24.0),
child: Form(
key: _formKey,
child: BlocConsumer<AuthCubit, AuthState>(
listener: (context, state) {
if (state is AuthFailure) {
// English: Show failure message as SnackBar when state is AuthFailure.
// Arabic: عرض رسالة الفشل كشريط وجبة خفيفة عندما تكون الحالة فشل المصادقة.
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text(
state.errorMessage,
style: const TextStyle(color: Colors.white, fontFamily: 'Outfit'),
),
backgroundColor: Colors.redAccent,
behavior: SnackBarBehavior.floating,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10),
),
),
);
}
},
builder: (context, state) {
return Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// English: Logo placeholder with dynamic design.
// Arabic: رمز شعار تجريبي بتصميم ديناميكي.
Container(
padding: const EdgeInsets.all(16),
decoration: BoxDecoration(
shape: BoxShape.circle,
color: Colors.deepPurple.withOpacity(0.2),
),
child: const Icon(
Icons.security,
size: 64,
color: Colors.purpleAccent,
),
),
const SizedBox(height: 24),
const Text(
'نـبـيـه',
style: TextStyle(
fontSize: 36,
fontWeight: FontWeight.bold,
color: Colors.white,
letterSpacing: 2,
),
),
const SizedBox(height: 8),
Text(
'تسجيل الدخول للمشرفين والعملاء',
style: TextStyle(
fontSize: 14,
color: Colors.white.withOpacity(0.6),
),
),
const SizedBox(height: 40),
// English: Email text input with custom borders and icons.
// Arabic: إدخال نص البريد الإلكتروني مع حدود وأيقونات مخصصة.
TextFormField(
controller: _emailController,
keyboardType: TextInputType.emailAddress,
style: const TextStyle(color: Colors.white),
decoration: InputDecoration(
labelText: 'البريد الإلكتروني',
labelStyle: TextStyle(color: Colors.white.withOpacity(0.6)),
prefixIcon: const Icon(Icons.email_outlined, color: Colors.purpleAccent),
filled: true,
fillColor: Colors.white.withOpacity(0.05),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: Colors.white.withOpacity(0.1)),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: Colors.purpleAccent, width: 2),
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
),
validator: (value) {
if (value == null || value.trim().isEmpty) {
return 'الرجاء إدخال البريد الإلكتروني';
}
if (!RegExp(r'^[\w-\.]+@([\w-]+\.)+[\w-]{2,4}$').hasMatch(value)) {
return 'الرجاء إدخال بريد إلكتروني صالح';
}
return null;
},
),
const SizedBox(height: 20),
// English: Password text input with visibility toggle.
// Arabic: إدخال نص كلمة المرور مع إمكانية التبديل بين إظهارها وإخفائها.
TextFormField(
controller: _passwordController,
obscureText: !_isPasswordVisible,
style: const TextStyle(color: Colors.white),
decoration: InputDecoration(
labelText: 'كلمة المرور',
labelStyle: TextStyle(color: Colors.white.withOpacity(0.6)),
prefixIcon: const Icon(Icons.lock_outline, color: Colors.purpleAccent),
suffixIcon: IconButton(
icon: Icon(
_isPasswordVisible ? Icons.visibility : Icons.visibility_off,
color: Colors.white.withOpacity(0.6),
),
onPressed: () {
setState(() {
_isPasswordVisible = !_isPasswordVisible;
});
},
),
filled: true,
fillColor: Colors.white.withOpacity(0.05),
enabledBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: BorderSide(color: Colors.white.withOpacity(0.1)),
),
focusedBorder: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
borderSide: const BorderSide(color: Colors.purpleAccent, width: 2),
),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(12),
),
),
validator: (value) {
if (value == null || value.isEmpty) {
return 'الرجاء إدخال كلمة المرور';
}
return null;
},
),
const SizedBox(height: 40),
// English: Submit button. Shows progress bar during login request.
// Arabic: زر الإرسال. يعرض شريط تقدم تحميل البيانات أثناء المصادقة.
if (state is AuthLoading)
const CircularProgressIndicator(color: Colors.purpleAccent)
else
GestureDetector(
onTap: () {
if (_formKey.currentState!.validate()) {
// English: Dispatch login method to AuthCubit.
// Arabic: استدعاء دالة تسجيل الدخول إلى الكيوبيت.
context.read<AuthCubit>().login(
_emailController.text.trim(),
_passwordController.text,
);
}
},
child: Container(
width: double.infinity,
height: 56,
decoration: BoxDecoration(
gradient: const LinearGradient(
colors: [Colors.purpleAccent, Colors.deepPurpleAccent],
begin: Alignment.centerLeft,
end: Alignment.centerRight,
),
borderRadius: BorderRadius.circular(12),
boxShadow: [
BoxShadow(
color: Colors.purpleAccent.withOpacity(0.3),
blurRadius: 12,
offset: const Offset(0, 4),
)
],
),
child: const Center(
child: Text(
'تسجيل الدخول',
style: TextStyle(
color: Colors.white,
fontSize: 16,
fontWeight: FontWeight.bold,
),
),
),
),
),
],
);
},
),
),
),
),
),
),
);
}
}