From f616a2c1043daa340f2a48c8e3eefef4060017e2 Mon Sep 17 00:00:00 2001 From: Daniah Ayad Al-sultani <148902945+Cactuskiller@users.noreply.github.com> Date: Sun, 22 Feb 2026 11:18:10 +0300 Subject: [PATCH] chnages has been made --- android/app/src/main/AndroidManifest.xml | 1 + .../com/example/coda_project/MainActivity.kt | 4 +- lib/core/network/api_client.dart | 12 +- .../attendance_remote_data_source.dart | 6 + .../datasources/theme_remote_data_source.dart | 49 ++ lib/data/dto/theme_response_dto.dart | 36 + .../attendance_repository_impl.dart | 2 + .../repositories/theme_repository_impl.dart | 24 + .../models/attendance_login_request.dart | 7 +- .../models/attendance_logout_request.dart | 7 +- lib/domain/models/theme_model.dart | 6 + lib/domain/repositories/theme_repository.dart | 7 + lib/domain/usecases/get_theme_usecase.dart | 11 + lib/presentation/blocs/theme/theme_cubit.dart | 56 ++ lib/presentation/blocs/theme/theme_state.dart | 36 + .../screens/attendence_screen.dart | 798 ++++++++++++++---- lib/presentation/screens/auth_screen.dart | 13 +- lib/presentation/screens/face_screen2.dart | 144 +++- .../screens/onboarding_screen.dart | 11 +- lib/presentation/screens/splash_screen.dart | 15 +- lib/presentation/widgets/settings_bar.dart | 23 +- macos/Flutter/GeneratedPluginRegistrant.swift | 2 + pubspec.lock | 56 +- pubspec.yaml | 1 + .../flutter/generated_plugin_registrant.cc | 3 + windows/flutter/generated_plugins.cmake | 1 + 26 files changed, 1130 insertions(+), 201 deletions(-) create mode 100644 lib/data/datasources/theme_remote_data_source.dart create mode 100644 lib/data/dto/theme_response_dto.dart create mode 100644 lib/data/repositories/theme_repository_impl.dart create mode 100644 lib/domain/models/theme_model.dart create mode 100644 lib/domain/repositories/theme_repository.dart create mode 100644 lib/domain/usecases/get_theme_usecase.dart create mode 100644 lib/presentation/blocs/theme/theme_cubit.dart create mode 100644 lib/presentation/blocs/theme/theme_state.dart diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index 29da374..fdc9658 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -2,6 +2,7 @@ + login({ required String employeeId, required File faceImage, + bool localAuth = false, }); Future logout({ required String employeeId, required File faceImage, + bool localAuth = false, }); Future> getAttendanceRecords({ @@ -43,11 +45,13 @@ class AttendanceRemoteDataSourceImpl implements AttendanceRemoteDataSource { Future login({ required String employeeId, required File faceImage, + bool localAuth = false, }) async { try { final formData = FormData.fromMap({ 'EmployeeId': employeeId, 'FaceImage': await MultipartFile.fromFile(faceImage.path), + 'IsAuth': localAuth.toString(), }); final response = await apiClient.post( @@ -107,11 +111,13 @@ class AttendanceRemoteDataSourceImpl implements AttendanceRemoteDataSource { Future logout({ required String employeeId, required File faceImage, + bool localAuth = false, }) async { try { final formData = FormData.fromMap({ 'EmployeeId': employeeId, 'FaceImage': await MultipartFile.fromFile(faceImage.path), + 'IsAuth': localAuth.toString(), }); final response = await apiClient.post( diff --git a/lib/data/datasources/theme_remote_data_source.dart b/lib/data/datasources/theme_remote_data_source.dart new file mode 100644 index 0000000..4c8f0ab --- /dev/null +++ b/lib/data/datasources/theme_remote_data_source.dart @@ -0,0 +1,49 @@ +import 'package:flutter/foundation.dart'; +import '../../core/error/exceptions.dart'; +import '../../core/network/api_client.dart'; +import '../dto/theme_response_dto.dart'; + +abstract class ThemeRemoteDataSource { + Future getTheme(); +} + +class ThemeRemoteDataSourceImpl implements ThemeRemoteDataSource { + final ApiClient apiClient; + + ThemeRemoteDataSourceImpl({required this.apiClient}); + + @override + Future getTheme() async { + try { + debugPrint('[ThemeDataSource] Calling GET /Theme (with auth)...'); + + final res = await apiClient.get('/Theme'); // ✅ no custom headers + + debugPrint('[ThemeDataSource] Status: ${res.statusCode}'); + debugPrint('[ThemeDataSource] Data: ${res.data}'); + + if (res.statusCode == 200) { + final dto = ThemeResponseDto.fromJson( + Map.from(res.data), + ); + + if (dto.isSuccess && dto.data != null) { + debugPrint('[ThemeDataSource] ✅ logo = ${dto.data!.logo}'); + return dto.data!; + } + + throw ServerException(message: dto.message ?? 'Theme request failed'); + } + + throw ServerException( + message: 'Theme request failed (code ${res.statusCode})', + ); + } on ServerException { + rethrow; + } catch (e, stack) { + debugPrint('[ThemeDataSource] ❌ Exception: $e'); + debugPrint('[ThemeDataSource] ❌ Stack: $stack'); + throw ServerException(message: e.toString()); + } + } +} diff --git a/lib/data/dto/theme_response_dto.dart b/lib/data/dto/theme_response_dto.dart new file mode 100644 index 0000000..796ee21 --- /dev/null +++ b/lib/data/dto/theme_response_dto.dart @@ -0,0 +1,36 @@ +class ThemeResponseDto { + final int statusCode; + final bool isSuccess; + final String? message; + final ThemeDataDto? data; + + ThemeResponseDto({ + required this.statusCode, + required this.isSuccess, + required this.message, + required this.data, + }); + + factory ThemeResponseDto.fromJson(Map json) { + return ThemeResponseDto( + statusCode: json['statusCode'] ?? 0, + isSuccess: json['isSuccess'] ?? false, + message: json['message']?.toString(), + data: json['data'] == null ? null : ThemeDataDto.fromJson(json['data']), + ); + } +} + +class ThemeDataDto { + final String name; + final String logo; + + ThemeDataDto({required this.name, required this.logo}); + + factory ThemeDataDto.fromJson(Map json) { + return ThemeDataDto( + name: (json['name'] ?? '').toString(), + logo: (json['logo'] ?? '').toString(), + ); + } +} diff --git a/lib/data/repositories/attendance_repository_impl.dart b/lib/data/repositories/attendance_repository_impl.dart index 07955b9..ada41e8 100644 --- a/lib/data/repositories/attendance_repository_impl.dart +++ b/lib/data/repositories/attendance_repository_impl.dart @@ -18,6 +18,7 @@ class AttendanceRepositoryImpl implements AttendanceRepository { final dto = await remoteDataSource.login( employeeId: request.employeeId, faceImage: request.faceImage, + localAuth: request.localAuth, ); return AttendanceResponseModel( @@ -34,6 +35,7 @@ class AttendanceRepositoryImpl implements AttendanceRepository { final dto = await remoteDataSource.logout( employeeId: request.employeeId, faceImage: request.faceImage, + localAuth: request.localAuth, ); return AttendanceResponseModel( diff --git a/lib/data/repositories/theme_repository_impl.dart b/lib/data/repositories/theme_repository_impl.dart new file mode 100644 index 0000000..f728c65 --- /dev/null +++ b/lib/data/repositories/theme_repository_impl.dart @@ -0,0 +1,24 @@ +import 'package:dartz/dartz.dart'; +import '../../core/error/failures.dart'; +import '../../core/error/exceptions.dart'; +import '../../domain/models/theme_model.dart'; +import '../../domain/repositories/theme_repository.dart'; +import '../datasources/theme_remote_data_source.dart'; + +class ThemeRepositoryImpl implements ThemeRepository { + final ThemeRemoteDataSource remote; + + ThemeRepositoryImpl({required this.remote}); + + @override + Future> getTheme() async { + try { + final dto = await remote.getTheme(); + return Right(ThemeModel(name: dto.name, logo: dto.logo)); + } on ServerException catch (e) { + return Left(ServerFailure(e.message)); + } catch (e) { + return Left(ServerFailure(e.toString())); + } + } +} diff --git a/lib/domain/models/attendance_login_request.dart b/lib/domain/models/attendance_login_request.dart index 1c84da1..06bdb9d 100644 --- a/lib/domain/models/attendance_login_request.dart +++ b/lib/domain/models/attendance_login_request.dart @@ -3,6 +3,11 @@ import 'dart:io'; class AttendanceLoginRequest { final String employeeId; final File faceImage; + final bool localAuth; - AttendanceLoginRequest({required this.employeeId, required this.faceImage}); + AttendanceLoginRequest({ + required this.employeeId, + required this.faceImage, + this.localAuth = false, + }); } diff --git a/lib/domain/models/attendance_logout_request.dart b/lib/domain/models/attendance_logout_request.dart index 3e8269d..ce602cd 100644 --- a/lib/domain/models/attendance_logout_request.dart +++ b/lib/domain/models/attendance_logout_request.dart @@ -3,6 +3,11 @@ import 'dart:io'; class AttendanceLogoutRequest { final String employeeId; final File faceImage; + final bool localAuth; - AttendanceLogoutRequest({required this.employeeId, required this.faceImage}); + AttendanceLogoutRequest({ + required this.employeeId, + required this.faceImage, + this.localAuth = false, + }); } diff --git a/lib/domain/models/theme_model.dart b/lib/domain/models/theme_model.dart new file mode 100644 index 0000000..dd5d3cb --- /dev/null +++ b/lib/domain/models/theme_model.dart @@ -0,0 +1,6 @@ +class ThemeModel { + final String name; + final String logo; // filename or url + + const ThemeModel({required this.name, required this.logo}); +} diff --git a/lib/domain/repositories/theme_repository.dart b/lib/domain/repositories/theme_repository.dart new file mode 100644 index 0000000..ae1233c --- /dev/null +++ b/lib/domain/repositories/theme_repository.dart @@ -0,0 +1,7 @@ +import 'package:dartz/dartz.dart'; +import '../../core/error/failures.dart'; +import '../models/theme_model.dart'; + +abstract class ThemeRepository { + Future> getTheme(); +} diff --git a/lib/domain/usecases/get_theme_usecase.dart b/lib/domain/usecases/get_theme_usecase.dart new file mode 100644 index 0000000..c9456ef --- /dev/null +++ b/lib/domain/usecases/get_theme_usecase.dart @@ -0,0 +1,11 @@ +import 'package:dartz/dartz.dart'; +import '../../core/error/failures.dart'; +import '../models/theme_model.dart'; +import '../repositories/theme_repository.dart'; + +class GetThemeUseCase { + final ThemeRepository repo; + GetThemeUseCase(this.repo); + + Future> call() => repo.getTheme(); +} diff --git a/lib/presentation/blocs/theme/theme_cubit.dart b/lib/presentation/blocs/theme/theme_cubit.dart new file mode 100644 index 0000000..7cac1b4 --- /dev/null +++ b/lib/presentation/blocs/theme/theme_cubit.dart @@ -0,0 +1,56 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter_bloc/flutter_bloc.dart'; +import '../../../domain/usecases/get_theme_usecase.dart'; +import 'theme_state.dart'; + +class ThemeCubit extends Cubit { + final GetThemeUseCase getThemeUseCase; + + /// Base URL for loading theme images (logo, etc.) + static const String _imageBaseUrl = 'https://hrm.go.iq/Images/'; + + /// Guard against re-entrant / duplicate calls + bool _isLoading = false; + + ThemeCubit({required this.getThemeUseCase}) : super(const ThemeInitial()); + + /// Load theme. Set [forceReload] to true after login to refresh. + Future loadTheme({bool forceReload = false}) async { + // Prevent duplicate concurrent calls + if (_isLoading) { + debugPrint('[ThemeCubit] loadTheme() skipped — already loading'); + return; + } + + // If already loaded and not forced, skip + if (!forceReload && state is ThemeLoaded) { + debugPrint('[ThemeCubit] loadTheme() skipped — already loaded'); + return; + } + + _isLoading = true; + debugPrint('[ThemeCubit] loadTheme() called (forceReload=$forceReload)'); + emit(const ThemeLoading()); + + final result = await getThemeUseCase(); + + _isLoading = false; + + result.fold( + (failure) { + debugPrint('[ThemeCubit] ❌ FAILED: ${failure.message}'); + emit(ThemeError(failure.message)); + }, + (theme) { + debugPrint( + '[ThemeCubit] ✅ Got theme — name: ${theme.name}, logo: ${theme.logo}', + ); + // Build the full logo URL: https://hrm.go.iq/Images/{filename} + final encodedLogo = Uri.encodeFull(theme.logo); + final logoUrl = '$_imageBaseUrl$encodedLogo'; + debugPrint('[ThemeCubit] 🖼️ Logo URL: $logoUrl'); + emit(ThemeLoaded(theme: theme, logoUrl: logoUrl)); + }, + ); + } +} diff --git a/lib/presentation/blocs/theme/theme_state.dart b/lib/presentation/blocs/theme/theme_state.dart new file mode 100644 index 0000000..40e2275 --- /dev/null +++ b/lib/presentation/blocs/theme/theme_state.dart @@ -0,0 +1,36 @@ +import 'package:equatable/equatable.dart'; +import '../../../domain/models/theme_model.dart'; + +abstract class ThemeState extends Equatable { + const ThemeState(); + + @override + List get props => []; +} + +class ThemeInitial extends ThemeState { + const ThemeInitial(); +} + +class ThemeLoading extends ThemeState { + const ThemeLoading(); +} + +class ThemeLoaded extends ThemeState { + final ThemeModel theme; + final String logoUrl; + + const ThemeLoaded({required this.theme, required this.logoUrl}); + + @override + List get props => [theme, logoUrl]; +} + +class ThemeError extends ThemeState { + final String message; + + const ThemeError(this.message); + + @override + List get props => [message]; +} diff --git a/lib/presentation/screens/attendence_screen.dart b/lib/presentation/screens/attendence_screen.dart index a4366bb..798fc84 100644 --- a/lib/presentation/screens/attendence_screen.dart +++ b/lib/presentation/screens/attendence_screen.dart @@ -1,3 +1,392 @@ +// // import 'package:coda_project/presentation/screens/face_screen2.dart'; +// // import 'package:coda_project/presentation/screens/notifications_screen.dart'; +// // 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'; +// // import '../../data/datasources/attendance_remote_data_source.dart'; + +// // class AttendanceScreen extends StatelessWidget { +// // const AttendanceScreen({super.key}); + +// // @override +// // Widget build(BuildContext context) { +// // final screenWidth = MediaQuery.sizeOf(context).width; +// // final screenHeight = MediaQuery.sizeOf(context).height; +// // return Directionality( +// // textDirection: TextDirection.ltr, +// // child: Stack( +// // children: [ +// // SizedBox(height: MediaQuery.of(context).size.height), + +// // /// ------------------------------ +// // /// SETTINGS BAR (STATIC) +// // /// ------------------------------ +// // SafeArea( +// // child: SettingsBar( +// // selectedIndex: 0, +// // showBackButton: false, +// // iconPaths: ['assets/images/user.svg', 'assets/images/ball.svg'], +// // onTap: (index) { +// // if (index == 0) { +// // Navigator.push( +// // context, +// // MaterialPageRoute( +// // builder: (context) => UserSettingsScreen(), +// // ), +// // ); +// // } else if (index == 1) { +// // Navigator.push( +// // context, +// // MaterialPageRoute( +// // builder: (context) => NotificationsScreen(), +// // ), +// // ); +// // } +// // }, +// // ), +// // ), + +// // /// ------------------------------ +// // /// GREETING TEXT +// // /// ------------------------------ +// // Positioned( +// // top: +// // screenHeight * +// // 0.14, // moved down because settings bar now exists +// // left: 0, +// // right: 0, +// // child: Center( +// // child: Text( +// // "صباح الخير, محمد", +// // style: TextStyle( +// // fontSize: 24, +// // fontWeight: FontWeight.w600, +// // color: Colors.white, +// // shadows: [Shadow(color: Color(0x42000000), blurRadius: 6)], +// // ), +// // ), +// // ), +// // ), + +// // /// ------------------------------ +// // /// MAIN CARD AREA +// // /// ------------------------------ +// // Positioned( +// // top: +// // screenHeight * +// // 0.2, // pushed down because of settings bar + greeting +// // left: 0, +// // right: 0, +// // child: Center( +// // child: Padding( +// // padding: EdgeInsets.symmetric(vertical: screenHeight * 0.05), +// // child: Stack( +// // children: [ +// // Container( +// // height: screenHeight * 0.5, +// // width: screenWidth * 0.7, +// // decoration: BoxDecoration( +// // borderRadius: BorderRadius.circular(32), +// // boxShadow: [ +// // BoxShadow( +// // color: Color(0x1F2B2B2B), +// // blurRadius: 5, +// // offset: Offset(10, -10), +// // ), +// // BoxShadow( +// // color: Color(0xABCECECE), +// // blurRadius: 5, +// // offset: Offset(-2, 5), +// // ), +// // BoxShadow( +// // color: Color.fromARGB(148, 2, 70, 35), +// // blurRadius: 80, +// // offset: Offset(0, 10), +// // ), +// // ], +// // ), +// // ), +// // Container( +// // height: screenHeight * 0.5, +// // width: screenWidth * 0.7, +// // decoration: BoxDecoration( +// // color: Color(0x92757575), +// // borderRadius: BorderRadius.circular(32), +// // ), +// // ), +// // ], +// // ), +// // ), +// // ), +// // ), + +// // /// ------------------------------ +// // /// LOGIN BUTTON +// // /// ------------------------------ +// // Positioned( +// // top: screenHeight * 0.21, +// // left: screenWidth * 0.05, +// // child: _ShadowedCard( +// // shadow: [ +// // BoxShadow( +// // color: Color(0x62000000), +// // blurRadius: 10, +// // spreadRadius: 5, +// // offset: Offset(5, 5), +// // ), +// // ], +// // child: _FingerButton( +// // icon: "assets/images/faceLogin.svg", +// // label: "تسجيل الدخول", +// // 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; +// // } + +// // // ------------------------------ +// // // ACTIVE SESSION CHECK (LOGIN) +// // // ------------------------------ +// // try { +// // // Optional: Show a loading dialog if it takes too long +// // final hasActive = await sl() +// // .hasActiveLogin(employeeId: employeeId); + +// // if (hasActive) { +// // if (context.mounted) { +// // ScaffoldMessenger.of(context).showSnackBar( +// // const SnackBar(content: Text('أنت مسجل دخول بالفعل')), +// // ); +// // } +// // return; +// // } +// // } catch (e) { +// // if (context.mounted) { +// // ScaffoldMessenger.of(context).showSnackBar( +// // SnackBar(content: Text('فشل التحقق من الجلسة: $e')), +// // ); +// // } +// // 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, +// // ), +// // ); +// // }, +// // checkIfLoggedIn: () {}, +// // ), +// // ), +// // ); +// // } +// // }, +// // ), +// // ), +// // ), + +// // /// ------------------------------ +// // /// LOGOUT BUTTON +// // /// ------------------------------ +// // Positioned( +// // bottom: screenHeight * 0.2, +// // right: screenWidth * 0.1, +// // child: _ShadowedCard( +// // shadow: [ +// // BoxShadow( +// // color: Color(0xABCECECE), +// // blurRadius: 5, +// // spreadRadius: 3, +// // offset: Offset(-6, -6), +// // ), +// // BoxShadow( +// // color: Color(0x92014221), +// // blurRadius: 10, +// // offset: Offset(-5, -5), +// // ), +// // BoxShadow( +// // color: Color(0x7D1A1A1A), +// // blurRadius: 10, +// // spreadRadius: 3, +// // offset: Offset(5, 5), +// // ), +// // ], +// // child: _FingerButton( +// // icon: "assets/images/faceLogout.svg", +// // label: "تسجيل خروج", +// // onTap: () async { +// // final employeeId = +// // await sl().getCachedEmployeeId(); +// // if (employeeId == null) { +// // if (context.mounted) { +// // ScaffoldMessenger.of(context).showSnackBar( +// // const SnackBar( +// // content: Text('خطأ: لم يتم العثور على رقم الموظف'), +// // ), +// // ); +// // } +// // return; +// // } + +// // // ------------------------------ +// // // ACTIVE SESSION CHECK (LOGOUT) +// // // ------------------------------ +// // try { +// // final hasActive = await sl() +// // .hasActiveLogin(employeeId: employeeId); + +// // if (!hasActive) { +// // if (context.mounted) { +// // ScaffoldMessenger.of(context).showSnackBar( +// // const SnackBar( +// // content: Text( +// // 'لا يوجد تسجيل دخول فعال لتسجيل الخروج', +// // ), +// // ), +// // ); +// // } +// // return; +// // } +// // } catch (e) { +// // if (context.mounted) { +// // ScaffoldMessenger.of(context).showSnackBar( +// // SnackBar(content: Text('فشل التحقق من الجلسة: $e')), +// // ); +// // } +// // 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, +// // ), +// // ); +// // }, +// // checkIfLoggedIn: () {}, +// // ), +// // ), +// // ); +// // } +// // }, +// // ), +// // ), +// // ), +// // ], +// // ), +// // ); +// // } +// // } + +// // /// --------------------------------------------- +// // /// SHADOW WRAPPER +// // /// --------------------------------------------- + +// // class _ShadowedCard extends StatelessWidget { +// // final Widget child; +// // final List shadow; + +// // const _ShadowedCard({required this.child, required this.shadow}); + +// // @override +// // Widget build(BuildContext context) { +// // return Stack( +// // children: [ +// // Container( +// // height: 160, +// // width: 160, +// // decoration: BoxDecoration( +// // borderRadius: BorderRadius.circular(32), +// // boxShadow: shadow, +// // ), +// // ), +// // child, +// // ], +// // ); +// // } +// // } + +// // /// --------------------------------------------- +// // /// BUTTON WIDGET +// // /// --------------------------------------------- + +// // class _FingerButton extends StatelessWidget { +// // final String icon; +// // final String label; +// // final VoidCallback onTap; + +// // const _FingerButton({ +// // required this.icon, +// // required this.label, +// // required this.onTap, +// // }); + +// // @override +// // Widget build(BuildContext context) { +// // return GestureDetector( +// // onTap: onTap, +// // child: Container( +// // height: 160, +// // width: 160, +// // decoration: BoxDecoration( +// // color: Color(0xFFEAFBF3), +// // borderRadius: BorderRadius.circular(32), +// // ), +// // child: Column( +// // mainAxisAlignment: MainAxisAlignment.center, +// // children: [ +// // SvgPicture.asset(icon, width: 75, height: 75), +// // SizedBox(height: 10), +// // Text( +// // label, +// // style: TextStyle( +// // fontSize: 18, +// // fontWeight: FontWeight.w600, +// // color: Colors.black, +// // ), +// // ), +// // ], +// // ), +// // ), +// // ); +// // } +// // } + // import 'package:coda_project/presentation/screens/face_screen2.dart'; // import 'package:coda_project/presentation/screens/notifications_screen.dart'; // import 'package:coda_project/presentation/screens/user_settings_screen.dart'; @@ -12,9 +401,31 @@ // import '../../data/datasources/user_local_data_source.dart'; // import '../../data/datasources/attendance_remote_data_source.dart'; -// class AttendanceScreen extends StatelessWidget { +// class AttendanceScreen extends StatefulWidget { // const AttendanceScreen({super.key}); +// @override +// State createState() => _AttendanceScreenState(); +// } + +// class _AttendanceScreenState extends State { +// String _userName = ''; + +// @override +// void initState() { +// super.initState(); +// _loadUserName(); +// } + +// Future _loadUserName() async { +// final name = await sl().getCachedFullName(); +// if (mounted) { +// setState(() { +// _userName = name ?? 'مستخدم'; +// }); +// } +// } + // @override // Widget build(BuildContext context) { // final screenWidth = MediaQuery.sizeOf(context).width; @@ -38,14 +449,14 @@ // Navigator.push( // context, // MaterialPageRoute( -// builder: (context) => UserSettingsScreen(), +// builder: (context) => const UserSettingsScreen(), // ), // ); // } else if (index == 1) { // Navigator.push( // context, // MaterialPageRoute( -// builder: (context) => NotificationsScreen(), +// builder: (context) => const NotificationsScreen(), // ), // ); // } @@ -57,14 +468,12 @@ // /// GREETING TEXT // /// ------------------------------ // Positioned( -// top: -// screenHeight * -// 0.14, // moved down because settings bar now exists +// top: screenHeight * 0.14, // left: 0, // right: 0, // child: Center( // child: Text( -// "صباح الخير, محمد", +// "صباح الخير $_userName", // style: TextStyle( // fontSize: 24, // fontWeight: FontWeight.w600, @@ -79,9 +488,7 @@ // /// MAIN CARD AREA // /// ------------------------------ // Positioned( -// top: -// screenHeight * -// 0.2, // pushed down because of settings bar + greeting +// top: screenHeight * 0.2, // left: 0, // right: 0, // child: Center( @@ -95,20 +502,20 @@ // decoration: BoxDecoration( // borderRadius: BorderRadius.circular(32), // boxShadow: [ -// BoxShadow( +// const BoxShadow( // color: Color(0x1F2B2B2B), // blurRadius: 5, // offset: Offset(10, -10), // ), -// BoxShadow( +// const BoxShadow( // color: Color(0xABCECECE), // blurRadius: 5, // offset: Offset(-2, 5), // ), // BoxShadow( -// color: Color.fromARGB(148, 2, 70, 35), +// color: const Color.fromARGB(148, 2, 70, 35), // blurRadius: 80, -// offset: Offset(0, 10), +// offset: const Offset(0, 10), // ), // ], // ), @@ -117,7 +524,7 @@ // height: screenHeight * 0.5, // width: screenWidth * 0.7, // decoration: BoxDecoration( -// color: Color(0x92757575), +// color: const Color(0x92757575), // borderRadius: BorderRadius.circular(32), // ), // ), @@ -135,7 +542,7 @@ // left: screenWidth * 0.05, // child: _ShadowedCard( // shadow: [ -// BoxShadow( +// const BoxShadow( // color: Color(0x62000000), // blurRadius: 10, // spreadRadius: 5, @@ -160,18 +567,33 @@ // return; // } -// // ------------------------------ // // ACTIVE SESSION CHECK (LOGIN) -// // ------------------------------ // try { -// // Optional: Show a loading dialog if it takes too long // final hasActive = await sl() // .hasActiveLogin(employeeId: employeeId); // if (hasActive) { // if (context.mounted) { -// ScaffoldMessenger.of(context).showSnackBar( -// const SnackBar(content: Text('أنت مسجل دخول بالفعل')), +// showDialog( +// context: context, +// builder: +// (_) => AlertDialog( +// title: const Text( +// 'تنبيه', +// textAlign: TextAlign.center, +// ), +// content: const Text( +// 'أنت مسجل دخول بالفعل، لا يمكنك تسجيل الدخول مرة أخرى.', +// textAlign: TextAlign.center, +// ), +// actions: [ +// TextButton( +// onPressed: +// () => Navigator.of(context).pop(), +// child: const Text('حسناً'), +// ), +// ], +// ), // ); // } // return; @@ -186,25 +608,51 @@ // } // if (context.mounted) { -// Navigator.of(context).push( +// final result = await Navigator.of(context).push( // MaterialPageRoute( // builder: // (_) => OvalCameraCapturePage( // isLogin: true, -// onCapture: (imageFile) async { +// onCapture: ( +// imageFile, { +// required bool localAuth, +// }) async { // final loginUseCase = // sl(); + // await loginUseCase( // AttendanceLoginRequest( // employeeId: employeeId, // faceImage: imageFile, +// localAuth: localAuth, // ✅ NEW FIELD // ), // ); // }, -// checkIfLoggedIn: () {}, // ), // ), // ); + +// if (!context.mounted) return; + +// if (result == "local_auth_failed") { +// ScaffoldMessenger.of(context).showSnackBar( +// const SnackBar( +// content: Text("فشل التحقق بالبصمة أو رمز القفل"), +// ), +// ); +// } else if (result == "retry_failed") { +// ScaffoldMessenger.of(context).showSnackBar( +// const SnackBar( +// content: Text("فشل تسجيل الحضور بعد التحقق المحلي"), +// ), +// ); +// } else if (result == "retry_missing_file") { +// ScaffoldMessenger.of(context).showSnackBar( +// const SnackBar( +// content: Text("تعذر إعادة المحاولة. حاول مرة أخرى."), +// ), +// ); +// } // } // }, // ), @@ -218,7 +666,7 @@ // bottom: screenHeight * 0.2, // right: screenWidth * 0.1, // child: _ShadowedCard( -// shadow: [ +// shadow: const [ // BoxShadow( // color: Color(0xABCECECE), // blurRadius: 5, @@ -254,21 +702,33 @@ // return; // } -// // ------------------------------ // // ACTIVE SESSION CHECK (LOGOUT) -// // ------------------------------ // try { // final hasActive = await sl() // .hasActiveLogin(employeeId: employeeId); // if (!hasActive) { // if (context.mounted) { -// ScaffoldMessenger.of(context).showSnackBar( -// const SnackBar( -// content: Text( -// 'لا يوجد تسجيل دخول فعال لتسجيل الخروج', -// ), -// ), +// showDialog( +// context: context, +// builder: +// (_) => AlertDialog( +// title: const Text( +// 'تنبيه', +// textAlign: TextAlign.center, +// ), +// content: const Text( +// 'لا يوجد تسجيل دخول فعال لتسجيل الخروج.', +// textAlign: TextAlign.center, +// ), +// actions: [ +// TextButton( +// onPressed: +// () => Navigator.of(context).pop(), +// child: const Text('حسناً'), +// ), +// ], +// ), // ); // } // return; @@ -298,7 +758,6 @@ // ), // ); // }, -// checkIfLoggedIn: () {}, // ), // ), // ); @@ -364,17 +823,17 @@ // height: 160, // width: 160, // decoration: BoxDecoration( -// color: Color(0xFFEAFBF3), +// color: const Color(0xFFEAFBF3), // borderRadius: BorderRadius.circular(32), // ), // child: Column( // mainAxisAlignment: MainAxisAlignment.center, // children: [ // SvgPicture.asset(icon, width: 75, height: 75), -// SizedBox(height: 10), +// const SizedBox(height: 10), // Text( // label, -// style: TextStyle( +// style: const TextStyle( // fontSize: 18, // fontWeight: FontWeight.w600, // color: Colors.black, @@ -392,7 +851,9 @@ import 'package:coda_project/presentation/screens/notifications_screen.dart'; 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'; @@ -426,37 +887,46 @@ class _AttendanceScreenState extends State { } } + void _showResultSnack(String message) { + if (!mounted) return; + ScaffoldMessenger.of( + context, + ).showSnackBar(SnackBar(content: Text(message))); + } + @override Widget build(BuildContext context) { final screenWidth = MediaQuery.sizeOf(context).width; final screenHeight = MediaQuery.sizeOf(context).height; + return Directionality( textDirection: TextDirection.ltr, child: Stack( children: [ SizedBox(height: MediaQuery.of(context).size.height), - /// ------------------------------ - /// SETTINGS BAR (STATIC) - /// ------------------------------ + /// SETTINGS BAR SafeArea( child: SettingsBar( selectedIndex: 0, showBackButton: false, - iconPaths: ['assets/images/user.svg', 'assets/images/ball.svg'], + iconPaths: const [ + 'assets/images/user.svg', + 'assets/images/ball.svg', + ], onTap: (index) { if (index == 0) { Navigator.push( context, MaterialPageRoute( - builder: (context) => const UserSettingsScreen(), + builder: (_) => const UserSettingsScreen(), ), ); } else if (index == 1) { Navigator.push( context, MaterialPageRoute( - builder: (context) => const NotificationsScreen(), + builder: (_) => const NotificationsScreen(), ), ); } @@ -464,9 +934,7 @@ class _AttendanceScreenState extends State { ), ), - /// ------------------------------ - /// GREETING TEXT - /// ------------------------------ + /// GREETING Positioned( top: screenHeight * 0.14, left: 0, @@ -474,7 +942,7 @@ class _AttendanceScreenState extends State { child: Center( child: Text( "صباح الخير $_userName", - style: TextStyle( + style: const TextStyle( fontSize: 24, fontWeight: FontWeight.w600, color: Colors.white, @@ -484,9 +952,7 @@ class _AttendanceScreenState extends State { ), ), - /// ------------------------------ /// MAIN CARD AREA - /// ------------------------------ Positioned( top: screenHeight * 0.2, left: 0, @@ -534,15 +1000,13 @@ class _AttendanceScreenState extends State { ), ), - /// ------------------------------ /// LOGIN BUTTON - /// ------------------------------ Positioned( top: screenHeight * 0.21, left: screenWidth * 0.05, child: _ShadowedCard( - shadow: [ - const BoxShadow( + shadow: const [ + BoxShadow( color: Color(0x62000000), blurRadius: 10, spreadRadius: 5, @@ -555,15 +1019,9 @@ class _AttendanceScreenState extends State { 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('خطأ: لم يتم العثور على رقم الموظف'), - ), - ); - } + _showResultSnack('خطأ: لم يتم العثور على رقم الموظف'); return; } @@ -573,68 +1031,74 @@ class _AttendanceScreenState extends State { .hasActiveLogin(employeeId: employeeId); if (hasActive) { - if (context.mounted) { - showDialog( - context: context, - builder: - (_) => AlertDialog( - title: const Text( - 'تنبيه', - textAlign: TextAlign.center, - ), - content: const Text( - 'أنت مسجل دخول بالفعل، لا يمكنك تسجيل الدخول مرة أخرى.', - textAlign: TextAlign.center, - ), - actions: [ - TextButton( - onPressed: - () => Navigator.of(context).pop(), - child: const Text('حسناً'), - ), - ], + if (!mounted) return; + showDialog( + context: context, + builder: + (_) => AlertDialog( + title: const Text( + 'تنبيه', + textAlign: TextAlign.center, ), - ); - } + content: const Text( + 'أنت مسجل دخول بالفعل، لا يمكنك تسجيل الدخول مرة أخرى.', + textAlign: TextAlign.center, + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('حسناً'), + ), + ], + ), + ); return; } } catch (e) { - if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('فشل التحقق من الجلسة: $e')), - ); - } + _showResultSnack('فشل التحقق من الجلسة: $e'); 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, - ), - ); - }, - ), - ), - ); + if (!mounted) return; + + final result = await Navigator.of(context).push( + MaterialPageRoute( + builder: + (_) => OvalCameraCapturePage( + isLogin: true, + onCapture: ( + imageFile, { + required bool localAuth, + }) async { + final loginUseCase = sl(); + + await loginUseCase( + AttendanceLoginRequest( + employeeId: employeeId, + faceImage: imageFile, + localAuth: localAuth, // ✅ + ), + ); + }, + ), + ), + ); + + if (!mounted) return; + + if (result == "local_auth_failed") { + _showResultSnack("فشل التحقق بالبصمة أو رمز القفل"); + } else if (result == "retry_failed") { + _showResultSnack("فشل تسجيل الحضور بعد التحقق المحلي"); + } else if (result == "retry_missing_file") { + _showResultSnack("تعذر إعادة المحاولة. حاول مرة أخرى."); } }, ), ), ), - /// ------------------------------ /// LOGOUT BUTTON - /// ------------------------------ Positioned( bottom: screenHeight * 0.2, right: screenWidth * 0.1, @@ -664,14 +1128,9 @@ class _AttendanceScreenState extends State { onTap: () async { final employeeId = await sl().getCachedEmployeeId(); + if (employeeId == null) { - if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - const SnackBar( - content: Text('خطأ: لم يتم العثور على رقم الموظف'), - ), - ); - } + _showResultSnack('خطأ: لم يتم العثور على رقم الموظف'); return; } @@ -681,59 +1140,68 @@ class _AttendanceScreenState extends State { .hasActiveLogin(employeeId: employeeId); if (!hasActive) { - if (context.mounted) { - showDialog( - context: context, - builder: - (_) => AlertDialog( - title: const Text( - 'تنبيه', - textAlign: TextAlign.center, - ), - content: const Text( - 'لا يوجد تسجيل دخول فعال لتسجيل الخروج.', - textAlign: TextAlign.center, - ), - actions: [ - TextButton( - onPressed: - () => Navigator.of(context).pop(), - child: const Text('حسناً'), - ), - ], + if (!mounted) return; + showDialog( + context: context, + builder: + (_) => AlertDialog( + title: const Text( + 'تنبيه', + textAlign: TextAlign.center, ), - ); - } + content: const Text( + 'لا يوجد تسجيل دخول فعال لتسجيل الخروج.', + textAlign: TextAlign.center, + ), + actions: [ + TextButton( + onPressed: () => Navigator.of(context).pop(), + child: const Text('حسناً'), + ), + ], + ), + ); return; } } catch (e) { - if (context.mounted) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text('فشل التحقق من الجلسة: $e')), - ); - } + _showResultSnack('فشل التحقق من الجلسة: $e'); 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, - ), - ); - }, - ), - ), - ); + if (!mounted) return; + + final result = await Navigator.of(context).push( + MaterialPageRoute( + builder: + (_) => OvalCameraCapturePage( + isLogin: false, + onCapture: ( + imageFile, { + required bool localAuth, + }) async { + final logoutUseCase = + sl(); + + await logoutUseCase( + AttendanceLogoutRequest( + employeeId: employeeId, + faceImage: imageFile, + localAuth: localAuth, // ✅ + ), + ); + }, + ), + ), + ); + + if (!mounted) return; + + if (result == "local_auth_failed") { + _showResultSnack("فشل التحقق بالبصمة أو رمز القفل"); + } else if (result == "retry_failed") { + _showResultSnack("فشل تسجيل الخروج بعد التحقق المحلي"); + } else if (result == "retry_missing_file") { + _showResultSnack("تعذر إعادة المحاولة. حاول مرة أخرى."); } }, ), @@ -745,10 +1213,7 @@ class _AttendanceScreenState extends State { } } -/// --------------------------------------------- /// SHADOW WRAPPER -/// --------------------------------------------- - class _ShadowedCard extends StatelessWidget { final Widget child; final List shadow; @@ -773,10 +1238,7 @@ class _ShadowedCard extends StatelessWidget { } } -/// --------------------------------------------- /// BUTTON WIDGET -/// --------------------------------------------- - class _FingerButton extends StatelessWidget { final String icon; final String label; diff --git a/lib/presentation/screens/auth_screen.dart b/lib/presentation/screens/auth_screen.dart index 8dfcb16..5cf7dc1 100644 --- a/lib/presentation/screens/auth_screen.dart +++ b/lib/presentation/screens/auth_screen.dart @@ -20,7 +20,18 @@ class AuthScreen extends StatelessWidget { children: [ const SizedBox(height: 60), // Logo - Center(child: Image.asset("assets/images/logo2.png", width: 200)), + // Center(child: Image.asset("assets/images/logo2.png", width: 200)), + const Center( + child: Text( + 'LOGO', + style: TextStyle( + fontSize: 36, + fontWeight: FontWeight.bold, + color: Colors.white, + letterSpacing: 2, + ), + ), + ), // const SizedBox(height: 15), // Form - taking remaining space and centered Expanded(child: Center(child: const AuthForm())), diff --git a/lib/presentation/screens/face_screen2.dart b/lib/presentation/screens/face_screen2.dart index a6cdc4d..36587f8 100644 --- a/lib/presentation/screens/face_screen2.dart +++ b/lib/presentation/screens/face_screen2.dart @@ -12,10 +12,11 @@ import 'package:google_mlkit_face_detection/google_mlkit_face_detection.dart'; import '../../core/error/exceptions.dart'; import '../face/face_feedback.dart'; +import 'package:local_auth/local_auth.dart'; class OvalCameraCapturePage extends StatefulWidget { final bool isLogin; - final Future Function(File image) onCapture; + final Future Function(File image, {required bool localAuth}) onCapture; const OvalCameraCapturePage({ super.key, @@ -37,6 +38,13 @@ class _OvalCameraCapturePageState extends State { bool _isSubmitting = false; bool _isStreaming = false; + //to handel the state of auth when it gives 422 status code + + final LocalAuthentication _localAuth = LocalAuthentication(); + + bool _handlingAuth422 = false; // prevents multiple dialogs/auth prompts + File? _lastCapturedFile; // keep the same image for retry + // Smart feedback FaceFeedback _feedback = FaceFeedback( type: FaceHintType.noFace, @@ -252,6 +260,49 @@ class _OvalCameraCapturePageState extends State { } } + Future _showLocalAuthDialog() { + return showDialog( + context: context, + barrierDismissible: false, + builder: + (_) => AlertDialog( + title: const Text('فشل التحقق بالوجه', textAlign: TextAlign.center), + content: const Text( + 'لم يتم التعرف على الوجه.\n\nيرجى استخدام بصمة الإصبع أو رمز القفل (PIN/النمط) للمتابعة.', + textAlign: TextAlign.center, + ), + actions: [ + TextButton( + onPressed: () => Navigator.pop(context, false), + child: const Text('إلغاء'), + ), + ElevatedButton( + onPressed: () => Navigator.pop(context, true), + child: const Text('استخدام البصمة / رمز القفل'), + ), + ], + ), + ); + } + + Future _authenticateLocally() async { + try { + final isSupported = await _localAuth.isDeviceSupported(); + if (!isSupported) return false; + + return await _localAuth.authenticate( + localizedReason: 'تأكيد هويتك لإكمال تسجيل الحضور.', + options: const AuthenticationOptions( + biometricOnly: false, // ✅ allows PIN/pattern fallback + stickyAuth: true, + useErrorDialogs: true, + ), + ); + } catch (_) { + return false; + } + } + Future _stopImageStream() async { if (!_isStreaming || _cameraController == null) return; try { @@ -408,7 +459,9 @@ class _OvalCameraCapturePageState extends State { final xFile = await _cameraController!.takePicture(); final file = File(xFile.path); - await widget.onCapture(file); + _lastCapturedFile = file; + + await widget.onCapture(file, localAuth: false); if (mounted) { setState(() { @@ -423,8 +476,13 @@ class _OvalCameraCapturePageState extends State { }); } } on ServerException catch (e) { - // Check if this is an "already logged in" error from the API final msg = e.message.toLowerCase(); + // If your ServerException has statusCode, prefer that: + if (e.statusCode == 422 || msg.contains('face verification failed')) { + await _handleFaceVerificationFailed422(e); + return; + } + if (msg.contains('already logged in') || msg.contains('مسجل دخول بالفعل')) { // Stop camera and go back with a dialog @@ -545,6 +603,86 @@ class _OvalCameraCapturePageState extends State { return null; } + Future _handleFaceVerificationFailed422(ServerException e) async { + if (!mounted) return; + if (_handlingAuth422) return; + + _handlingAuth422 = true; + + // stop everything so camera doesn’t keep scanning + await _stopImageStream(); + + setState(() { + _isSubmitting = false; + _errorMessage = null; + _debugInfo = "Face verification failed (422) → Local Auth..."; + }); + + final proceed = await _showLocalAuthDialog(); + if (proceed != true) { + _handlingAuth422 = false; + _stopCameraCompletely(); + if (!mounted) return; + + // Go back to attendance + show message there + Navigator.of(context).pop(false); + // If you prefer to show inside this screen before pop: + // ScaffoldMessenger.of(context).showSnackBar(const SnackBar(content: Text('تم الإلغاء.'))); + return; + } + + final ok = await _authenticateLocally(); + if (!ok) { + _handlingAuth422 = false; + _stopCameraCompletely(); + if (!mounted) return; + + // Return to attendance; attendance screen should show snack "failed fingerprint/pattern" + Navigator.of(context).pop("local_auth_failed"); + return; + } + + // Local auth success → retry SAME image with localAuth=true + final file = _lastCapturedFile; + if (file == null) { + _handlingAuth422 = false; + _stopCameraCompletely(); + if (!mounted) return; + Navigator.of(context).pop("retry_missing_file"); + return; + } + + setState(() { + _isSubmitting = true; + _debugInfo = "Local auth success → retrying with localAuth=true..."; + }); + + try { + await widget.onCapture(file, localAuth: true); + + if (!mounted) return; + setState(() { + _isSuccess = true; + _isSubmitting = false; + }); + + Future.delayed(const Duration(seconds: 1), () { + if (mounted) Navigator.of(context).pop(true); + }); + } on ServerException catch (e2) { + _handlingAuth422 = false; + _stopCameraCompletely(); + if (!mounted) return; + // Retry failed → go back to attendance + Navigator.of(context).pop("retry_failed"); + } catch (_) { + _handlingAuth422 = false; + _stopCameraCompletely(); + if (!mounted) return; + Navigator.of(context).pop("retry_failed"); + } + } + Uint8List _convertYUV420ToNV21(CameraImage image) { final int width = image.width; final int height = image.height; diff --git a/lib/presentation/screens/onboarding_screen.dart b/lib/presentation/screens/onboarding_screen.dart index 7ff4747..4bef705 100644 --- a/lib/presentation/screens/onboarding_screen.dart +++ b/lib/presentation/screens/onboarding_screen.dart @@ -88,7 +88,16 @@ class _OnboardingScreenState extends State { Column( children: [ const SizedBox(height: 70), - Image.asset("assets/images/logo2.png", width: 200), + // Image.asset("assets/images/logo2.png", width: 200), + const Text( + 'LOGO', + style: TextStyle( + fontSize: 36, + fontWeight: FontWeight.bold, + color: Colors.white, + letterSpacing: 2, + ), + ), /// PAGEVIEW (SVG + TEXT ONLY) Expanded( diff --git a/lib/presentation/screens/splash_screen.dart b/lib/presentation/screens/splash_screen.dart index 4a08286..fa4d2cb 100644 --- a/lib/presentation/screens/splash_screen.dart +++ b/lib/presentation/screens/splash_screen.dart @@ -31,7 +31,7 @@ class _SplashScreenState extends State { final token = await sl().getCachedUserToken(); if (token != null && token.isNotEmpty) { - // Token exists, navigate directly to MainPage + // Token exists — go to MainPage (theme already loaded in main.dart) Navigator.pushReplacement( context, MaterialPageRoute(builder: (_) => const MainPage()), @@ -65,7 +65,18 @@ class _SplashScreenState extends State { fit: BoxFit.cover, ), ), - child: Center(child: Image.asset("assets/images/logo.png", width: 200)), + // child: Center(child: Image.asset("assets/images/logo.png", width: 200)), + child: const Center( + child: Text( + 'LOGO', + style: TextStyle( + fontSize: 36, + fontWeight: FontWeight.bold, + color: Colors.white, + letterSpacing: 2, + ), + ), + ), ), ); } diff --git a/lib/presentation/widgets/settings_bar.dart b/lib/presentation/widgets/settings_bar.dart index 9214c5c..2208f8f 100644 --- a/lib/presentation/widgets/settings_bar.dart +++ b/lib/presentation/widgets/settings_bar.dart @@ -12,7 +12,7 @@ class SettingsBar extends StatelessWidget { super.key, required this.selectedIndex, required this.onTap, - this.showBackButton = false, // to switch between back button and settings icons + this.showBackButton = false, this.onBackTap, required this.iconPaths, }); @@ -25,10 +25,15 @@ class SettingsBar extends StatelessWidget { child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - Row( - children: [ - Image.asset('assets/images/logo2.png', width: 150, height: 40), - ], + // Text Logo + const Text( + 'LOGO', + style: TextStyle( + fontSize: 28, + fontWeight: FontWeight.bold, + color: Colors.white, + letterSpacing: 2, + ), ), Row( children: [ @@ -50,11 +55,10 @@ class SettingsBar extends StatelessWidget { ], ), child: Center( - // Always use Flutter's built-in back icon pointing to the right child: const Icon( - Icons.arrow_forward, // Changed to arrow_forward for RTL + Icons.arrow_forward, size: 26, - color: Colors.black, // Adjust color as needed + color: Colors.black, ), ), ), @@ -65,7 +69,6 @@ class SettingsBar extends StatelessWidget { ...iconPaths.asMap().entries.map((entry) { final index = entry.key; final iconPath = entry.value; - // final isSelected = selectedIndex == index; return Padding( padding: const EdgeInsets.only(left: 10), @@ -102,4 +105,4 @@ class SettingsBar extends StatelessWidget { ), ); } -} \ No newline at end of file +} diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 724bb2a..80dc39e 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -5,8 +5,10 @@ import FlutterMacOS import Foundation +import local_auth_darwin import shared_preferences_foundation func RegisterGeneratedPlugins(registry: FlutterPluginRegistry) { + LocalAuthPlugin.register(with: registry.registrar(forPlugin: "LocalAuthPlugin")) SharedPreferencesPlugin.register(with: registry.registrar(forPlugin: "SharedPreferencesPlugin")) } diff --git a/pubspec.lock b/pubspec.lock index 00cefaf..0d99a82 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -29,10 +29,10 @@ packages: dependency: transitive description: name: async - sha256: "758e6d74e971c3e5aceb4110bfd6698efc7f501675bcfe0c775459a8140750eb" + sha256: d2872f9c19731c2e5f10444b14686eb7cc85c76274bd6c16e1816bff9a3bab63 url: "https://pub.dev" source: hosted - version: "2.13.0" + version: "2.12.0" bloc: dependency: transitive description: @@ -189,10 +189,10 @@ packages: dependency: transitive description: name: fake_async - sha256: "5368f224a74523e8d2e7399ea1638b37aecfca824a3cc4dfdf77bf1fa905ac44" + sha256: "6a95e56b2449df2273fd8c45a662d6947ce1ebb7aafe80e550a3f68297f3cacc" url: "https://pub.dev" source: hosted - version: "1.3.3" + version: "1.3.2" ffi: dependency: transitive description: @@ -348,10 +348,10 @@ packages: dependency: transitive description: name: leak_tracker - sha256: "6bb818ecbdffe216e81182c2f0714a2e62b593f4a4f13098713ff1685dfb6ab0" + sha256: c35baad643ba394b40aac41080300150a4f08fd0fd6a10378f8f7c6bc161acec url: "https://pub.dev" source: hosted - version: "10.0.9" + version: "10.0.8" leak_tracker_flutter_testing: dependency: transitive description: @@ -376,6 +376,46 @@ packages: url: "https://pub.dev" source: hosted version: "5.1.1" + local_auth: + dependency: "direct main" + description: + name: local_auth + sha256: "434d854cf478f17f12ab29a76a02b3067f86a63a6d6c4eb8fbfdcfe4879c1b7b" + url: "https://pub.dev" + source: hosted + version: "2.3.0" + local_auth_android: + dependency: transitive + description: + name: local_auth_android + sha256: "48924f4a8b3cc45994ad5993e2e232d3b00788a305c1bf1c7db32cef281ce9a3" + url: "https://pub.dev" + source: hosted + version: "1.0.52" + local_auth_darwin: + dependency: transitive + description: + name: local_auth_darwin + sha256: "0e9706a8543a4a2eee60346294d6a633dd7c3ee60fae6b752570457c4ff32055" + url: "https://pub.dev" + source: hosted + version: "1.6.0" + local_auth_platform_interface: + dependency: transitive + description: + name: local_auth_platform_interface + sha256: f98b8e388588583d3f781f6806e4f4c9f9e189d898d27f0c249b93a1973dd122 + url: "https://pub.dev" + source: hosted + version: "1.1.0" + local_auth_windows: + dependency: transitive + description: + name: local_auth_windows + sha256: bc4e66a29b0fdf751aafbec923b5bed7ad6ed3614875d8151afe2578520b2ab5 + url: "https://pub.dev" + source: hosted + version: "1.0.11" matcher: dependency: transitive description: @@ -665,10 +705,10 @@ packages: dependency: transitive description: name: vm_service - sha256: ddfa8d30d89985b96407efce8acbdd124701f96741f2d981ca860662f1c0dc02 + sha256: "0968250880a6c5fe7edc067ed0a13d4bae1577fe2771dcf3010d52c4a9d3ca14" url: "https://pub.dev" source: hosted - version: "15.0.0" + version: "14.3.1" web: dependency: transitive description: diff --git a/pubspec.yaml b/pubspec.yaml index abf543d..802d696 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -20,6 +20,7 @@ dependencies: flutter_bloc: ^8.1.6 intl: ^0.19.0 google_mlkit_face_detection: ^0.12.0 + local_auth: ^2.1.8 dev_dependencies: flutter_test: diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 8b6d468..7407ddd 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -6,6 +6,9 @@ #include "generated_plugin_registrant.h" +#include void RegisterPlugins(flutter::PluginRegistry* registry) { + LocalAuthPluginRegisterWithRegistrar( + registry->GetRegistrarForPlugin("LocalAuthPlugin")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index b93c4c3..ef187dc 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -3,6 +3,7 @@ # list(APPEND FLUTTER_PLUGIN_LIST + local_auth_windows ) list(APPEND FLUTTER_FFI_PLUGIN_LIST