1111
This commit is contained in:
113
lib/LOGIN_SETUP.md
Normal file
113
lib/LOGIN_SETUP.md
Normal file
@@ -0,0 +1,113 @@
|
||||
# إعداد تسجيل الدخول (Login Setup)
|
||||
|
||||
تم ربط عملية تسجيل الدخول مع السيرفر بنجاح.
|
||||
|
||||
## الملفات المُنشأة
|
||||
|
||||
### 1. Data Layer
|
||||
- `data/dto/login_dto.dart` - DTO لإرسال بيانات تسجيل الدخول
|
||||
- `data/dto/login_response_dto.dart` - DTO لاستقبال استجابة تسجيل الدخول
|
||||
- `data/datasources/auth_remote_data_source.dart` - مصدر البيانات البعيدة لتسجيل الدخول
|
||||
|
||||
### 2. Domain Layer
|
||||
- `domain/models/login_request.dart` - نموذج طلب تسجيل الدخول
|
||||
- `domain/models/login_response_model.dart` - نموذج استجابة تسجيل الدخول
|
||||
- `domain/repositories/auth_repository.dart` - واجهة Repository
|
||||
- `domain/usecases/login_usecase.dart` - Use Case لتسجيل الدخول
|
||||
|
||||
### 3. Data Implementation
|
||||
- `data/repositories/auth_repository_impl.dart` - تطبيق Repository
|
||||
|
||||
### 4. Core Updates
|
||||
- `core/network/api_client.dart` - تم تحديثه لإضافة token تلقائياً في الطلبات
|
||||
- `core/di/injection_container.dart` - تم تسجيل جميع التبعيات
|
||||
|
||||
### 5. UI Updates
|
||||
- `widgets/auth_form.dart` - تم تحديثه لاستخدام LoginUseCase
|
||||
|
||||
## كيفية الاستخدام
|
||||
|
||||
### في الكود:
|
||||
```dart
|
||||
// الحصول على LoginUseCase من dependency injection
|
||||
final loginUseCase = sl<LoginUseCase>();
|
||||
|
||||
// إنشاء طلب تسجيل الدخول
|
||||
final request = LoginRequest(
|
||||
phoneNumber: '7856121557',
|
||||
password: 'qaqaqa',
|
||||
);
|
||||
|
||||
// استدعاء UseCase
|
||||
final result = await loginUseCase(request);
|
||||
|
||||
// التعامل مع النتيجة
|
||||
result.fold(
|
||||
(failure) {
|
||||
// معالجة الخطأ
|
||||
print('خطأ: ${failure.message}');
|
||||
},
|
||||
(response) {
|
||||
// معالجة النجاح
|
||||
if (response.isSuccess) {
|
||||
print('تم تسجيل الدخول بنجاح');
|
||||
print('Token: ${response.data?.token}');
|
||||
print('اسم المستخدم: ${response.data?.fullName}');
|
||||
}
|
||||
},
|
||||
);
|
||||
```
|
||||
|
||||
## API Endpoint
|
||||
|
||||
- **URL**: `https://hrm.go.iq/api/Auth/login`
|
||||
- **Method**: POST
|
||||
- **Headers**:
|
||||
- `Content-Type: application/json`
|
||||
- `accept: text/plain`
|
||||
|
||||
### Request Body:
|
||||
```json
|
||||
{
|
||||
"phoneNumber": "7856121557",
|
||||
"password": "qaqaqa"
|
||||
}
|
||||
```
|
||||
|
||||
### Response:
|
||||
```json
|
||||
{
|
||||
"statusCode": 200,
|
||||
"isSuccess": true,
|
||||
"message": "Login Successful",
|
||||
"data": {
|
||||
"token": "...",
|
||||
"id": "...",
|
||||
"username": "...",
|
||||
"fullName": "...",
|
||||
"role": "...",
|
||||
"email": "...",
|
||||
"phoneNumber": "...",
|
||||
"permissions": [...]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## المميزات
|
||||
|
||||
1. ✅ حفظ Token تلقائياً في SharedPreferences
|
||||
2. ✅ إضافة Token تلقائياً في جميع الطلبات عبر ApiClient interceptor
|
||||
3. ✅ معالجة الأخطاء بشكل شامل (Network, Server, Validation)
|
||||
4. ✅ رسائل خطأ بالعربية
|
||||
5. ✅ Loading state في واجهة المستخدم
|
||||
6. ✅ التحقق من صحة المدخلات
|
||||
|
||||
## الخطوات التالية
|
||||
|
||||
1. قم بتشغيل `flutter pub get` لتثبيت الحزم
|
||||
2. اختبر تسجيل الدخول باستخدام البيانات الصحيحة
|
||||
3. يمكنك إضافة المزيد من الميزات مثل:
|
||||
- حفظ بيانات المستخدم الكاملة
|
||||
- تذكر المستخدم (Remember Me)
|
||||
- تسجيل الخروج (Logout)
|
||||
- تحديث Token تلقائياً عند انتهاء الصلاحية
|
||||
@@ -2,6 +2,11 @@ import 'package:dio/dio.dart';
|
||||
import 'package:get_it/get_it.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import '../network/api_client.dart';
|
||||
import '../../data/datasources/auth_remote_data_source.dart';
|
||||
import '../../data/datasources/user_local_data_source.dart';
|
||||
import '../../data/repositories/auth_repository_impl.dart';
|
||||
import '../../domain/repositories/auth_repository.dart';
|
||||
import '../../domain/usecases/login_usecase.dart';
|
||||
|
||||
final sl = GetIt.instance;
|
||||
|
||||
@@ -14,26 +19,29 @@ Future<void> initializeDependencies() async {
|
||||
sl.registerLazySingleton<SharedPreferences>(() => sharedPreferences);
|
||||
|
||||
// Core
|
||||
sl.registerLazySingleton<ApiClient>(() => ApiClient(dio: sl()));
|
||||
sl.registerLazySingleton<ApiClient>(
|
||||
() => ApiClient(dio: sl(), sharedPreferences: sl()),
|
||||
);
|
||||
|
||||
// Data sources will be registered here
|
||||
// Example:
|
||||
// sl.registerLazySingleton<AuthRemoteDataSource>(
|
||||
// () => AuthRemoteDataSourceImpl(apiClient: sl()),
|
||||
// );
|
||||
// Data sources
|
||||
sl.registerLazySingleton<AuthRemoteDataSource>(
|
||||
() => AuthRemoteDataSourceImpl(apiClient: sl()),
|
||||
);
|
||||
|
||||
sl.registerLazySingleton<UserLocalDataSource>(
|
||||
() => UserLocalDataSourceImpl(sharedPreferences: sl()),
|
||||
);
|
||||
|
||||
// Repositories will be registered here
|
||||
// Example:
|
||||
// sl.registerLazySingleton<AuthRepository>(
|
||||
// () => AuthRepositoryImpl(
|
||||
// remoteDataSource: sl(),
|
||||
// localDataSource: sl(),
|
||||
// ),
|
||||
// );
|
||||
// Repositories
|
||||
sl.registerLazySingleton<AuthRepository>(
|
||||
() => AuthRepositoryImpl(
|
||||
remoteDataSource: sl(),
|
||||
localDataSource: sl(),
|
||||
),
|
||||
);
|
||||
|
||||
// Use cases will be registered here
|
||||
// Example:
|
||||
// sl.registerLazySingleton(() => LoginUseCase(repository: sl()));
|
||||
// Use cases
|
||||
sl.registerLazySingleton(() => LoginUseCase(repository: sl()));
|
||||
|
||||
// Blocs will be registered here
|
||||
// Example:
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
|
||||
class ApiClient {
|
||||
final Dio dio;
|
||||
static const String baseUrl = 'YOUR_API_BASE_URL_HERE';
|
||||
final SharedPreferences? sharedPreferences;
|
||||
static const String baseUrl = 'https://hrm.go.iq/api';
|
||||
static const String _tokenKey = 'user_token';
|
||||
|
||||
ApiClient({required this.dio}) {
|
||||
ApiClient({required this.dio, this.sharedPreferences}) {
|
||||
dio.options = BaseOptions(
|
||||
baseUrl: baseUrl,
|
||||
connectTimeout: const Duration(seconds: 30),
|
||||
@@ -15,6 +18,20 @@ class ApiClient {
|
||||
},
|
||||
);
|
||||
|
||||
// Add interceptor to add token to requests
|
||||
dio.interceptors.add(
|
||||
InterceptorsWrapper(
|
||||
onRequest: (options, handler) async {
|
||||
// Get token from SharedPreferences
|
||||
final token = sharedPreferences?.getString(_tokenKey);
|
||||
if (token != null && token.isNotEmpty) {
|
||||
options.headers['Authorization'] = 'Bearer $token';
|
||||
}
|
||||
return handler.next(options);
|
||||
},
|
||||
),
|
||||
);
|
||||
|
||||
// Add interceptors for logging and error handling
|
||||
dio.interceptors.add(
|
||||
LogInterceptor(
|
||||
|
||||
77
lib/data/datasources/auth_remote_data_source.dart
Normal file
77
lib/data/datasources/auth_remote_data_source.dart
Normal file
@@ -0,0 +1,77 @@
|
||||
import 'package:dio/dio.dart';
|
||||
import '../../core/error/exceptions.dart';
|
||||
import '../../core/network/api_client.dart';
|
||||
import '../dto/login_dto.dart';
|
||||
import '../dto/login_response_dto.dart';
|
||||
|
||||
abstract class AuthRemoteDataSource {
|
||||
Future<LoginResponseDto> login(LoginDto dto);
|
||||
}
|
||||
|
||||
class AuthRemoteDataSourceImpl implements AuthRemoteDataSource {
|
||||
final ApiClient apiClient;
|
||||
|
||||
AuthRemoteDataSourceImpl({required this.apiClient});
|
||||
|
||||
@override
|
||||
Future<LoginResponseDto> login(LoginDto dto) async {
|
||||
try {
|
||||
final response = await apiClient.post(
|
||||
'/Auth/login',
|
||||
data: dto.toJson(),
|
||||
);
|
||||
|
||||
if (response.statusCode == 200 || response.statusCode == 201) {
|
||||
final responseData = response.data;
|
||||
|
||||
if (responseData is Map<String, dynamic>) {
|
||||
return LoginResponseDto.fromJson(responseData);
|
||||
} else {
|
||||
throw ServerException(
|
||||
message: 'استجابة غير صحيحة من الخادم',
|
||||
statusCode: response.statusCode,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
throw ServerException(
|
||||
message: 'فشل تسجيل الدخول',
|
||||
statusCode: response.statusCode,
|
||||
);
|
||||
}
|
||||
} on DioException catch (e) {
|
||||
if (e.type == DioExceptionType.connectionTimeout ||
|
||||
e.type == DioExceptionType.receiveTimeout) {
|
||||
throw NetworkException(message: 'انتهت مهلة الاتصال');
|
||||
} else if (e.type == DioExceptionType.connectionError) {
|
||||
throw NetworkException(message: 'لا يوجد اتصال بالانترنيت');
|
||||
} else if (e.response?.statusCode == 500) {
|
||||
throw ServerException(message: 'خطأ في الخادم يرجى المحاولة لاحقا');
|
||||
} else if (e.response != null) {
|
||||
final message = e.response?.data?['message'] ??
|
||||
e.response?.data?['error'] ??
|
||||
'فشل تسجيل الدخول';
|
||||
|
||||
// Check for invalid credentials
|
||||
final customMessage =
|
||||
message.toString().toLowerCase().contains('invalid') ||
|
||||
message.toString().toLowerCase().contains('incorrect')
|
||||
? 'رقم الهاتف أو كلمة المرور غير صحيحة'
|
||||
: message.toString().toLowerCase().contains('not found')
|
||||
? 'المستخدم غير موجود'
|
||||
: message;
|
||||
|
||||
throw ServerException(
|
||||
message: customMessage,
|
||||
statusCode: e.response?.statusCode,
|
||||
);
|
||||
} else {
|
||||
throw NetworkException(message: 'خطأ في الانترنيت يرجى المحاولة لاحقا');
|
||||
}
|
||||
} catch (e) {
|
||||
if (e is ServerException || e is NetworkException) {
|
||||
rethrow;
|
||||
}
|
||||
throw ServerException(message: 'خطأ غير متوقع');
|
||||
}
|
||||
}
|
||||
}
|
||||
16
lib/data/dto/login_dto.dart
Normal file
16
lib/data/dto/login_dto.dart
Normal file
@@ -0,0 +1,16 @@
|
||||
class LoginDto {
|
||||
final String phoneNumber;
|
||||
final String password;
|
||||
|
||||
LoginDto({
|
||||
required this.phoneNumber,
|
||||
required this.password,
|
||||
});
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'phoneNumber': phoneNumber,
|
||||
'password': password,
|
||||
};
|
||||
}
|
||||
}
|
||||
85
lib/data/dto/login_response_dto.dart
Normal file
85
lib/data/dto/login_response_dto.dart
Normal file
@@ -0,0 +1,85 @@
|
||||
class LoginResponseDto {
|
||||
final int statusCode;
|
||||
final bool isSuccess;
|
||||
final String message;
|
||||
final LoginDataDto? data;
|
||||
|
||||
LoginResponseDto({
|
||||
required this.statusCode,
|
||||
required this.isSuccess,
|
||||
required this.message,
|
||||
this.data,
|
||||
});
|
||||
|
||||
factory LoginResponseDto.fromJson(Map<String, dynamic> json) {
|
||||
return LoginResponseDto(
|
||||
statusCode: json['statusCode'] ?? 0,
|
||||
isSuccess: json['isSuccess'] ?? false,
|
||||
message: json['message'] ?? '',
|
||||
data: json['data'] != null ? LoginDataDto.fromJson(json['data']) : null,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'statusCode': statusCode,
|
||||
'isSuccess': isSuccess,
|
||||
'message': message,
|
||||
'data': data?.toJson(),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
class LoginDataDto {
|
||||
final String? token;
|
||||
final String? id;
|
||||
final String? employeeId;
|
||||
final String? username;
|
||||
final String? fullName;
|
||||
final String? role;
|
||||
final String? email;
|
||||
final String? phoneNumber;
|
||||
final List<String>? permissions;
|
||||
|
||||
LoginDataDto({
|
||||
this.token,
|
||||
this.id,
|
||||
this.employeeId,
|
||||
this.username,
|
||||
this.fullName,
|
||||
this.role,
|
||||
this.email,
|
||||
this.phoneNumber,
|
||||
this.permissions,
|
||||
});
|
||||
|
||||
factory LoginDataDto.fromJson(Map<String, dynamic> json) {
|
||||
return LoginDataDto(
|
||||
token: json['token'],
|
||||
id: json['id'],
|
||||
employeeId: json['employeeId'],
|
||||
username: json['username'],
|
||||
fullName: json['fullName'],
|
||||
role: json['role'],
|
||||
email: json['email'],
|
||||
phoneNumber: json['phoneNumber'],
|
||||
permissions: json['permissions'] != null
|
||||
? List<String>.from(json['permissions'])
|
||||
: null,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> toJson() {
|
||||
return {
|
||||
'token': token,
|
||||
'id': id,
|
||||
'employeeId': employeeId,
|
||||
'username': username,
|
||||
'fullName': fullName,
|
||||
'role': role,
|
||||
'email': email,
|
||||
'phoneNumber': phoneNumber,
|
||||
'permissions': permissions,
|
||||
};
|
||||
}
|
||||
}
|
||||
65
lib/data/repositories/auth_repository_impl.dart
Normal file
65
lib/data/repositories/auth_repository_impl.dart
Normal file
@@ -0,0 +1,65 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import '../../core/error/exceptions.dart';
|
||||
import '../../core/error/failures.dart';
|
||||
import '../datasources/auth_remote_data_source.dart';
|
||||
import '../datasources/user_local_data_source.dart';
|
||||
import '../dto/login_dto.dart';
|
||||
import '../dto/login_response_dto.dart';
|
||||
import '../../domain/models/login_request.dart';
|
||||
import '../../domain/models/login_response_model.dart';
|
||||
import '../../domain/repositories/auth_repository.dart';
|
||||
|
||||
class AuthRepositoryImpl implements AuthRepository {
|
||||
final AuthRemoteDataSource remoteDataSource;
|
||||
final UserLocalDataSource localDataSource;
|
||||
|
||||
AuthRepositoryImpl({
|
||||
required this.remoteDataSource,
|
||||
required this.localDataSource,
|
||||
});
|
||||
|
||||
@override
|
||||
Future<Either<Failure, LoginResponseModel>> login(LoginRequest request) async {
|
||||
try {
|
||||
final dto = LoginDto(
|
||||
phoneNumber: request.phoneNumber,
|
||||
password: request.password,
|
||||
);
|
||||
|
||||
final responseDto = await remoteDataSource.login(dto);
|
||||
|
||||
// Cache the token locally
|
||||
if (responseDto.data?.token != null) {
|
||||
await localDataSource.cacheUserToken(responseDto.data!.token!);
|
||||
}
|
||||
|
||||
// Convert DTO to Model
|
||||
final responseModel = LoginResponseModel(
|
||||
statusCode: responseDto.statusCode,
|
||||
isSuccess: responseDto.isSuccess,
|
||||
message: responseDto.message,
|
||||
data: responseDto.data != null
|
||||
? LoginDataModel(
|
||||
token: responseDto.data!.token,
|
||||
id: responseDto.data!.id,
|
||||
employeeId: responseDto.data!.employeeId,
|
||||
username: responseDto.data!.username,
|
||||
fullName: responseDto.data!.fullName,
|
||||
role: responseDto.data!.role,
|
||||
email: responseDto.data!.email,
|
||||
phoneNumber: responseDto.data!.phoneNumber,
|
||||
permissions: responseDto.data!.permissions,
|
||||
)
|
||||
: null,
|
||||
);
|
||||
|
||||
return Right(responseModel);
|
||||
} on ServerException catch (e) {
|
||||
return Left(ServerFailure(e.message));
|
||||
} on NetworkException catch (e) {
|
||||
return Left(NetworkFailure(e.message));
|
||||
} catch (e) {
|
||||
return Left(ServerFailure('خطأ غير متوقع: $e'));
|
||||
}
|
||||
}
|
||||
}
|
||||
9
lib/domain/models/login_request.dart
Normal file
9
lib/domain/models/login_request.dart
Normal file
@@ -0,0 +1,9 @@
|
||||
class LoginRequest {
|
||||
final String phoneNumber;
|
||||
final String password;
|
||||
|
||||
LoginRequest({
|
||||
required this.phoneNumber,
|
||||
required this.password,
|
||||
});
|
||||
}
|
||||
37
lib/domain/models/login_response_model.dart
Normal file
37
lib/domain/models/login_response_model.dart
Normal file
@@ -0,0 +1,37 @@
|
||||
class LoginResponseModel {
|
||||
final int statusCode;
|
||||
final bool isSuccess;
|
||||
final String message;
|
||||
final LoginDataModel? data;
|
||||
|
||||
LoginResponseModel({
|
||||
required this.statusCode,
|
||||
required this.isSuccess,
|
||||
required this.message,
|
||||
this.data,
|
||||
});
|
||||
}
|
||||
|
||||
class LoginDataModel {
|
||||
final String? token;
|
||||
final String? id;
|
||||
final String? employeeId;
|
||||
final String? username;
|
||||
final String? fullName;
|
||||
final String? role;
|
||||
final String? email;
|
||||
final String? phoneNumber;
|
||||
final List<String>? permissions;
|
||||
|
||||
LoginDataModel({
|
||||
this.token,
|
||||
this.id,
|
||||
this.employeeId,
|
||||
this.username,
|
||||
this.fullName,
|
||||
this.role,
|
||||
this.email,
|
||||
this.phoneNumber,
|
||||
this.permissions,
|
||||
});
|
||||
}
|
||||
8
lib/domain/repositories/auth_repository.dart
Normal file
8
lib/domain/repositories/auth_repository.dart
Normal file
@@ -0,0 +1,8 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import '../../core/error/failures.dart';
|
||||
import '../models/login_request.dart';
|
||||
import '../models/login_response_model.dart';
|
||||
|
||||
abstract class AuthRepository {
|
||||
Future<Either<Failure, LoginResponseModel>> login(LoginRequest request);
|
||||
}
|
||||
15
lib/domain/usecases/login_usecase.dart
Normal file
15
lib/domain/usecases/login_usecase.dart
Normal file
@@ -0,0 +1,15 @@
|
||||
import 'package:dartz/dartz.dart';
|
||||
import '../../core/error/failures.dart';
|
||||
import '../models/login_request.dart';
|
||||
import '../models/login_response_model.dart';
|
||||
import '../repositories/auth_repository.dart';
|
||||
|
||||
class LoginUseCase {
|
||||
final AuthRepository repository;
|
||||
|
||||
LoginUseCase({required this.repository});
|
||||
|
||||
Future<Either<Failure, LoginResponseModel>> call(LoginRequest request) {
|
||||
return repository.login(request);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,8 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../screens/main_screen.dart';
|
||||
import '../screens/main_screen.dart';
|
||||
import '../core/di/injection_container.dart';
|
||||
import '../domain/usecases/login_usecase.dart';
|
||||
import '../domain/models/login_request.dart';
|
||||
import 'onboarding_button.dart';
|
||||
|
||||
class AuthForm extends StatefulWidget {
|
||||
@@ -13,25 +16,86 @@ class AuthForm extends StatefulWidget {
|
||||
|
||||
class _AuthFormState extends State<AuthForm> {
|
||||
bool _obscure = true;
|
||||
bool _isLoading = false;
|
||||
|
||||
// Text controllers
|
||||
final TextEditingController _phoneNumberController = TextEditingController();
|
||||
final TextEditingController _passwordController = TextEditingController();
|
||||
|
||||
// Focus nodes for text fields
|
||||
late FocusNode _usernameFocusNode;
|
||||
late FocusNode _phoneNumberFocusNode;
|
||||
late FocusNode _passwordFocusNode;
|
||||
|
||||
void _handleLogin() {
|
||||
// Unfocus any focused text field
|
||||
_usernameFocusNode.unfocus();
|
||||
_passwordFocusNode.unfocus();
|
||||
// Get LoginUseCase from dependency injection
|
||||
final LoginUseCase _loginUseCase = sl<LoginUseCase>();
|
||||
|
||||
// Call the onSubmit callback if provided (for any other logic you might have)
|
||||
if (widget.onSubmit != null) {
|
||||
widget.onSubmit!();
|
||||
Future<void> _handleLogin() async {
|
||||
// Validate inputs
|
||||
if (_phoneNumberController.text.trim().isEmpty) {
|
||||
_showError('الرجاء إدخال رقم الهاتف');
|
||||
return;
|
||||
}
|
||||
|
||||
// Navigate to the AttendancePage
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => const MainPage()),
|
||||
if (_passwordController.text.trim().isEmpty) {
|
||||
_showError('الرجاء إدخال كلمة المرور');
|
||||
return;
|
||||
}
|
||||
|
||||
// Unfocus any focused text field
|
||||
_phoneNumberFocusNode.unfocus();
|
||||
_passwordFocusNode.unfocus();
|
||||
|
||||
setState(() {
|
||||
_isLoading = true;
|
||||
});
|
||||
|
||||
try {
|
||||
final request = LoginRequest(
|
||||
phoneNumber: _phoneNumberController.text.trim(),
|
||||
password: _passwordController.text.trim(),
|
||||
);
|
||||
|
||||
final result = await _loginUseCase(request);
|
||||
|
||||
result.fold(
|
||||
(failure) {
|
||||
_showError(failure.message);
|
||||
},
|
||||
(response) {
|
||||
if (response.isSuccess) {
|
||||
// Call the onSubmit callback if provided
|
||||
if (widget.onSubmit != null) {
|
||||
widget.onSubmit!();
|
||||
}
|
||||
|
||||
// Navigate to the MainPage
|
||||
Navigator.pushReplacement(
|
||||
context,
|
||||
MaterialPageRoute(builder: (context) => const MainPage()),
|
||||
);
|
||||
} else {
|
||||
_showError(response.message);
|
||||
}
|
||||
},
|
||||
);
|
||||
} catch (e) {
|
||||
_showError('حدث خطأ غير متوقع: $e');
|
||||
} finally {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_isLoading = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void _showError(String message) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(message),
|
||||
backgroundColor: Colors.red,
|
||||
behavior: SnackBarBehavior.floating,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -39,14 +103,16 @@ class _AuthFormState extends State<AuthForm> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
// Initialize focus nodes
|
||||
_usernameFocusNode = FocusNode();
|
||||
_phoneNumberFocusNode = FocusNode();
|
||||
_passwordFocusNode = FocusNode();
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
// Clean up focus nodes when widget is disposed
|
||||
_usernameFocusNode.dispose();
|
||||
// Clean up controllers and focus nodes
|
||||
_phoneNumberController.dispose();
|
||||
_passwordController.dispose();
|
||||
_phoneNumberFocusNode.dispose();
|
||||
_passwordFocusNode.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
@@ -130,11 +196,11 @@ class _AuthFormState extends State<AuthForm> {
|
||||
|
||||
SizedBox(height: verticalSpacing),
|
||||
|
||||
/// Username Label
|
||||
/// Phone Number Label
|
||||
Align(
|
||||
alignment: Alignment.centerRight,
|
||||
child: Text(
|
||||
"اسم المستخدم",
|
||||
"رقم الهاتف",
|
||||
style: TextStyle(
|
||||
fontSize: labelFontSize,
|
||||
color: Colors.black87,
|
||||
@@ -144,9 +210,11 @@ class _AuthFormState extends State<AuthForm> {
|
||||
const SizedBox(height: 8),
|
||||
|
||||
_buildField(
|
||||
hint: "اسم المستخدم",
|
||||
controller: _phoneNumberController,
|
||||
hint: "رقم الهاتف",
|
||||
obscure: false,
|
||||
focusNode: _usernameFocusNode,
|
||||
keyboardType: TextInputType.phone,
|
||||
focusNode: _phoneNumberFocusNode,
|
||||
textInputAction: TextInputAction.next,
|
||||
onSubmitted: (_) {
|
||||
// Move focus to password field when next is pressed
|
||||
@@ -171,14 +239,13 @@ class _AuthFormState extends State<AuthForm> {
|
||||
const SizedBox(height: 8),
|
||||
|
||||
_buildField(
|
||||
controller: _passwordController,
|
||||
hint: "كلمة المرور",
|
||||
obscure: _obscure,
|
||||
hasEye: true,
|
||||
focusNode: _passwordFocusNode,
|
||||
textInputAction: TextInputAction.done,
|
||||
onSubmitted:
|
||||
(_) =>
|
||||
_handleLogin(), // Added parentheses to call the method
|
||||
onSubmitted: (_) => _handleLogin(),
|
||||
fontSize: fieldFontSize,
|
||||
),
|
||||
|
||||
@@ -186,9 +253,9 @@ class _AuthFormState extends State<AuthForm> {
|
||||
|
||||
Center(
|
||||
child: OnboardingButton(
|
||||
text: "تسجيل دخول",
|
||||
text: _isLoading ? "جاري تسجيل الدخول..." : "تسجيل دخول",
|
||||
backgroundColor: const Color.fromARGB(239, 35, 87, 74),
|
||||
onPressed: _handleLogin,
|
||||
onPressed: _isLoading ? null : _handleLogin,
|
||||
),
|
||||
),
|
||||
|
||||
@@ -203,9 +270,11 @@ class _AuthFormState extends State<AuthForm> {
|
||||
}
|
||||
|
||||
Widget _buildField({
|
||||
TextEditingController? controller,
|
||||
required String hint,
|
||||
required bool obscure,
|
||||
bool hasEye = false,
|
||||
TextInputType? keyboardType,
|
||||
FocusNode? focusNode,
|
||||
TextInputAction? textInputAction,
|
||||
Function(String)? onSubmitted,
|
||||
@@ -220,8 +289,10 @@ class _AuthFormState extends State<AuthForm> {
|
||||
],
|
||||
),
|
||||
child: TextField(
|
||||
controller: controller,
|
||||
focusNode: focusNode,
|
||||
obscureText: obscure,
|
||||
keyboardType: keyboardType,
|
||||
textAlign: TextAlign.right,
|
||||
textInputAction: textInputAction,
|
||||
onSubmitted: onSubmitted,
|
||||
|
||||
Reference in New Issue
Block a user