From 56e2c0ffaac40ffafa56e904db12125f7c5cd27b Mon Sep 17 00:00:00 2001 From: Daniah Ayad Al-sultani <148902945+Cactuskiller@users.noreply.github.com> Date: Thu, 15 Jan 2026 22:35:10 +0300 Subject: [PATCH] attendence login/logout has been implemented --- lib/core/di/injection_container.dart | 27 +- .../attendance_remote_data_source.dart | 58 ++++ .../datasources/auth_remote_data_source.dart | 12 +- .../datasources/user_local_data_source.dart | 14 + lib/data/dto/attendance_response_dto.dart | 24 ++ lib/data/dto/login_response_dto.dart | 10 +- .../attendance_repository_impl.dart | 41 +++ .../repositories/auth_repository_impl.dart | 39 +-- .../models/attendance_login_request.dart | 8 + .../models/attendance_logout_request.dart | 8 + .../models/attendance_response_model.dart | 13 + lib/domain/models/login_request.dart | 5 +- .../repositories/attendance_repository.dart | 12 + .../usecases/attendance_login_usecase.dart | 15 ++ .../usecases/attendance_logout_usecase.dart | 13 + .../screens/attendence_screen.dart | 85 +++++- lib/presentation/screens/face_screen.dart | 69 +++-- lib/presentation/screens/main_screen.dart | 9 +- .../screens/user_settings_screen.dart | 26 +- lib/presentation/widgets/auth_form.dart | 250 +++++++++--------- 20 files changed, 538 insertions(+), 200 deletions(-) create mode 100644 lib/data/datasources/attendance_remote_data_source.dart create mode 100644 lib/data/dto/attendance_response_dto.dart create mode 100644 lib/data/repositories/attendance_repository_impl.dart create mode 100644 lib/domain/models/attendance_login_request.dart create mode 100644 lib/domain/models/attendance_logout_request.dart create mode 100644 lib/domain/models/attendance_response_model.dart create mode 100644 lib/domain/repositories/attendance_repository.dart create mode 100644 lib/domain/usecases/attendance_login_usecase.dart create mode 100644 lib/domain/usecases/attendance_logout_usecase.dart diff --git a/lib/core/di/injection_container.dart b/lib/core/di/injection_container.dart index 11b6b25..4cc12c6 100644 --- a/lib/core/di/injection_container.dart +++ b/lib/core/di/injection_container.dart @@ -1,3 +1,6 @@ +import 'package:coda_project/data/datasources/attendance_remote_data_source.dart'; +import 'package:coda_project/data/repositories/attendance_repository_impl.dart'; +import 'package:coda_project/domain/repositories/attendance_repository.dart'; import 'package:dio/dio.dart'; import 'package:get_it/get_it.dart'; import 'package:shared_preferences/shared_preferences.dart'; @@ -7,6 +10,8 @@ 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'; +import '../../domain/usecases/attendance_login_usecase.dart'; +import '../../domain/usecases/attendance_logout_usecase.dart'; import '../../presentation/blocs/login/login_bloc.dart'; final sl = GetIt.instance; @@ -14,7 +19,7 @@ final sl = GetIt.instance; Future initializeDependencies() async { // External sl.registerLazySingleton(() => Dio()); - + // SharedPreferences final sharedPreferences = await SharedPreferences.getInstance(); sl.registerLazySingleton(() => sharedPreferences); @@ -28,17 +33,14 @@ Future initializeDependencies() async { sl.registerLazySingleton( () => AuthRemoteDataSourceImpl(apiClient: sl()), ); - + sl.registerLazySingleton( () => UserLocalDataSourceImpl(sharedPreferences: sl()), ); // Repositories sl.registerLazySingleton( - () => AuthRepositoryImpl( - remoteDataSource: sl(), - localDataSource: sl(), - ), + () => AuthRepositoryImpl(remoteDataSource: sl(), localDataSource: sl()), ); // Use cases @@ -46,4 +48,17 @@ Future initializeDependencies() async { // Blocs sl.registerFactory(() => LoginBloc(loginUseCase: sl())); + + //Attendence + sl.registerLazySingleton( + () => AttendanceRemoteDataSourceImpl(apiClient: sl()), + ); + + sl.registerLazySingleton( + () => AttendanceRepositoryImpl(remoteDataSource: sl()), + ); + + sl.registerLazySingleton(() => AttendanceLoginUsecase(repository: sl())); + + sl.registerLazySingleton(() => AttendanceLogoutUseCase(repository: sl())); } diff --git a/lib/data/datasources/attendance_remote_data_source.dart b/lib/data/datasources/attendance_remote_data_source.dart new file mode 100644 index 0000000..ac9ff47 --- /dev/null +++ b/lib/data/datasources/attendance_remote_data_source.dart @@ -0,0 +1,58 @@ +import 'dart:io'; +import 'package:dio/dio.dart'; +import '../../core/network/api_client.dart'; +import '../dto/attendance_response_dto.dart'; + +abstract class AttendanceRemoteDataSource { + Future login({ + required String employeeId, + required File faceImage, + }); + + Future logout({ + required String employeeId, + required File faceImage, + }); +} + +class AttendanceRemoteDataSourceImpl implements AttendanceRemoteDataSource { + final ApiClient apiClient; + + AttendanceRemoteDataSourceImpl({required this.apiClient}); + @override + Future login({ + required String employeeId, + required File faceImage, + }) async { + final formData = FormData.fromMap({ + 'EmployeeId': employeeId, + 'FaceImage': await MultipartFile.fromFile(faceImage.path), + }); + + final response = await apiClient.post( + '/Attendance/login', + data: formData, + options: Options(contentType: 'multipart/form-data'), + ); + return AttendanceResponseDto.fromJson(response.data); + } + + @override + Future logout({ + required String employeeId, + required File faceImage, + }) async { + final formData = FormData.fromMap({ + 'EmployeeId': employeeId, + 'FaceImage': await MultipartFile.fromFile(faceImage.path), + }); + + final response = await apiClient.post( + '/Attendance/logout', + data: formData, + options: Options(contentType: 'multipart/form-data'), + ); + + return AttendanceResponseDto.fromJson(response.data); + } +} diff --git a/lib/data/datasources/auth_remote_data_source.dart b/lib/data/datasources/auth_remote_data_source.dart index 408c7a8..e207113 100644 --- a/lib/data/datasources/auth_remote_data_source.dart +++ b/lib/data/datasources/auth_remote_data_source.dart @@ -16,10 +16,7 @@ class AuthRemoteDataSourceImpl implements AuthRemoteDataSource { @override Future login(LoginDto dto) async { try { - final response = await apiClient.post( - '/Auth/login', - data: dto.toJson(), - ); + final response = await apiClient.post('/Auth/login', data: dto.toJson()); if (response.statusCode == 200 || response.statusCode == 201) { final responseData = response.data; @@ -47,7 +44,8 @@ class AuthRemoteDataSourceImpl implements AuthRemoteDataSource { } else if (e.response?.statusCode == 500) { throw ServerException(message: 'خطأ في الخادم يرجى المحاولة لاحقا'); } else if (e.response != null) { - final message = e.response?.data?['message'] ?? + final message = + e.response?.data?['message'] ?? e.response?.data?['error'] ?? 'فشل تسجيل الدخول'; @@ -57,8 +55,8 @@ class AuthRemoteDataSourceImpl implements AuthRemoteDataSource { message.toString().toLowerCase().contains('incorrect') ? 'رقم الهاتف أو كلمة المرور غير صحيحة' : message.toString().toLowerCase().contains('not found') - ? 'المستخدم غير موجود' - : message; + ? 'المستخدم غير موجود' + : message; throw ServerException( message: customMessage, diff --git a/lib/data/datasources/user_local_data_source.dart b/lib/data/datasources/user_local_data_source.dart index dacf105..652cc69 100644 --- a/lib/data/datasources/user_local_data_source.dart +++ b/lib/data/datasources/user_local_data_source.dart @@ -4,11 +4,14 @@ abstract class UserLocalDataSource { Future cacheUserToken(String token); Future getCachedUserToken(); Future clearCache(); + Future cacheEmployeeId(String id); + Future getCachedEmployeeId(); } class UserLocalDataSourceImpl implements UserLocalDataSource { final SharedPreferences sharedPreferences; static const String _tokenKey = 'user_token'; + static const String _employeeIdKey = 'employee_id'; UserLocalDataSourceImpl({required this.sharedPreferences}); @@ -25,5 +28,16 @@ class UserLocalDataSourceImpl implements UserLocalDataSource { @override Future clearCache() async { await sharedPreferences.remove(_tokenKey); + await sharedPreferences.remove(_employeeIdKey); + } + + @override + Future cacheEmployeeId(String id) async { + await sharedPreferences.setString(_employeeIdKey, id); + } + + @override + Future getCachedEmployeeId() async { + return sharedPreferences.getString(_employeeIdKey); } } diff --git a/lib/data/dto/attendance_response_dto.dart b/lib/data/dto/attendance_response_dto.dart new file mode 100644 index 0000000..748f180 --- /dev/null +++ b/lib/data/dto/attendance_response_dto.dart @@ -0,0 +1,24 @@ +class AttendanceResponseDto { + final String id; + final String employeeId; + final DateTime? login; + final DateTime? logout; + + AttendanceResponseDto({ + required this.id, + required this.employeeId, + this.login, + this.logout, + }); + + factory AttendanceResponseDto.fromJson(Map json) { + final data = json['data']; + + return AttendanceResponseDto( + id: data['id'], + employeeId: data['employeeId'], + login: data['login'], + logout: data['logout'], + ); + } +} diff --git a/lib/data/dto/login_response_dto.dart b/lib/data/dto/login_response_dto.dart index 635e354..1972f42 100644 --- a/lib/data/dto/login_response_dto.dart +++ b/lib/data/dto/login_response_dto.dart @@ -57,15 +57,17 @@ class LoginDataDto { return LoginDataDto( token: json['token'], id: json['id'], - employeeId: json['employeeId'], + employeeId: + json['employeeId'] ?? json['EmployeeId'] ?? json['employee_id'], username: json['username'], fullName: json['fullName'], role: json['role'], email: json['email'], phoneNumber: json['phoneNumber'], - permissions: json['permissions'] != null - ? List.from(json['permissions']) - : null, + permissions: + json['permissions'] != null + ? List.from(json['permissions']) + : null, ); } diff --git a/lib/data/repositories/attendance_repository_impl.dart b/lib/data/repositories/attendance_repository_impl.dart new file mode 100644 index 0000000..4555a75 --- /dev/null +++ b/lib/data/repositories/attendance_repository_impl.dart @@ -0,0 +1,41 @@ +import '../../domain/models/attendance_login_request.dart'; +import '../../domain/models/attendance_logout_request.dart'; +import '../../domain/models/attendance_response_model.dart'; +import '../../domain/repositories/attendance_repository.dart'; +import '../datasources/attendance_remote_data_source.dart'; + +class AttendanceRepositoryImpl implements AttendanceRepository { + final AttendanceRemoteDataSource remoteDataSource; + + AttendanceRepositoryImpl({required this.remoteDataSource}); + + @override + Future login(AttendanceLoginRequest request) async { + final dto = await remoteDataSource.login( + employeeId: request.employeeId, + faceImage: request.faceImage, + ); + + return AttendanceResponseModel( + id: dto.id, + employeeId: dto.employeeId, + login: dto.login, + ); + } + + @override + Future logout( + AttendanceLogoutRequest request, + ) async { + final dto = await remoteDataSource.logout( + employeeId: request.employeeId, + faceImage: request.faceImage, + ); + + return AttendanceResponseModel( + id: dto.id, + employeeId: dto.employeeId, + logout: dto.logout, + ); + } +} diff --git a/lib/data/repositories/auth_repository_impl.dart b/lib/data/repositories/auth_repository_impl.dart index 20dd1eb..5836e01 100644 --- a/lib/data/repositories/auth_repository_impl.dart +++ b/lib/data/repositories/auth_repository_impl.dart @@ -4,7 +4,6 @@ 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'; @@ -19,7 +18,9 @@ class AuthRepositoryImpl implements AuthRepository { }); @override - Future> login(LoginRequest request) async { + Future> login( + LoginRequest request, + ) async { try { final dto = LoginDto( phoneNumber: request.phoneNumber, @@ -27,30 +28,38 @@ class AuthRepositoryImpl implements AuthRepository { ); final responseDto = await remoteDataSource.login(dto); + print("LOGIN RESPONSE DATA: ${responseDto.toJson()}"); // Debugging Log // Cache the token locally if (responseDto.data?.token != null) { await localDataSource.cacheUserToken(responseDto.data!.token!); } + if (responseDto.data?.employeeId != null) { + print("AUTH_REPO: Caching EmployeeId: ${responseDto.data!.employeeId}"); + await localDataSource.cacheEmployeeId(responseDto.data!.employeeId!); + } else { + print("AUTH_REPO: EmployeeId is NULL in response!"); + } // 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, + 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); diff --git a/lib/domain/models/attendance_login_request.dart b/lib/domain/models/attendance_login_request.dart new file mode 100644 index 0000000..1c84da1 --- /dev/null +++ b/lib/domain/models/attendance_login_request.dart @@ -0,0 +1,8 @@ +import 'dart:io'; + +class AttendanceLoginRequest { + final String employeeId; + final File faceImage; + + AttendanceLoginRequest({required this.employeeId, required this.faceImage}); +} diff --git a/lib/domain/models/attendance_logout_request.dart b/lib/domain/models/attendance_logout_request.dart new file mode 100644 index 0000000..3e8269d --- /dev/null +++ b/lib/domain/models/attendance_logout_request.dart @@ -0,0 +1,8 @@ +import 'dart:io'; + +class AttendanceLogoutRequest { + final String employeeId; + final File faceImage; + + AttendanceLogoutRequest({required this.employeeId, required this.faceImage}); +} diff --git a/lib/domain/models/attendance_response_model.dart b/lib/domain/models/attendance_response_model.dart new file mode 100644 index 0000000..ee92f2b --- /dev/null +++ b/lib/domain/models/attendance_response_model.dart @@ -0,0 +1,13 @@ +class AttendanceResponseModel { + final String id; + final String employeeId; + final DateTime? login; + final DateTime? logout; + + AttendanceResponseModel({ + required this.id, + required this.employeeId, + this.login, + this.logout, + }); +} diff --git a/lib/domain/models/login_request.dart b/lib/domain/models/login_request.dart index 5877c04..eeadc53 100644 --- a/lib/domain/models/login_request.dart +++ b/lib/domain/models/login_request.dart @@ -2,8 +2,5 @@ class LoginRequest { final String phoneNumber; final String password; - LoginRequest({ - required this.phoneNumber, - required this.password, - }); + LoginRequest({required this.phoneNumber, required this.password}); } diff --git a/lib/domain/repositories/attendance_repository.dart b/lib/domain/repositories/attendance_repository.dart new file mode 100644 index 0000000..1165144 --- /dev/null +++ b/lib/domain/repositories/attendance_repository.dart @@ -0,0 +1,12 @@ +import '../models/attendance_login_request.dart'; +import '../models/attendance_logout_request.dart'; +import '../models/attendance_response_model.dart'; + +//in the following polymorphism is being used , a quich recap it is where th esame method but opperate in a different way + +//one Repo two requests +abstract class AttendanceRepository { + Future login(AttendanceLoginRequest request); + + Future logout(AttendanceLogoutRequest request); +} diff --git a/lib/domain/usecases/attendance_login_usecase.dart b/lib/domain/usecases/attendance_login_usecase.dart new file mode 100644 index 0000000..54681b5 --- /dev/null +++ b/lib/domain/usecases/attendance_login_usecase.dart @@ -0,0 +1,15 @@ +import '../models/attendance_login_request.dart'; +import '../models/attendance_response_model.dart'; +import '../repositories/attendance_repository.dart'; + +//always remmber that the usecase uses the repo + +class AttendanceLoginUsecase { + final AttendanceRepository repository; + + AttendanceLoginUsecase({required this.repository}); + + Future call(AttendanceLoginRequest request) { + return repository.login(request); + } +} diff --git a/lib/domain/usecases/attendance_logout_usecase.dart b/lib/domain/usecases/attendance_logout_usecase.dart new file mode 100644 index 0000000..0cb6a9c --- /dev/null +++ b/lib/domain/usecases/attendance_logout_usecase.dart @@ -0,0 +1,13 @@ +import '../models/attendance_logout_request.dart'; +import '../models/attendance_response_model.dart'; +import '../repositories/attendance_repository.dart'; + +class AttendanceLogoutUseCase { + final AttendanceRepository repository; + + AttendanceLogoutUseCase({required this.repository}); + + Future call(AttendanceLogoutRequest request) { + return repository.logout(request); + } +} diff --git a/lib/presentation/screens/attendence_screen.dart b/lib/presentation/screens/attendence_screen.dart index 4316091..b7aa96c 100644 --- a/lib/presentation/screens/attendence_screen.dart +++ b/lib/presentation/screens/attendence_screen.dart @@ -4,6 +4,12 @@ import 'package:coda_project/presentation/screens/user_settings_screen.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import '../widgets/settings_bar.dart'; +import '../../core/di/injection_container.dart'; +import '../../domain/models/attendance_login_request.dart'; +import '../../domain/models/attendance_logout_request.dart'; +import '../../domain/usecases/attendance_login_usecase.dart'; +import '../../domain/usecases/attendance_logout_usecase.dart'; +import '../../data/datasources/user_local_data_source.dart'; class AttendanceScreen extends StatelessWidget { const AttendanceScreen({super.key}); @@ -138,12 +144,40 @@ class AttendanceScreen extends StatelessWidget { child: _FingerButton( icon: "assets/images/faceLogin.svg", label: "تسجيل الدخول", - onTap: () { - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => OvalCameraCapturePage(isLogin: true), - ), - ); + onTap: () async { + final employeeId = + await sl().getCachedEmployeeId(); + print("ATTENDANCE_SCREEN: Retrieved EmployeeId: $employeeId"); + if (employeeId == null) { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('خطأ: لم يتم العثور على رقم الموظف'), + ), + ); + } + return; + } + if (context.mounted) { + Navigator.of(context).push( + MaterialPageRoute( + builder: + (_) => OvalCameraCapturePage( + isLogin: true, + onCapture: (imageFile) async { + final loginUseCase = + sl(); + await loginUseCase( + AttendanceLoginRequest( + employeeId: employeeId, + faceImage: imageFile, + ), + ); + }, + ), + ), + ); + } }, ), ), @@ -178,12 +212,39 @@ class AttendanceScreen extends StatelessWidget { child: _FingerButton( icon: "assets/images/faceLogout.svg", label: "تسجيل خروج", - onTap: () { - Navigator.of(context).push( - MaterialPageRoute( - builder: (_) => OvalCameraCapturePage(isLogin: false), - ), - ); + onTap: () async { + final employeeId = + await sl().getCachedEmployeeId(); + if (employeeId == null) { + if (context.mounted) { + ScaffoldMessenger.of(context).showSnackBar( + const SnackBar( + content: Text('خطأ: لم يتم العثور على رقم الموظف'), + ), + ); + } + return; + } + if (context.mounted) { + Navigator.of(context).push( + MaterialPageRoute( + builder: + (_) => OvalCameraCapturePage( + isLogin: false, + onCapture: (imageFile) async { + final logoutUseCase = + sl(); + await logoutUseCase( + AttendanceLogoutRequest( + employeeId: employeeId, + faceImage: imageFile, + ), + ); + }, + ), + ), + ); + } }, ), ), diff --git a/lib/presentation/screens/face_screen.dart b/lib/presentation/screens/face_screen.dart index 18362d0..cd670b0 100644 --- a/lib/presentation/screens/face_screen.dart +++ b/lib/presentation/screens/face_screen.dart @@ -2,10 +2,17 @@ import 'package:camera/camera.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/flutter_svg.dart'; import 'dart:async'; +import 'dart:io'; class OvalCameraCapturePage extends StatefulWidget { final bool isLogin; - const OvalCameraCapturePage({super.key, this.isLogin = true}); + final Future Function(File image) onCapture; + + const OvalCameraCapturePage({ + super.key, + this.isLogin = true, + required this.onCapture, + }); @override State createState() => _OvalCameraCapturePageState(); @@ -77,20 +84,7 @@ class _OvalCameraCapturePageState extends State { _errorMessage = null; }); - _timer = Timer(const Duration(seconds: 3), () { - if (mounted) { - setState(() { - _isSuccess = true; - }); - - // Auto-close after 2 seconds - Future.delayed(const Duration(seconds: 2), () { - if (mounted) { - Navigator.of(context).pop(); - } - }); - } - }); + _startScan(); } catch (e) { if (!mounted) return; @@ -102,6 +96,51 @@ class _OvalCameraCapturePageState extends State { } } + Future _startScan() async { + try { + // Simulate scanning delay + await Future.delayed(const Duration(seconds: 2)); + + if (!mounted || + _cameraController == null || + !_cameraController!.value.isInitialized) { + return; + } + + final xFile = await _cameraController!.takePicture(); + final file = File(xFile.path); + + await widget.onCapture(file); + + if (mounted) { + setState(() { + _isSuccess = true; + }); + + // Auto-close after 2 seconds + Future.delayed(const Duration(seconds: 2), () { + if (mounted) { + Navigator.of(context).pop(); + } + }); + } + } catch (e) { + if (mounted) { + setState(() { + _errorMessage = e.toString(); // Show error to user + }); + + // Auto-close or let user retry? For now let's just show error. + // If we want to auto-close on error: + Future.delayed(const Duration(seconds: 3), () { + if (mounted) { + Navigator.of(context).pop(); + } + }); + } + } + } + @override void dispose() { _cameraController?.dispose(); diff --git a/lib/presentation/screens/main_screen.dart b/lib/presentation/screens/main_screen.dart index 8a87413..8b4d4d1 100644 --- a/lib/presentation/screens/main_screen.dart +++ b/lib/presentation/screens/main_screen.dart @@ -1,6 +1,6 @@ import 'package:flutter/material.dart'; import '../widgets/app_background.dart'; -import '../../widgets/floatingnavbar.dart'; +import '../widgets/FloatingNavBar.dart'; import 'attendence_screen.dart'; import 'finance_screen.dart'; import 'holiday_screen.dart'; @@ -15,20 +15,17 @@ class MainPage extends StatefulWidget { class _MainPageState extends State { int _currentIndex = 0; - - @override Widget build(BuildContext context) { - final screenHeight = MediaQuery.sizeOf(context).height; + // final screenHeight = MediaQuery.sizeOf(context).height; return Scaffold( body: Stack( children: [ - /// BACKGROUND const AppBackground(child: SizedBox()), /// ACTIVE SCREEN (fills entire screen - content will extend behind navbar) - /// + /// Positioned.fill( child: IndexedStack( index: _currentIndex, diff --git a/lib/presentation/screens/user_settings_screen.dart b/lib/presentation/screens/user_settings_screen.dart index 12b056a..9054fef 100644 --- a/lib/presentation/screens/user_settings_screen.dart +++ b/lib/presentation/screens/user_settings_screen.dart @@ -5,7 +5,8 @@ import '../widgets/settings_bar.dart'; import 'about_screen.dart'; import 'auth_screen.dart'; import '../widgets/change_password_modal.dart'; - +import '../../core/di/injection_container.dart'; +import '../../data/datasources/user_local_data_source.dart'; class UserSettingsScreen extends StatefulWidget { const UserSettingsScreen({super.key}); @@ -143,8 +144,8 @@ class _UserSettingsScreenState extends State { duration: const Duration( milliseconds: 250, ), - width: 75, - height: 30, + width: 75, + height: 30, padding: const EdgeInsets.symmetric( horizontal: 4, ), @@ -227,13 +228,18 @@ class _UserSettingsScreenState extends State { _settingsRow( label: "تسجيل خروج", icon: "assets/images/logout2.svg", - onTap: () { - Navigator.push( - context, - MaterialPageRoute( - builder: (_) => const AuthScreen(), - ), - ); + onTap: () async { + await sl() + .clearCache(); + if (context.mounted) { + Navigator.pushAndRemoveUntil( + context, + MaterialPageRoute( + builder: (_) => const AuthScreen(), + ), + (route) => false, + ); + } }, ), ], diff --git a/lib/presentation/widgets/auth_form.dart b/lib/presentation/widgets/auth_form.dart index fb8bd37..d46424c 100644 --- a/lib/presentation/widgets/auth_form.dart +++ b/lib/presentation/widgets/auth_form.dart @@ -123,135 +123,143 @@ class _AuthFormState extends State { textDirection: TextDirection.rtl, child: FocusScope( child: Stack( - alignment: Alignment.center, - children: [ - // Border container - decorative element behind the form - Container( - width: borderWidth, - constraints: BoxConstraints( - minHeight: formHeight + 40, - maxHeight: - formHeight + 80, // Allows shrinking when keyboard opens + alignment: Alignment.center, + children: [ + // Border container - decorative element behind the form + Container( + width: borderWidth, + constraints: BoxConstraints( + minHeight: formHeight + 40, + maxHeight: + formHeight + 80, // Allows shrinking when keyboard opens + ), + decoration: BoxDecoration( + color: Colors.transparent, + borderRadius: BorderRadius.circular(32), + border: Border.all(color: const Color(0xDD00C28E), width: 1), + ), ), - decoration: BoxDecoration( - color: Colors.transparent, - borderRadius: BorderRadius.circular(32), - border: Border.all(color: const Color(0xDD00C28E), width: 1), - ), - ), - // Main form container - Container( - width: formWidth, - padding: EdgeInsets.symmetric( - horizontal: horizontalPadding, - vertical: verticalPadding, - ), - decoration: BoxDecoration( - color: const Color(0xFFEEFFFA), - borderRadius: BorderRadius.circular(28), - boxShadow: const [ - BoxShadow( - color: Colors.black26, - blurRadius: 10, - offset: Offset(0, 5), - ), - ], - ), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - mainAxisSize: MainAxisSize.min, - children: [ - /// Title - Center( - child: Text( - "تسجيل دخول", - style: TextStyle( - fontSize: titleFontSize, - fontWeight: FontWeight.bold, - color: Colors.black87, - ), + // Main form container + Container( + width: formWidth, + padding: EdgeInsets.symmetric( + horizontal: horizontalPadding, + vertical: verticalPadding, + ), + decoration: BoxDecoration( + color: const Color(0xFFEEFFFA), + borderRadius: BorderRadius.circular(28), + boxShadow: const [ + BoxShadow( + color: Colors.black26, + blurRadius: 10, + offset: Offset(0, 5), ), - ), - - SizedBox(height: verticalSpacing), - - /// Phone Number Label - Align( - alignment: Alignment.centerRight, - child: Text( - "رقم الهاتف", - style: TextStyle( - fontSize: labelFontSize, - color: Colors.black87, - ), - ), - ), - const SizedBox(height: 8), - - _buildField( - controller: _phoneNumberController, - hint: "رقم الهاتف", - obscure: false, - keyboardType: TextInputType.phone, - focusNode: _phoneNumberFocusNode, - textInputAction: TextInputAction.next, - onSubmitted: (_) { - // Move focus to password field when next is pressed - FocusScope.of(context).requestFocus(_passwordFocusNode); - }, - fontSize: fieldFontSize, - ), - - SizedBox(height: fieldSpacing), - - /// Password Label - Align( - alignment: Alignment.centerRight, - child: Text( - "كلمة المرور", - style: TextStyle( - fontSize: labelFontSize, - color: Colors.black87, - ), - ), - ), - const SizedBox(height: 8), - - _buildField( - controller: _passwordController, - hint: "كلمة المرور", - obscure: _obscure, - hasEye: true, - focusNode: _passwordFocusNode, - textInputAction: TextInputAction.done, - onSubmitted: (_) => _handleLogin(), - fontSize: fieldFontSize, - ), - - SizedBox(height: buttonSpacing), // Responsive spacing - - BlocBuilder( - builder: (context, state) { - final isLoading = state is LoginLoading; - return Center( - child: OnboardingButton( - text: isLoading ? "جاري تسجيل الدخول..." : "تسجيل دخول", - backgroundColor: const Color.fromARGB(239, 35, 87, 74), - onPressed: isLoading ? null : _handleLogin, + ], + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + mainAxisSize: MainAxisSize.min, + children: [ + /// Title + Center( + child: Text( + "تسجيل دخول", + style: TextStyle( + fontSize: titleFontSize, + fontWeight: FontWeight.bold, + color: Colors.black87, ), - ); - }, - ), + ), + ), - SizedBox(height: bottomSpacing), - ], + SizedBox(height: verticalSpacing), + + /// Phone Number Label + Align( + alignment: Alignment.centerRight, + child: Text( + "رقم الهاتف", + style: TextStyle( + fontSize: labelFontSize, + color: Colors.black87, + ), + ), + ), + const SizedBox(height: 8), + + _buildField( + controller: _phoneNumberController, + hint: "رقم الهاتف", + obscure: false, + keyboardType: TextInputType.phone, + focusNode: _phoneNumberFocusNode, + textInputAction: TextInputAction.next, + onSubmitted: (_) { + // Move focus to password field when next is pressed + FocusScope.of(context).requestFocus(_passwordFocusNode); + }, + fontSize: fieldFontSize, + ), + + SizedBox(height: fieldSpacing), + + /// Password Label + Align( + alignment: Alignment.centerRight, + child: Text( + "كلمة المرور", + style: TextStyle( + fontSize: labelFontSize, + color: Colors.black87, + ), + ), + ), + const SizedBox(height: 8), + + _buildField( + controller: _passwordController, + hint: "كلمة المرور", + obscure: _obscure, + hasEye: true, + focusNode: _passwordFocusNode, + textInputAction: TextInputAction.done, + onSubmitted: (_) => _handleLogin(), + fontSize: fieldFontSize, + ), + + SizedBox(height: buttonSpacing), // Responsive spacing + + BlocBuilder( + builder: (context, state) { + final isLoading = state is LoginLoading; + return Center( + child: OnboardingButton( + text: + isLoading + ? "جاري تسجيل الدخول..." + : "تسجيل دخول", + backgroundColor: const Color.fromARGB( + 239, + 35, + 87, + 74, + ), + onPressed: isLoading ? null : _handleLogin, + ), + ); + }, + ), + + SizedBox(height: bottomSpacing), + ], + ), ), - ), - ], + ], + ), ), ), - ), ); }