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