chnages has been made and net salary is being displayed
This commit is contained in:
@@ -28,7 +28,10 @@ import '../../domain/usecases/get_advances_usecase.dart';
|
|||||||
import '../../domain/usecases/get_extra_hours_usecase.dart';
|
import '../../domain/usecases/get_extra_hours_usecase.dart';
|
||||||
import '../../domain/usecases/get_rewards_usecase.dart';
|
import '../../domain/usecases/get_rewards_usecase.dart';
|
||||||
import '../../domain/usecases/get_punishments_usecase.dart';
|
import '../../domain/usecases/get_punishments_usecase.dart';
|
||||||
|
import '../../domain/usecases/get_salary_summary_usecase.dart';
|
||||||
|
import '../../domain/usecases/change_password_usecase.dart';
|
||||||
import '../../presentation/blocs/login/login_bloc.dart';
|
import '../../presentation/blocs/login/login_bloc.dart';
|
||||||
|
import '../../presentation/blocs/change_password/change_password_bloc.dart';
|
||||||
|
|
||||||
final sl = GetIt.instance;
|
final sl = GetIt.instance;
|
||||||
|
|
||||||
@@ -61,9 +64,11 @@ Future<void> initializeDependencies() async {
|
|||||||
|
|
||||||
// Use cases
|
// Use cases
|
||||||
sl.registerLazySingleton(() => LoginUseCase(repository: sl()));
|
sl.registerLazySingleton(() => LoginUseCase(repository: sl()));
|
||||||
|
sl.registerLazySingleton(() => ChangePasswordUseCase(repository: sl()));
|
||||||
|
|
||||||
// Blocs
|
// Blocs
|
||||||
sl.registerFactory(() => LoginBloc(loginUseCase: sl()));
|
sl.registerFactory(() => LoginBloc(loginUseCase: sl()));
|
||||||
|
sl.registerFactory(() => ChangePasswordBloc(changePasswordUseCase: sl()));
|
||||||
|
|
||||||
//Attendence
|
//Attendence
|
||||||
sl.registerLazySingleton<AttendanceRemoteDataSource>(
|
sl.registerLazySingleton<AttendanceRemoteDataSource>(
|
||||||
@@ -82,6 +87,7 @@ Future<void> initializeDependencies() async {
|
|||||||
sl.registerLazySingleton(() => GetExtraHoursUseCase(repository: sl()));
|
sl.registerLazySingleton(() => GetExtraHoursUseCase(repository: sl()));
|
||||||
sl.registerLazySingleton(() => GetRewardsUseCase(repository: sl()));
|
sl.registerLazySingleton(() => GetRewardsUseCase(repository: sl()));
|
||||||
sl.registerLazySingleton(() => GetPunishmentsUseCase(repository: sl()));
|
sl.registerLazySingleton(() => GetPunishmentsUseCase(repository: sl()));
|
||||||
|
sl.registerLazySingleton(() => GetSalarySummaryUseCase(sl()));
|
||||||
|
|
||||||
// Finance
|
// Finance
|
||||||
sl.registerFactory(
|
sl.registerFactory(
|
||||||
@@ -90,6 +96,7 @@ Future<void> initializeDependencies() async {
|
|||||||
getExtraHoursUseCase: sl(),
|
getExtraHoursUseCase: sl(),
|
||||||
getRewardsUseCase: sl(),
|
getRewardsUseCase: sl(),
|
||||||
getPunishmentsUseCase: sl(),
|
getPunishmentsUseCase: sl(),
|
||||||
|
getSalarySummaryUseCase: sl(),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,9 @@ class ServerException implements Exception {
|
|||||||
final int? statusCode;
|
final int? statusCode;
|
||||||
|
|
||||||
ServerException({required this.message, this.statusCode});
|
ServerException({required this.message, this.statusCode});
|
||||||
|
|
||||||
|
@override
|
||||||
|
String toString() => 'ServerException: $message (Status: $statusCode)';
|
||||||
}
|
}
|
||||||
|
|
||||||
class NetworkException implements Exception {
|
class NetworkException implements Exception {
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ import '../dto/attendance_record_dto.dart';
|
|||||||
import '../dto/overtime_dto.dart';
|
import '../dto/overtime_dto.dart';
|
||||||
import '../dto/reward_dto.dart';
|
import '../dto/reward_dto.dart';
|
||||||
import '../dto/punishment_dto.dart';
|
import '../dto/punishment_dto.dart';
|
||||||
|
import '../dto/salary_response_dto.dart';
|
||||||
|
|
||||||
abstract class AttendanceRemoteDataSource {
|
abstract class AttendanceRemoteDataSource {
|
||||||
Future<AttendanceResponseDto> login({
|
Future<AttendanceResponseDto> login({
|
||||||
@@ -25,6 +26,11 @@ abstract class AttendanceRemoteDataSource {
|
|||||||
Future<List<OvertimeDto>> getExtraHours({required String employeeId});
|
Future<List<OvertimeDto>> getExtraHours({required String employeeId});
|
||||||
Future<List<RewardDto>> getRewards({required String employeeId});
|
Future<List<RewardDto>> getRewards({required String employeeId});
|
||||||
Future<List<PunishmentDto>> getPunishments({required String employeeId});
|
Future<List<PunishmentDto>> getPunishments({required String employeeId});
|
||||||
|
Future<SalaryResponseDto> calculateSalary({
|
||||||
|
required String employeeId,
|
||||||
|
required int month,
|
||||||
|
required int year,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class AttendanceRemoteDataSourceImpl implements AttendanceRemoteDataSource {
|
class AttendanceRemoteDataSourceImpl implements AttendanceRemoteDataSource {
|
||||||
@@ -286,4 +292,66 @@ class AttendanceRemoteDataSourceImpl implements AttendanceRemoteDataSource {
|
|||||||
throw ServerException(message: 'خطأ غير متوقع');
|
throw ServerException(message: 'خطأ غير متوقع');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<SalaryResponseDto> calculateSalary({
|
||||||
|
required String employeeId,
|
||||||
|
required int month,
|
||||||
|
required int year,
|
||||||
|
}) async {
|
||||||
|
try {
|
||||||
|
final response = await apiClient.get(
|
||||||
|
'/SalaryRecord/calculate',
|
||||||
|
queryParameters: {
|
||||||
|
'EmployeeId': employeeId,
|
||||||
|
'Month': month,
|
||||||
|
'Year': year,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
print('Salary Response Status: ${response.statusCode}');
|
||||||
|
if (response.statusCode == 200 || response.statusCode == 201) {
|
||||||
|
final responseData = response.data;
|
||||||
|
print(
|
||||||
|
'Salary Response Data: $responseData (${responseData.runtimeType})',
|
||||||
|
);
|
||||||
|
|
||||||
|
if (responseData is Map<String, dynamic>) {
|
||||||
|
return SalaryResponseDto.fromJson(responseData);
|
||||||
|
} else if (responseData is num) {
|
||||||
|
// Handle case where API returns raw number
|
||||||
|
return SalaryResponseDto(
|
||||||
|
isSuccess: true,
|
||||||
|
message: 'Success',
|
||||||
|
data: SalaryDataDto(netAmount: responseData.toDouble()),
|
||||||
|
);
|
||||||
|
} else if (responseData is String &&
|
||||||
|
double.tryParse(responseData) != null) {
|
||||||
|
// Handle case where API returns raw numeric string
|
||||||
|
return SalaryResponseDto(
|
||||||
|
isSuccess: true,
|
||||||
|
message: 'Success',
|
||||||
|
data: SalaryDataDto(netAmount: double.parse(responseData)),
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
throw ServerException(
|
||||||
|
message: 'استجابة غير صحيحة من الخادم: $responseData',
|
||||||
|
statusCode: response.statusCode,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
throw ServerException(
|
||||||
|
message: 'فشل في حساب الراتب (Status: ${response.statusCode})',
|
||||||
|
statusCode: response.statusCode,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} on DioException catch (e) {
|
||||||
|
throw ServerException(
|
||||||
|
message: e.message ?? 'Unknown error',
|
||||||
|
statusCode: e.response?.statusCode,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
throw ServerException(message: 'خطأ غير متوقع');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,9 +3,14 @@ import '../../core/error/exceptions.dart';
|
|||||||
import '../../core/network/api_client.dart';
|
import '../../core/network/api_client.dart';
|
||||||
import '../dto/login_dto.dart';
|
import '../dto/login_dto.dart';
|
||||||
import '../dto/login_response_dto.dart';
|
import '../dto/login_response_dto.dart';
|
||||||
|
import '../dto/change_password_request_dto.dart';
|
||||||
|
import '../dto/change_password_response_dto.dart';
|
||||||
|
|
||||||
abstract class AuthRemoteDataSource {
|
abstract class AuthRemoteDataSource {
|
||||||
Future<LoginResponseDto> login(LoginDto dto);
|
Future<LoginResponseDto> login(LoginDto dto);
|
||||||
|
Future<ChangePasswordResponseDto> changePassword(
|
||||||
|
ChangePasswordRequestDto dto,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
class AuthRemoteDataSourceImpl implements AuthRemoteDataSource {
|
class AuthRemoteDataSourceImpl implements AuthRemoteDataSource {
|
||||||
@@ -72,4 +77,37 @@ class AuthRemoteDataSourceImpl implements AuthRemoteDataSource {
|
|||||||
throw ServerException(message: 'خطأ غير متوقع');
|
throw ServerException(message: 'خطأ غير متوقع');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<ChangePasswordResponseDto> changePassword(
|
||||||
|
ChangePasswordRequestDto dto,
|
||||||
|
) async {
|
||||||
|
try {
|
||||||
|
final response = await apiClient.post(
|
||||||
|
'/Auth/change-password',
|
||||||
|
data: dto.toJson(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.statusCode == 200) {
|
||||||
|
return ChangePasswordResponseDto.fromJson(response.data);
|
||||||
|
} else {
|
||||||
|
throw ServerException(
|
||||||
|
message: response.data['message'] ?? 'فشل تغيير كلمة المرور',
|
||||||
|
statusCode: response.statusCode,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
} on DioException catch (e) {
|
||||||
|
final message =
|
||||||
|
e.response?.data?['message'] ??
|
||||||
|
e.response?.data?['error'] ??
|
||||||
|
'فشل تغيير كلمة المرور';
|
||||||
|
throw ServerException(
|
||||||
|
message: message,
|
||||||
|
statusCode: e.response?.statusCode,
|
||||||
|
);
|
||||||
|
} catch (e) {
|
||||||
|
if (e is ServerException) rethrow;
|
||||||
|
throw ServerException(message: 'خطأ غير متوقع: $e');
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
13
lib/data/dto/change_password_request_dto.dart
Normal file
13
lib/data/dto/change_password_request_dto.dart
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
class ChangePasswordRequestDto {
|
||||||
|
final String oldPassword;
|
||||||
|
final String newPassword;
|
||||||
|
|
||||||
|
ChangePasswordRequestDto({
|
||||||
|
required this.oldPassword,
|
||||||
|
required this.newPassword,
|
||||||
|
});
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {'oldPassword': oldPassword, 'newPassword': newPassword};
|
||||||
|
}
|
||||||
|
}
|
||||||
36
lib/data/dto/change_password_response_dto.dart
Normal file
36
lib/data/dto/change_password_response_dto.dart
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
import 'login_response_dto.dart';
|
||||||
|
|
||||||
|
class ChangePasswordResponseDto {
|
||||||
|
final int statusCode;
|
||||||
|
final bool isSuccess;
|
||||||
|
final String message;
|
||||||
|
final LoginDataDto? data;
|
||||||
|
|
||||||
|
ChangePasswordResponseDto({
|
||||||
|
required this.statusCode,
|
||||||
|
required this.isSuccess,
|
||||||
|
required this.message,
|
||||||
|
this.data,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory ChangePasswordResponseDto.fromJson(Map<String, dynamic> json) {
|
||||||
|
return ChangePasswordResponseDto(
|
||||||
|
statusCode: json['statusCode'] ?? 0,
|
||||||
|
isSuccess: json['isSuccess'] ?? false,
|
||||||
|
message: json['message'] ?? '',
|
||||||
|
data:
|
||||||
|
json['data'] != null && json['data'] is Map<String, dynamic>
|
||||||
|
? LoginDataDto.fromJson(json['data'])
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Map<String, dynamic> toJson() {
|
||||||
|
return {
|
||||||
|
'statusCode': statusCode,
|
||||||
|
'isSuccess': isSuccess,
|
||||||
|
'message': message,
|
||||||
|
'data': data?.toJson(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
36
lib/data/dto/salary_response_dto.dart
Normal file
36
lib/data/dto/salary_response_dto.dart
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
class SalaryResponseDto {
|
||||||
|
final bool isSuccess;
|
||||||
|
final String message;
|
||||||
|
final SalaryDataDto? data;
|
||||||
|
|
||||||
|
SalaryResponseDto({
|
||||||
|
required this.isSuccess,
|
||||||
|
required this.message,
|
||||||
|
this.data,
|
||||||
|
});
|
||||||
|
|
||||||
|
factory SalaryResponseDto.fromJson(Map<String, dynamic> json) {
|
||||||
|
return SalaryResponseDto(
|
||||||
|
isSuccess: json['isSuccess'] ?? json['IsSuccess'] ?? false,
|
||||||
|
message: json['message'] ?? json['Message'] ?? '',
|
||||||
|
data:
|
||||||
|
json['data'] != null
|
||||||
|
? SalaryDataDto.fromJson(json['data'])
|
||||||
|
: json['Data'] != null
|
||||||
|
? SalaryDataDto.fromJson(json['Data'])
|
||||||
|
: null,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class SalaryDataDto {
|
||||||
|
final double netAmount;
|
||||||
|
|
||||||
|
SalaryDataDto({required this.netAmount});
|
||||||
|
|
||||||
|
factory SalaryDataDto.fromJson(Map<String, dynamic> json) {
|
||||||
|
return SalaryDataDto(
|
||||||
|
netAmount: (json['netAmount'] ?? json['NetAmount'] ?? 0.0).toDouble(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import '../../domain/models/attendance_response_model.dart';
|
|||||||
import '../../domain/models/attendance_model.dart';
|
import '../../domain/models/attendance_model.dart';
|
||||||
import '../../domain/models/overtime_model.dart';
|
import '../../domain/models/overtime_model.dart';
|
||||||
import '../../domain/models/extra_payment_model.dart';
|
import '../../domain/models/extra_payment_model.dart';
|
||||||
|
import '../../domain/models/salary_model.dart';
|
||||||
import '../../domain/repositories/attendance_repository.dart';
|
import '../../domain/repositories/attendance_repository.dart';
|
||||||
import '../datasources/attendance_remote_data_source.dart';
|
import '../datasources/attendance_remote_data_source.dart';
|
||||||
|
|
||||||
@@ -131,4 +132,19 @@ class AttendanceRepositoryImpl implements AttendanceRepository {
|
|||||||
)
|
)
|
||||||
.toList();
|
.toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<SalaryModel> calculateSalary({
|
||||||
|
required String employeeId,
|
||||||
|
required int month,
|
||||||
|
required int year,
|
||||||
|
}) async {
|
||||||
|
final dto = await remoteDataSource.calculateSalary(
|
||||||
|
employeeId: employeeId,
|
||||||
|
month: month,
|
||||||
|
year: year,
|
||||||
|
);
|
||||||
|
|
||||||
|
return SalaryModel(netAmount: dto.data?.netAmount ?? 0.0);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,6 +8,10 @@ import '../../domain/models/login_request.dart';
|
|||||||
import '../../domain/models/login_response_model.dart';
|
import '../../domain/models/login_response_model.dart';
|
||||||
import '../../domain/repositories/auth_repository.dart';
|
import '../../domain/repositories/auth_repository.dart';
|
||||||
|
|
||||||
|
import '../dto/change_password_request_dto.dart';
|
||||||
|
import '../../domain/models/change_password_request.dart';
|
||||||
|
import '../../domain/models/general_response_model.dart';
|
||||||
|
|
||||||
class AuthRepositoryImpl implements AuthRepository {
|
class AuthRepositoryImpl implements AuthRepository {
|
||||||
final AuthRemoteDataSource remoteDataSource;
|
final AuthRemoteDataSource remoteDataSource;
|
||||||
final UserLocalDataSource localDataSource;
|
final UserLocalDataSource localDataSource;
|
||||||
@@ -71,4 +75,45 @@ class AuthRepositoryImpl implements AuthRepository {
|
|||||||
return Left(ServerFailure('خطأ غير متوقع: $e'));
|
return Left(ServerFailure('خطأ غير متوقع: $e'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<Either<Failure, GeneralResponseModel>> changePassword(
|
||||||
|
ChangePasswordRequest request,
|
||||||
|
) async {
|
||||||
|
try {
|
||||||
|
final dto = ChangePasswordRequestDto(
|
||||||
|
oldPassword: request.oldPassword,
|
||||||
|
newPassword: request.newPassword,
|
||||||
|
);
|
||||||
|
|
||||||
|
final responseDto = await remoteDataSource.changePassword(dto);
|
||||||
|
|
||||||
|
// Re-cache token and employeeId if provided in response
|
||||||
|
if (responseDto.data != null) {
|
||||||
|
if (responseDto.data!.token != null) {
|
||||||
|
await localDataSource.cacheUserToken(responseDto.data!.token!);
|
||||||
|
}
|
||||||
|
if (responseDto.data!.employeeId != null) {
|
||||||
|
await localDataSource.cacheEmployeeId(responseDto.data!.employeeId!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final model = GeneralResponseModel(
|
||||||
|
statusCode: responseDto.statusCode,
|
||||||
|
isSuccess: responseDto.isSuccess,
|
||||||
|
message: responseDto.message,
|
||||||
|
data:
|
||||||
|
responseDto
|
||||||
|
.isSuccess, // Use isSuccess as data for the boolean model
|
||||||
|
);
|
||||||
|
|
||||||
|
return Right(model);
|
||||||
|
} on ServerException catch (e) {
|
||||||
|
return Left(ServerFailure(e.message));
|
||||||
|
} on NetworkException catch (e) {
|
||||||
|
return Left(NetworkFailure(e.message));
|
||||||
|
} catch (e) {
|
||||||
|
return Left(ServerFailure('خطأ غير متوقع: $e'));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
6
lib/domain/models/change_password_request.dart
Normal file
6
lib/domain/models/change_password_request.dart
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
class ChangePasswordRequest {
|
||||||
|
final String oldPassword;
|
||||||
|
final String newPassword;
|
||||||
|
|
||||||
|
ChangePasswordRequest({required this.oldPassword, required this.newPassword});
|
||||||
|
}
|
||||||
13
lib/domain/models/general_response_model.dart
Normal file
13
lib/domain/models/general_response_model.dart
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
class GeneralResponseModel {
|
||||||
|
final int statusCode;
|
||||||
|
final bool isSuccess;
|
||||||
|
final String message;
|
||||||
|
final bool? data;
|
||||||
|
|
||||||
|
GeneralResponseModel({
|
||||||
|
required this.statusCode,
|
||||||
|
required this.isSuccess,
|
||||||
|
required this.message,
|
||||||
|
this.data,
|
||||||
|
});
|
||||||
|
}
|
||||||
4
lib/domain/models/salary_model.dart
Normal file
4
lib/domain/models/salary_model.dart
Normal file
@@ -0,0 +1,4 @@
|
|||||||
|
class SalaryModel {
|
||||||
|
final double netAmount;
|
||||||
|
SalaryModel({required this.netAmount});
|
||||||
|
}
|
||||||
@@ -4,6 +4,7 @@ import '../models/attendance_response_model.dart';
|
|||||||
import '../models/attendance_model.dart';
|
import '../models/attendance_model.dart';
|
||||||
import '../models/overtime_model.dart';
|
import '../models/overtime_model.dart';
|
||||||
import '../models/extra_payment_model.dart';
|
import '../models/extra_payment_model.dart';
|
||||||
|
import '../models/salary_model.dart';
|
||||||
|
|
||||||
//in the following polymorphism is being used , a quich recap it is where th esame method but opperate in a different way
|
//in the following polymorphism is being used , a quich recap it is where th esame method but opperate in a different way
|
||||||
|
|
||||||
@@ -19,4 +20,9 @@ abstract class AttendanceRepository {
|
|||||||
Future<List<OvertimeModel>> getExtraHours({required String employeeId});
|
Future<List<OvertimeModel>> getExtraHours({required String employeeId});
|
||||||
Future<List<ExtraPaymentModel>> getRewards({required String employeeId});
|
Future<List<ExtraPaymentModel>> getRewards({required String employeeId});
|
||||||
Future<List<ExtraPaymentModel>> getPunishments({required String employeeId});
|
Future<List<ExtraPaymentModel>> getPunishments({required String employeeId});
|
||||||
|
Future<SalaryModel> calculateSalary({
|
||||||
|
required String employeeId,
|
||||||
|
required int month,
|
||||||
|
required int year,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,12 @@ import 'package:dartz/dartz.dart';
|
|||||||
import '../../core/error/failures.dart';
|
import '../../core/error/failures.dart';
|
||||||
import '../models/login_request.dart';
|
import '../models/login_request.dart';
|
||||||
import '../models/login_response_model.dart';
|
import '../models/login_response_model.dart';
|
||||||
|
import '../models/change_password_request.dart';
|
||||||
|
import '../models/general_response_model.dart';
|
||||||
|
|
||||||
abstract class AuthRepository {
|
abstract class AuthRepository {
|
||||||
Future<Either<Failure, LoginResponseModel>> login(LoginRequest request);
|
Future<Either<Failure, LoginResponseModel>> login(LoginRequest request);
|
||||||
|
Future<Either<Failure, GeneralResponseModel>> changePassword(
|
||||||
|
ChangePasswordRequest request,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
17
lib/domain/usecases/change_password_usecase.dart
Normal file
17
lib/domain/usecases/change_password_usecase.dart
Normal file
@@ -0,0 +1,17 @@
|
|||||||
|
import 'package:dartz/dartz.dart';
|
||||||
|
import '../../core/error/failures.dart';
|
||||||
|
import '../models/change_password_request.dart';
|
||||||
|
import '../models/general_response_model.dart';
|
||||||
|
import '../repositories/auth_repository.dart';
|
||||||
|
|
||||||
|
class ChangePasswordUseCase {
|
||||||
|
final AuthRepository repository;
|
||||||
|
|
||||||
|
ChangePasswordUseCase({required this.repository});
|
||||||
|
|
||||||
|
Future<Either<Failure, GeneralResponseModel>> call(
|
||||||
|
ChangePasswordRequest request,
|
||||||
|
) async {
|
||||||
|
return await repository.changePassword(request);
|
||||||
|
}
|
||||||
|
}
|
||||||
20
lib/domain/usecases/get_salary_summary_usecase.dart
Normal file
20
lib/domain/usecases/get_salary_summary_usecase.dart
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
import '../repositories/attendance_repository.dart';
|
||||||
|
import '../models/salary_model.dart';
|
||||||
|
|
||||||
|
class GetSalarySummaryUseCase {
|
||||||
|
final AttendanceRepository repository;
|
||||||
|
|
||||||
|
GetSalarySummaryUseCase(this.repository);
|
||||||
|
|
||||||
|
Future<SalaryModel> execute({
|
||||||
|
required String employeeId,
|
||||||
|
required int month,
|
||||||
|
required int year,
|
||||||
|
}) {
|
||||||
|
return repository.calculateSalary(
|
||||||
|
employeeId: employeeId,
|
||||||
|
month: month,
|
||||||
|
year: year,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,6 +3,7 @@ import '../../../domain/usecases/get_attendance_records_usecase.dart';
|
|||||||
import '../../../domain/usecases/get_extra_hours_usecase.dart';
|
import '../../../domain/usecases/get_extra_hours_usecase.dart';
|
||||||
import '../../../domain/usecases/get_rewards_usecase.dart';
|
import '../../../domain/usecases/get_rewards_usecase.dart';
|
||||||
import '../../../domain/usecases/get_punishments_usecase.dart';
|
import '../../../domain/usecases/get_punishments_usecase.dart';
|
||||||
|
import '../../../domain/usecases/get_salary_summary_usecase.dart';
|
||||||
import '../../../domain/models/finance_record.dart';
|
import '../../../domain/models/finance_record.dart';
|
||||||
import '../../../domain/models/finance_category.dart';
|
import '../../../domain/models/finance_category.dart';
|
||||||
import '../../../core/error/exceptions.dart';
|
import '../../../core/error/exceptions.dart';
|
||||||
@@ -13,8 +14,15 @@ abstract class FinanceEvent {}
|
|||||||
class LoadFinanceDataEvent extends FinanceEvent {
|
class LoadFinanceDataEvent extends FinanceEvent {
|
||||||
final String employeeId;
|
final String employeeId;
|
||||||
final FinanceCategory category;
|
final FinanceCategory category;
|
||||||
|
final int? month;
|
||||||
|
final int? year;
|
||||||
|
|
||||||
LoadFinanceDataEvent({required this.employeeId, required this.category});
|
LoadFinanceDataEvent({
|
||||||
|
required this.employeeId,
|
||||||
|
required this.category,
|
||||||
|
this.month,
|
||||||
|
this.year,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// States
|
// States
|
||||||
@@ -27,8 +35,13 @@ class FinanceLoading extends FinanceState {}
|
|||||||
class FinanceLoaded extends FinanceState {
|
class FinanceLoaded extends FinanceState {
|
||||||
final List<FinanceRecord> records;
|
final List<FinanceRecord> records;
|
||||||
final FinanceCategory category;
|
final FinanceCategory category;
|
||||||
|
final double netSalary;
|
||||||
|
|
||||||
FinanceLoaded({required this.records, required this.category});
|
FinanceLoaded({
|
||||||
|
required this.records,
|
||||||
|
required this.category,
|
||||||
|
this.netSalary = 0.0,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class FinanceError extends FinanceState {
|
class FinanceError extends FinanceState {
|
||||||
@@ -43,12 +56,14 @@ class FinanceBloc extends Bloc<FinanceEvent, FinanceState> {
|
|||||||
final GetExtraHoursUseCase getExtraHoursUseCase;
|
final GetExtraHoursUseCase getExtraHoursUseCase;
|
||||||
final GetRewardsUseCase getRewardsUseCase;
|
final GetRewardsUseCase getRewardsUseCase;
|
||||||
final GetPunishmentsUseCase getPunishmentsUseCase;
|
final GetPunishmentsUseCase getPunishmentsUseCase;
|
||||||
|
final GetSalarySummaryUseCase getSalarySummaryUseCase;
|
||||||
|
|
||||||
FinanceBloc({
|
FinanceBloc({
|
||||||
required this.getAttendanceRecordsUseCase,
|
required this.getAttendanceRecordsUseCase,
|
||||||
required this.getExtraHoursUseCase,
|
required this.getExtraHoursUseCase,
|
||||||
required this.getRewardsUseCase,
|
required this.getRewardsUseCase,
|
||||||
required this.getPunishmentsUseCase,
|
required this.getPunishmentsUseCase,
|
||||||
|
required this.getSalarySummaryUseCase,
|
||||||
}) : super(FinanceInitial()) {
|
}) : super(FinanceInitial()) {
|
||||||
on<LoadFinanceDataEvent>(_onLoadFinanceData);
|
on<LoadFinanceDataEvent>(_onLoadFinanceData);
|
||||||
}
|
}
|
||||||
@@ -64,6 +79,27 @@ class FinanceBloc extends Bloc<FinanceEvent, FinanceState> {
|
|||||||
|
|
||||||
emit(FinanceLoading());
|
emit(FinanceLoading());
|
||||||
try {
|
try {
|
||||||
|
final now = DateTime.now();
|
||||||
|
final month = event.month ?? now.month;
|
||||||
|
final year = event.year ?? now.year;
|
||||||
|
double netSalary = 0.0;
|
||||||
|
|
||||||
|
print(
|
||||||
|
'Fetching salary for Employee: ${event.employeeId}, Month: $month, Year: $year',
|
||||||
|
);
|
||||||
|
try {
|
||||||
|
final salaryModel = await getSalarySummaryUseCase.execute(
|
||||||
|
employeeId: event.employeeId,
|
||||||
|
month: month,
|
||||||
|
year: year,
|
||||||
|
);
|
||||||
|
print('Salary Model fetched: ${salaryModel.netAmount}');
|
||||||
|
netSalary = salaryModel.netAmount;
|
||||||
|
} catch (e) {
|
||||||
|
print('Error fetching salary: $e');
|
||||||
|
// We continue even if salary fails, to show the list at least
|
||||||
|
}
|
||||||
|
|
||||||
List<FinanceRecord> records;
|
List<FinanceRecord> records;
|
||||||
switch (event.category) {
|
switch (event.category) {
|
||||||
case FinanceCategory.attendance:
|
case FinanceCategory.attendance:
|
||||||
@@ -87,7 +123,13 @@ class FinanceBloc extends Bloc<FinanceEvent, FinanceState> {
|
|||||||
);
|
);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
emit(FinanceLoaded(records: records, category: event.category));
|
emit(
|
||||||
|
FinanceLoaded(
|
||||||
|
records: records,
|
||||||
|
category: event.category,
|
||||||
|
netSalary: netSalary,
|
||||||
|
),
|
||||||
|
);
|
||||||
} on ServerException catch (e) {
|
} on ServerException catch (e) {
|
||||||
emit(FinanceError(message: e.message));
|
emit(FinanceError(message: e.message));
|
||||||
} on NetworkException catch (e) {
|
} on NetworkException catch (e) {
|
||||||
|
|||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import '../../../domain/usecases/change_password_usecase.dart';
|
||||||
|
import 'change_password_event.dart';
|
||||||
|
import 'change_password_state.dart';
|
||||||
|
|
||||||
|
class ChangePasswordBloc
|
||||||
|
extends Bloc<ChangePasswordEvent, ChangePasswordState> {
|
||||||
|
final ChangePasswordUseCase changePasswordUseCase;
|
||||||
|
|
||||||
|
ChangePasswordBloc({required this.changePasswordUseCase})
|
||||||
|
: super(const ChangePasswordInitial()) {
|
||||||
|
on<ChangePasswordSubmitted>(_onChangePasswordSubmitted);
|
||||||
|
on<ChangePasswordReset>(_onChangePasswordReset);
|
||||||
|
}
|
||||||
|
|
||||||
|
Future<void> _onChangePasswordSubmitted(
|
||||||
|
ChangePasswordSubmitted event,
|
||||||
|
Emitter<ChangePasswordState> emit,
|
||||||
|
) async {
|
||||||
|
emit(const ChangePasswordLoading());
|
||||||
|
|
||||||
|
final result = await changePasswordUseCase(event.request);
|
||||||
|
|
||||||
|
result.fold((failure) => emit(ChangePasswordError(failure.message)), (
|
||||||
|
response,
|
||||||
|
) {
|
||||||
|
if (response.isSuccess) {
|
||||||
|
emit(ChangePasswordSuccess(response.message));
|
||||||
|
} else {
|
||||||
|
emit(ChangePasswordError(response.message));
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
void _onChangePasswordReset(
|
||||||
|
ChangePasswordReset event,
|
||||||
|
Emitter<ChangePasswordState> emit,
|
||||||
|
) {
|
||||||
|
emit(const ChangePasswordInitial());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,11 @@
|
|||||||
|
import '../../../domain/models/change_password_request.dart';
|
||||||
|
|
||||||
|
abstract class ChangePasswordEvent {}
|
||||||
|
|
||||||
|
class ChangePasswordSubmitted extends ChangePasswordEvent {
|
||||||
|
final ChangePasswordRequest request;
|
||||||
|
|
||||||
|
ChangePasswordSubmitted(this.request);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ChangePasswordReset extends ChangePasswordEvent {}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
abstract class ChangePasswordState {
|
||||||
|
const ChangePasswordState();
|
||||||
|
}
|
||||||
|
|
||||||
|
class ChangePasswordInitial extends ChangePasswordState {
|
||||||
|
const ChangePasswordInitial();
|
||||||
|
}
|
||||||
|
|
||||||
|
class ChangePasswordLoading extends ChangePasswordState {
|
||||||
|
const ChangePasswordLoading();
|
||||||
|
}
|
||||||
|
|
||||||
|
class ChangePasswordSuccess extends ChangePasswordState {
|
||||||
|
final String message;
|
||||||
|
|
||||||
|
const ChangePasswordSuccess(this.message);
|
||||||
|
}
|
||||||
|
|
||||||
|
class ChangePasswordError extends ChangePasswordState {
|
||||||
|
final String message;
|
||||||
|
|
||||||
|
const ChangePasswordError(this.message);
|
||||||
|
}
|
||||||
@@ -22,18 +22,22 @@ class FinanceScreen extends StatefulWidget {
|
|||||||
class _FinanceScreenState extends State<FinanceScreen> {
|
class _FinanceScreenState extends State<FinanceScreen> {
|
||||||
FinanceCategory currentCategory = FinanceCategory.attendance;
|
FinanceCategory currentCategory = FinanceCategory.attendance;
|
||||||
late ScrollController scrollController;
|
late ScrollController scrollController;
|
||||||
|
late FinanceBloc _financeBloc;
|
||||||
String? _employeeId;
|
String? _employeeId;
|
||||||
|
DateTime selectedDate = DateTime.now();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void initState() {
|
void initState() {
|
||||||
super.initState();
|
super.initState();
|
||||||
scrollController = ScrollController();
|
scrollController = ScrollController();
|
||||||
|
_financeBloc = sl<FinanceBloc>();
|
||||||
_loadInitialData();
|
_loadInitialData();
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void dispose() {
|
void dispose() {
|
||||||
scrollController.dispose();
|
scrollController.dispose();
|
||||||
|
_financeBloc.close();
|
||||||
super.dispose();
|
super.dispose();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,17 +45,19 @@ class _FinanceScreenState extends State<FinanceScreen> {
|
|||||||
_employeeId = await sl<UserLocalDataSource>().getCachedEmployeeId();
|
_employeeId = await sl<UserLocalDataSource>().getCachedEmployeeId();
|
||||||
if (mounted) {
|
if (mounted) {
|
||||||
setState(() {});
|
setState(() {});
|
||||||
|
_triggerLoad();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _triggerLoad(BuildContext context) {
|
void _triggerLoad() {
|
||||||
if (_employeeId != null && _employeeId!.isNotEmpty) {
|
if (_employeeId != null && _employeeId!.isNotEmpty) {
|
||||||
final bloc = context.read<FinanceBloc>();
|
if (_financeBloc.state is FinanceInitial) {
|
||||||
if (bloc.state is FinanceInitial) {
|
_financeBloc.add(
|
||||||
bloc.add(
|
|
||||||
LoadFinanceDataEvent(
|
LoadFinanceDataEvent(
|
||||||
employeeId: _employeeId!,
|
employeeId: _employeeId!,
|
||||||
category: currentCategory,
|
category: currentCategory,
|
||||||
|
month: selectedDate.month,
|
||||||
|
year: selectedDate.year,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -60,17 +66,12 @@ class _FinanceScreenState extends State<FinanceScreen> {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
return BlocProvider(
|
return BlocProvider.value(
|
||||||
create: (context) => sl<FinanceBloc>(),
|
value: _financeBloc,
|
||||||
child: Directionality(
|
child: Directionality(
|
||||||
textDirection: TextDirection.ltr,
|
textDirection: TextDirection.ltr,
|
||||||
child: SafeArea(
|
child: SafeArea(
|
||||||
child: Builder(
|
child: CustomScrollView(
|
||||||
builder: (context) {
|
|
||||||
// Trigger initial load when bloc is ready
|
|
||||||
_triggerLoad(context);
|
|
||||||
|
|
||||||
return CustomScrollView(
|
|
||||||
controller: scrollController,
|
controller: scrollController,
|
||||||
physics: const BouncingScrollPhysics(),
|
physics: const BouncingScrollPhysics(),
|
||||||
slivers: [
|
slivers: [
|
||||||
@@ -105,26 +106,58 @@ class _FinanceScreenState extends State<FinanceScreen> {
|
|||||||
|
|
||||||
/// SUMMARY CARD
|
/// SUMMARY CARD
|
||||||
SliverToBoxAdapter(
|
SliverToBoxAdapter(
|
||||||
child: FinanceSummaryCard(
|
child: BlocBuilder<FinanceBloc, FinanceState>(
|
||||||
totalAmount: "333,000",
|
buildWhen: (previous, current) => current is FinanceLoaded,
|
||||||
|
builder: (context, state) {
|
||||||
|
String amount = "0";
|
||||||
|
if (state is FinanceLoaded) {
|
||||||
|
amount = state.netSalary.toStringAsFixed(0);
|
||||||
|
amount = amount.replaceAllMapped(
|
||||||
|
RegExp(r'(\d{1,3})(?=(\d{3})+(?!\d))'),
|
||||||
|
(Match m) => '${m[1]},',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return FinanceSummaryCard(
|
||||||
|
totalAmount: amount,
|
||||||
currentCategory: currentCategory,
|
currentCategory: currentCategory,
|
||||||
onCalendarTap:
|
onCalendarTap: () async {
|
||||||
() => showDatePicker(
|
final date = await showDatePicker(
|
||||||
context: context,
|
context: context,
|
||||||
initialDate: DateTime.now(),
|
initialDate: selectedDate,
|
||||||
firstDate: DateTime(2020),
|
firstDate: DateTime(2020),
|
||||||
lastDate: DateTime(2030),
|
lastDate: DateTime(2030),
|
||||||
),
|
);
|
||||||
onCategoryChanged: (category) {
|
if (date != null && mounted) {
|
||||||
if (category != null) {
|
setState(() => selectedDate = date);
|
||||||
setState(() => currentCategory = category);
|
if (_employeeId != null && _employeeId!.isNotEmpty) {
|
||||||
context.read<FinanceBloc>().add(
|
_financeBloc.add(
|
||||||
LoadFinanceDataEvent(
|
LoadFinanceDataEvent(
|
||||||
employeeId: _employeeId ?? '',
|
employeeId: _employeeId!,
|
||||||
category: currentCategory,
|
category: currentCategory,
|
||||||
|
month: selectedDate.month,
|
||||||
|
year: selectedDate.year,
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onCategoryChanged: (category) {
|
||||||
|
if (category != null) {
|
||||||
|
setState(() => currentCategory = category);
|
||||||
|
if (_employeeId != null && _employeeId!.isNotEmpty) {
|
||||||
|
_financeBloc.add(
|
||||||
|
LoadFinanceDataEvent(
|
||||||
|
employeeId: _employeeId!,
|
||||||
|
category: currentCategory,
|
||||||
|
month: selectedDate.month,
|
||||||
|
year: selectedDate.year,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -153,10 +186,7 @@ class _FinanceScreenState extends State<FinanceScreen> {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
return SliverList(
|
return SliverList(
|
||||||
delegate: SliverChildBuilderDelegate((
|
delegate: SliverChildBuilderDelegate((context, index) {
|
||||||
context,
|
|
||||||
index,
|
|
||||||
) {
|
|
||||||
return WorkDayCard(record: state.records[index]);
|
return WorkDayCard(record: state.records[index]);
|
||||||
}, childCount: state.records.length),
|
}, childCount: state.records.length),
|
||||||
);
|
);
|
||||||
@@ -180,8 +210,6 @@ class _FinanceScreenState extends State<FinanceScreen> {
|
|||||||
|
|
||||||
const SliverToBoxAdapter(child: SizedBox(height: 120)),
|
const SliverToBoxAdapter(child: SizedBox(height: 120)),
|
||||||
],
|
],
|
||||||
);
|
|
||||||
},
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -13,11 +13,11 @@ import '../../core/services/request_service.dart';
|
|||||||
import '../../core/di/injection_container.dart';
|
import '../../core/di/injection_container.dart';
|
||||||
import '../../domain/usecases/get_vacations_usecase.dart';
|
import '../../domain/usecases/get_vacations_usecase.dart';
|
||||||
import '../../domain/usecases/get_advances_usecase.dart';
|
import '../../domain/usecases/get_advances_usecase.dart';
|
||||||
import '../../domain/models/vacations_list_response_model.dart';
|
// import '../../domain/models/vacations_list_response_model.dart';
|
||||||
import '../../domain/models/vacation_response_model.dart';
|
import '../../domain/models/vacation_response_model.dart';
|
||||||
import '../../domain/models/advances_list_response_model.dart';
|
// import '../../domain/models/advances_list_response_model.dart';
|
||||||
import '../../domain/models/advance_request_model.dart';
|
import '../../domain/models/advance_request_model.dart';
|
||||||
import '../../core/error/failures.dart';
|
// import '../../core/error/failures.dart';
|
||||||
|
|
||||||
class HolidayScreen extends StatefulWidget {
|
class HolidayScreen extends StatefulWidget {
|
||||||
final void Function(bool isScrollingDown)? onScrollEvent;
|
final void Function(bool isScrollingDown)? onScrollEvent;
|
||||||
@@ -118,7 +118,8 @@ class _HolidayScreenState extends State<HolidayScreen> {
|
|||||||
(response) {
|
(response) {
|
||||||
if (mounted && response.data != null) {
|
if (mounted && response.data != null) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_leaveRequests = response.data!.items
|
_leaveRequests =
|
||||||
|
response.data!.items
|
||||||
.map((vacation) => _convertVacationToLeaveRequest(vacation))
|
.map((vacation) => _convertVacationToLeaveRequest(vacation))
|
||||||
.toList();
|
.toList();
|
||||||
_isLoadingVacations = false;
|
_isLoadingVacations = false;
|
||||||
@@ -141,7 +142,8 @@ class _HolidayScreenState extends State<HolidayScreen> {
|
|||||||
String leaveTypeName = _getArabicVacationTypeName(vacation.type);
|
String leaveTypeName = _getArabicVacationTypeName(vacation.type);
|
||||||
|
|
||||||
// Check if it's timed leave (same day but different times)
|
// Check if it's timed leave (same day but different times)
|
||||||
bool isTimedLeave = vacation.startDate.year == vacation.endDate.year &&
|
bool isTimedLeave =
|
||||||
|
vacation.startDate.year == vacation.endDate.year &&
|
||||||
vacation.startDate.month == vacation.endDate.month &&
|
vacation.startDate.month == vacation.endDate.month &&
|
||||||
vacation.startDate.day == vacation.endDate.day &&
|
vacation.startDate.day == vacation.endDate.day &&
|
||||||
vacation.startDate.hour != vacation.endDate.hour;
|
vacation.startDate.hour != vacation.endDate.hour;
|
||||||
@@ -200,7 +202,8 @@ class _HolidayScreenState extends State<HolidayScreen> {
|
|||||||
(response) {
|
(response) {
|
||||||
if (mounted && response.data != null) {
|
if (mounted && response.data != null) {
|
||||||
setState(() {
|
setState(() {
|
||||||
_advanceRequests = response.data!.items
|
_advanceRequests =
|
||||||
|
response.data!.items
|
||||||
.map((advance) => _convertAdvanceToAdvanceRequest(advance))
|
.map((advance) => _convertAdvanceToAdvanceRequest(advance))
|
||||||
.toList();
|
.toList();
|
||||||
_isLoadingAdvances = false;
|
_isLoadingAdvances = false;
|
||||||
@@ -417,9 +420,7 @@ class _HolidayScreenState extends State<HolidayScreen> {
|
|||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(40.0),
|
padding: const EdgeInsets.all(40.0),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: CircularProgressIndicator(
|
child: CircularProgressIndicator(color: Color(0xFF8EFDC2)),
|
||||||
color: Color(0xFF8EFDC2),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@@ -458,9 +459,7 @@ class _HolidayScreenState extends State<HolidayScreen> {
|
|||||||
child: Padding(
|
child: Padding(
|
||||||
padding: const EdgeInsets.all(40.0),
|
padding: const EdgeInsets.all(40.0),
|
||||||
child: Center(
|
child: Center(
|
||||||
child: CircularProgressIndicator(
|
child: CircularProgressIndicator(color: Color(0xFF8EFDC2)),
|
||||||
color: Color(0xFF8EFDC2),
|
|
||||||
),
|
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -1,4 +1,10 @@
|
|||||||
import 'package:flutter/material.dart';
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_bloc/flutter_bloc.dart';
|
||||||
|
import '../../core/di/injection_container.dart';
|
||||||
|
import '../../domain/models/change_password_request.dart';
|
||||||
|
import '../blocs/change_password/change_password_bloc.dart';
|
||||||
|
import '../blocs/change_password/change_password_event.dart';
|
||||||
|
import '../blocs/change_password/change_password_state.dart';
|
||||||
import 'onboarding_button.dart';
|
import 'onboarding_button.dart';
|
||||||
|
|
||||||
class ChangePasswordModal extends StatefulWidget {
|
class ChangePasswordModal extends StatefulWidget {
|
||||||
@@ -10,7 +16,17 @@ class ChangePasswordModal extends StatefulWidget {
|
|||||||
|
|
||||||
class _ChangePasswordModalState extends State<ChangePasswordModal> {
|
class _ChangePasswordModalState extends State<ChangePasswordModal> {
|
||||||
bool _oldObscure = true;
|
bool _oldObscure = true;
|
||||||
// bool _newObscure = true;
|
bool _newObscure = true;
|
||||||
|
|
||||||
|
final TextEditingController _oldPasswordController = TextEditingController();
|
||||||
|
final TextEditingController _newPasswordController = TextEditingController();
|
||||||
|
|
||||||
|
@override
|
||||||
|
void dispose() {
|
||||||
|
_oldPasswordController.dispose();
|
||||||
|
_newPasswordController.dispose();
|
||||||
|
super.dispose();
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Widget build(BuildContext context) {
|
Widget build(BuildContext context) {
|
||||||
@@ -73,10 +89,11 @@ class _ChangePasswordModalState extends State<ChangePasswordModal> {
|
|||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
|
|
||||||
_buildField(
|
_buildField(
|
||||||
|
controller: _oldPasswordController,
|
||||||
hint: "أدخل كلمة المرور",
|
hint: "أدخل كلمة المرور",
|
||||||
obscure: _oldObscure,
|
obscure: _oldObscure,
|
||||||
fontSize: fieldFontSize,
|
fontSize: fieldFontSize,
|
||||||
hasEye: true, // ✅ eye here
|
hasEye: true,
|
||||||
onEyeTap:
|
onEyeTap:
|
||||||
() => setState(() => _oldObscure = !_oldObscure),
|
() => setState(() => _oldObscure = !_oldObscure),
|
||||||
),
|
),
|
||||||
@@ -98,21 +115,75 @@ class _ChangePasswordModalState extends State<ChangePasswordModal> {
|
|||||||
const SizedBox(height: 8),
|
const SizedBox(height: 8),
|
||||||
|
|
||||||
_buildField(
|
_buildField(
|
||||||
|
controller: _newPasswordController,
|
||||||
hint: "كلمة المرور",
|
hint: "كلمة المرور",
|
||||||
obscure: false,
|
obscure: _newObscure,
|
||||||
fontSize: fieldFontSize,
|
fontSize: fieldFontSize,
|
||||||
hasEye: false,
|
hasEye: true,
|
||||||
onEyeTap: () {}, // unused
|
onEyeTap:
|
||||||
|
() => setState(() => _newObscure = !_newObscure),
|
||||||
),
|
),
|
||||||
|
|
||||||
SizedBox(height: buttonSpacing),
|
SizedBox(height: buttonSpacing),
|
||||||
|
|
||||||
Center(
|
BlocProvider(
|
||||||
|
create: (context) => sl<ChangePasswordBloc>(),
|
||||||
|
child: BlocConsumer<
|
||||||
|
ChangePasswordBloc,
|
||||||
|
ChangePasswordState
|
||||||
|
>(
|
||||||
|
listener: (context, state) {
|
||||||
|
if (state is ChangePasswordSuccess) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(state.message),
|
||||||
|
backgroundColor: Colors.green,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
Navigator.pop(context);
|
||||||
|
} else if (state is ChangePasswordError) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
SnackBar(
|
||||||
|
content: Text(state.message),
|
||||||
|
backgroundColor: Colors.red,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
builder: (context, state) {
|
||||||
|
if (state is ChangePasswordLoading) {
|
||||||
|
return const Center(
|
||||||
|
child: CircularProgressIndicator(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Center(
|
||||||
child: OnboardingButton(
|
child: OnboardingButton(
|
||||||
text: "حفظ التغيير",
|
text: "حفظ التغيير",
|
||||||
backgroundColor: const Color(0xEE23574A),
|
backgroundColor: const Color(0xEE23574A),
|
||||||
onPressed: () {
|
onPressed: () {
|
||||||
Navigator.pop(context);
|
if (_oldPasswordController.text.isEmpty ||
|
||||||
|
_newPasswordController.text.isEmpty) {
|
||||||
|
ScaffoldMessenger.of(context).showSnackBar(
|
||||||
|
const SnackBar(
|
||||||
|
content: Text("يرجى ملء جميع الحقول"),
|
||||||
|
backgroundColor: Colors.orange,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
context.read<ChangePasswordBloc>().add(
|
||||||
|
ChangePasswordSubmitted(
|
||||||
|
ChangePasswordRequest(
|
||||||
|
oldPassword: _oldPasswordController.text,
|
||||||
|
newPassword: _newPasswordController.text,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
},
|
||||||
|
),
|
||||||
|
);
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@@ -127,6 +198,7 @@ class _ChangePasswordModalState extends State<ChangePasswordModal> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Widget _buildField({
|
Widget _buildField({
|
||||||
|
required TextEditingController controller,
|
||||||
required String hint,
|
required String hint,
|
||||||
required bool obscure,
|
required bool obscure,
|
||||||
required double fontSize,
|
required double fontSize,
|
||||||
@@ -142,6 +214,7 @@ class _ChangePasswordModalState extends State<ChangePasswordModal> {
|
|||||||
],
|
],
|
||||||
),
|
),
|
||||||
child: TextField(
|
child: TextField(
|
||||||
|
controller: controller,
|
||||||
obscureText: obscure,
|
obscureText: obscure,
|
||||||
textAlign: TextAlign.right,
|
textAlign: TextAlign.right,
|
||||||
style: TextStyle(fontSize: fontSize),
|
style: TextStyle(fontSize: fontSize),
|
||||||
|
|||||||
Reference in New Issue
Block a user