From cd48d83c076a46c50bd5defef4a15932c53f809c Mon Sep 17 00:00:00 2001 From: aamir-csol Date: Wed, 10 Sep 2025 09:19:51 +0300 Subject: [PATCH 01/20] signup --- lib/core/app_state.dart | 3 +- lib/core/utils/request_utils.dart | 55 +++++++++++++++++++ .../authentication_view_model.dart | 24 ++++---- .../registration_payload_model.dart | 22 ++++++++ .../authentication/register_step2.dart | 9 ++- 5 files changed, 93 insertions(+), 20 deletions(-) diff --git a/lib/core/app_state.dart b/lib/core/app_state.dart index 4408844..05fe59a 100644 --- a/lib/core/app_state.dart +++ b/lib/core/app_state.dart @@ -37,7 +37,7 @@ class AppState { if (isFamily) { _authenticatedChildUser = authenticatedUser; } else { - setIsAuthenticated = true; + setIsAuthenticated = true; _authenticatedRootUser = authenticatedUser; } } @@ -106,7 +106,6 @@ class AppState { _nHICUserData = value; } - RegistrationDataModelPayload? _userRegistrationPayload; RegistrationDataModelPayload get getUserRegistrationPayload => _userRegistrationPayload!; diff --git a/lib/core/utils/request_utils.dart b/lib/core/utils/request_utils.dart index 1045d9c..2a26e10 100644 --- a/lib/core/utils/request_utils.dart +++ b/lib/core/utils/request_utils.dart @@ -2,6 +2,9 @@ import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/features/authentication/models/request_models/registration_payload_model.dart'; import 'package:hmg_patient_app_new/features/authentication/models/request_models/send_activation_request_model.dart'; import 'package:hmg_patient_app_new/features/common/models/commong_authanticated_req_model.dart'; @@ -169,4 +172,56 @@ class RequestUtils { request.tokenId = null; return request; } + + static dynamic getUserSignupCompletionRequest() { + AppState appState = getIt.get(); + + bool isDubai = appState.getUserRegistrationPayload.patientOutSa == 1 ? true : false; + List names = appState.getUserRegistrationPayload.fullName != null ? appState.getUserRegistrationPayload.fullName!.split(" ") : []; + + var dob = appState.getUserRegistrationPayload.dob; + + return { + "Patientobject": { + "TempValue": true, + "PatientIdentificationType": + (isDubai ? appState.getNHICUserData.idNumber!.substring(0, 1) : appState.getUserRegistrationPayload.patientIdentificationId?.toString().substring(0, 1)) == "1" ? 1 : 2, + "PatientIdentificationNo": isDubai ? appState.getNHICUserData.idNumber! : appState.getUserRegistrationPayload.patientIdentificationId, + "MobileNumber": appState.getUserRegistrationPayload.patientMobileNumber ?? 0, + "PatientOutSA": (appState.getUserRegistrationPayload.zipCode == CountryEnum.saudiArabia.countryCode || appState.getUserRegistrationPayload.zipCode == '+966') ? 0 : 1, + "FirstNameN": isDubai ? "..." : appState.getNHICUserData.firstNameAr, + "FirstName": isDubai ? (names.isNotEmpty ? names[0] : "...") : appState.getNHICUserData.firstNameEn, + "MiddleNameN": isDubai ? "..." : appState.getNHICUserData.secondNameAr, + "MiddleName": isDubai ? "..." : appState.getNHICUserData.secondNameEn, + "LastNameN": isDubai ? "..." : appState.getNHICUserData.lastNameAr, + "LastName": isDubai ? (names.length > 1 ? names[1] : "...") : appState.getNHICUserData.lastNameEn, + "StrDateofBirth": dob, + "DateofBirth": DateUtil.convertISODateToJsonDate((dob ?? "").replaceAll('/', '-')), + "Gender": isDubai ? (appState.getUserRegistrationPayload.gender == GenderTypeEnum.male ? 1 : 2) : (appState.getNHICUserData.gender == 'M' ? 1 : 2), + "NationalityID": isDubai ? "UAE" : appState.getUserRegistrationPayload.nationalityCode, + "eHealthIDField": isDubai ? null : appState.getNHICUserData.healthId, + "DateofBirthN": DateTime.now(), + "EmailAddress": appState.getUserRegistrationPayload.emailAddress, + "SourceType": (appState.getUserRegistrationPayload.zipCode == CountryEnum.saudiArabia.countryCode || appState.getUserRegistrationPayload.zipCode == '+966') ? 1 : 2, + "PreferredLanguage": appState.getLanguageCode() == "ar" ? (isDubai ? "1" : 1) : (isDubai ? "2" : 2), + "Marital": isDubai + ? (appState.getUserRegistrationPayload.maritalStatus == MaritalStatusTypeEnum.single + ? '0' + : appState.getUserRegistrationPayload.maritalStatus == MaritalStatusTypeEnum.married + ? '1' + : '2') + : (appState.getNHICUserData.maritalStatusCode == 'U' + ? '0' + : appState.getNHICUserData.maritalStatusCode == 'M' + ? '1' + : '2'), + }, + "PatientIdentificationID": isDubai ? appState.getUserRegistrationPayload.patientIdentificationId : appState.getNHICUserData.idNumber, + "PatientMobileNumber": appState.getUserRegistrationPayload.patientMobileNumber.toString()[0] == '0' + ? appState.getUserRegistrationPayload.patientMobileNumber + : '0${appState.getUserRegistrationPayload.patientMobileNumber}', + if (isDubai) "DOB": dob, + if (isDubai) "IsHijri": appState.getUserRegistrationPayload.isHijri, + }; + } } diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index fbfa2b1..e1a4d32 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -50,7 +50,11 @@ class AuthenticationViewModel extends ChangeNotifier { _authenticationRepo = authenticationRepo, _localAuthService = localAuthService; - final TextEditingController nationalIdController = TextEditingController(), phoneNumberController = TextEditingController(), dobController = TextEditingController(), nameController = TextEditingController(), emailController = TextEditingController(); + final TextEditingController nationalIdController = TextEditingController(), + phoneNumberController = TextEditingController(), + dobController = TextEditingController(), + nameController = TextEditingController(), + emailController = TextEditingController(); CountryEnum selectedCountrySignup = CountryEnum.saudiArabia; MaritalStatusTypeEnum? maritalStatus; GenderTypeEnum? genderType; @@ -558,20 +562,14 @@ class AuthenticationViewModel extends ChangeNotifier { }); } - - Future onRegistrationComplete() async{ - - // if (emailAddress.text.isEmpty) { - // Utils.showErrorToast(TranslationBase.of(context).enterEmailAddress); - // return; - // } else { - // Navigator.of(context).pop(); - // registerNow(); - // } - //authVM!.clearDefaultInputValues(); + Future onRegistrationComplete() async { + var request = RequestUtils.getUserSignupCompletionRequest(); + clearDefaultInputValues(); + print("============= Final Payload ==============="); + print(request); + print("==========================================="); } - Future checkUserStatusForRegistration({required dynamic response, required dynamic request}) async { if (response is Map) { _appState.setAppAuthToken = response["LogInTokenID"]; diff --git a/lib/features/authentication/models/request_models/registration_payload_model.dart b/lib/features/authentication/models/request_models/registration_payload_model.dart index fa4f612..93c409b 100644 --- a/lib/features/authentication/models/request_models/registration_payload_model.dart +++ b/lib/features/authentication/models/request_models/registration_payload_model.dart @@ -1,5 +1,7 @@ import 'dart:convert'; +import 'package:hmg_patient_app_new/core/enums.dart'; + class RegistrationDataModelPayload { int? patientMobileNumber; String? zipCode; @@ -14,6 +16,11 @@ class RegistrationDataModelPayload { bool? forRegister; bool? isRegister; String? healthId; + String? emailAddress; + String? nationalityCode; + GenderTypeEnum? gender; + MaritalStatusTypeEnum? maritalStatus; + String? fullName; RegistrationDataModelPayload({ this.patientMobileNumber, @@ -29,6 +36,11 @@ class RegistrationDataModelPayload { this.forRegister, this.isRegister, this.healthId, + this.emailAddress, + this.nationalityCode, + this.gender, + this.maritalStatus, + this.fullName, }); factory RegistrationDataModelPayload.fromRawJson(String str) => RegistrationDataModelPayload.fromJson(json.decode(str)); @@ -49,6 +61,11 @@ class RegistrationDataModelPayload { forRegister: json["forRegister"], isRegister: json["isRegister"], healthId: json["healthId"], + emailAddress: json["emailAddress"], + nationalityCode: json["nationalityCode"], + gender: json["gender"], + maritalStatus: json["maritalStatus"], + fullName: json["fullName"], ); Map toJson() => { @@ -65,5 +82,10 @@ class RegistrationDataModelPayload { "forRegister": forRegister, "isRegister": isRegister, "healthId": healthId, + "emailAddress": emailAddress, + "nationalityCode": nationalityCode, + "gender": gender, + "maritalStatus": maritalStatus, + "fullName": fullName, }; } diff --git a/lib/presentation/authentication/register_step2.dart b/lib/presentation/authentication/register_step2.dart index bd23be1..da46888 100644 --- a/lib/presentation/authentication/register_step2.dart +++ b/lib/presentation/authentication/register_step2.dart @@ -46,7 +46,7 @@ class _RegisterNew extends State { appBar: CustomAppBar( onBackPressed: () { Navigator.of(context).pop(); - authVM!.clearDefaultInputValues(); + // authVM!.clearDefaultInputValues(); }, onLanguageChanged: (lang) {}, hideLogoAndLang: true, @@ -224,8 +224,7 @@ class _RegisterNew extends State { isBorderAllowed: false, isAllowLeadingIcon: true, isReadOnly: true, - leadingIcon: AppAssets.call, - onChange: (value) {}) + leadingIcon: AppAssets.call) .paddingSymmetrical(0.h, 16.h), Divider( height: 1, @@ -233,8 +232,8 @@ class _RegisterNew extends State { ), TextInputWidget( labelText: LocaleKeys.dob.tr(), - hintText: authVM!.isUserFromUAE() ? appState.getUserRegistrationPayload.dob! : (appState.getNHICUserData.dateOfBirth ?? ""), - controller: authVM!.dobController, + hintText: authVM!.isUserFromUAE() ? appState.getUserRegistrationPayload.dob! : appState.getNHICUserData.dateOfBirth ?? "", + controller: authVM!.isUserFromUAE() ? authVM!.dobController : null, isEnable: authVM!.isUserFromUAE() ? true : false, prefix: null, isBorderAllowed: false, From ccd29f0cf9311819a938394c462be38afa3ce7e0 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Wed, 10 Sep 2025 13:11:26 +0300 Subject: [PATCH 02/20] updates --- lib/core/api/api_client.dart | 9 +- lib/core/dependencies.dart | 21 +-- lib/core/utils/utils.dart | 5 +- lib/extensions/widget_extensions.dart | 2 + .../authentication_view_model.dart | 2 +- .../models/habib_wallet_repo.dart | 58 +++++++ .../models/habib_wallet_view_model.dart | 39 +++++ lib/features/lab/lab_repo.dart | 4 +- lib/features/lab/lab_view_model.dart | 2 +- lib/features/payfort/payfort_repo.dart | 8 +- lib/main.dart | 7 + .../appointment_payment_page.dart | 142 ++++++++++-------- .../habib_wallet/habib_wallet_page.dart | 111 ++++++++++++++ .../habib_wallet/recharge_wallet_page.dart | 105 +++++++++++++ lib/presentation/home/landing_page.dart | 15 +- .../home/widgets/habib_wallet_card.dart | 42 ++++-- lib/widgets/input_widget.dart | 103 +++++++++---- pubspec.yaml | 1 + 18 files changed, 548 insertions(+), 128 deletions(-) create mode 100644 lib/features/habib_wallet/models/habib_wallet_repo.dart create mode 100644 lib/features/habib_wallet/models/habib_wallet_view_model.dart create mode 100644 lib/presentation/habib_wallet/habib_wallet_page.dart create mode 100644 lib/presentation/habib_wallet/recharge_wallet_page.dart diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index 2fd8082..5e9dbb7 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -26,6 +26,7 @@ abstract class ApiClient { bool isAllowAny, bool isExternal, bool isRCService, + bool isPaymentServices, bool bypassConnectionCheck, }); @@ -97,6 +98,7 @@ class ApiClientImp implements ApiClient { bool isAllowAny = false, bool isExternal = false, bool isRCService = false, + bool isPaymentServices = false, bool bypassConnectionCheck = false, }) async { String url; @@ -204,7 +206,12 @@ class ApiClientImp implements ApiClient { } else { var parsed = json.decode(utf8.decode(response.bodyBytes)); if (isAllowAny) { - onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage']); + if (isPaymentServices) { + onSuccess(parsed, statusCode, messageStatus: 1, errorMessage: ""); + } else { + onSuccess(parsed, statusCode, + messageStatus: parsed.contains('MessageStatus') ? parsed['MessageStatus'] : 1, errorMessage: parsed.contains('ErrorEndUserMessage') ? parsed['ErrorEndUserMessage'] : ""); + } } else { if (parsed['Response_Message'] != null) { onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index 0afa53c..0edec4e 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -7,6 +7,8 @@ import 'package:hmg_patient_app_new/features/authentication/authentication_repo. import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_repo.dart'; import 'package:hmg_patient_app_new/features/common/common_repo.dart'; +import 'package:hmg_patient_app_new/features/habib_wallet/models/habib_wallet_repo.dart'; +import 'package:hmg_patient_app_new/features/habib_wallet/models/habib_wallet_view_model.dart'; import 'package:hmg_patient_app_new/features/insurance/insurance_repo.dart'; import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/lab_repo.dart'; @@ -85,6 +87,7 @@ class AppDependencies { getIt.registerLazySingleton(() => InsuranceRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => PayfortRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => LocalAuthService(loggerService: getIt(), localAuth: getIt())); + getIt.registerLazySingleton(() => HabibWalletRepoImp(loggerService: getIt(), apiClient: getIt())); // ViewModels // Global/shared VMs → LazySingleton @@ -125,24 +128,24 @@ class AppDependencies { ); getIt.registerLazySingleton( - () => PayfortViewModel( + () => PayfortViewModel( payfortRepo: getIt(), errorHandlerService: getIt(), ), ); - getIt.registerLazySingleton( - () => AuthenticationViewModel( - authenticationRepo: getIt(), - cacheService: getIt(), - navigationService: getIt(), - dialogService: getIt(), - appState: getIt(), + getIt.registerLazySingleton( + () => HabibWalletViewModel( + habibWalletRepo: getIt(), errorHandlerService: getIt(), - localAuthService: getIt() ), ); + getIt.registerLazySingleton( + () => AuthenticationViewModel( + authenticationRepo: getIt(), cacheService: getIt(), navigationService: getIt(), dialogService: getIt(), appState: getIt(), errorHandlerService: getIt(), localAuthService: getIt()), + ); + // Screen-specific VMs → Factory // getIt.registerFactory( // () => BookAppointmentsViewModel( diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart index ebb7037..a330879 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -568,9 +568,10 @@ class Utils { ); } - static Widget getPaymentAmountWithSymbol(Widget paymentAmountWidget, Color iconColor, double iconSize, {bool isSaudiCurrency = true}) { + static Widget getPaymentAmountWithSymbol(Widget paymentAmountWidget, Color iconColor, double iconSize, {bool isSaudiCurrency = true, bool isExpanded = true}) { return Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + mainAxisAlignment: isExpanded ? MainAxisAlignment.spaceBetween : MainAxisAlignment.start, + crossAxisAlignment: CrossAxisAlignment.end, children: [ appState.isArabic() ? Container() diff --git a/lib/extensions/widget_extensions.dart b/lib/extensions/widget_extensions.dart index f576d0c..5724b91 100644 --- a/lib/extensions/widget_extensions.dart +++ b/lib/extensions/widget_extensions.dart @@ -39,6 +39,8 @@ extension WidgetExtensions on Widget { child: this, ); + + //TODO: @Sikander to make shimmer widget width dynamic Widget toShimmer2({bool isShow = true, double radius = 20}) => isShow ? Shimmer.fromColors( baseColor: const Color(0xffe8eff0), diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index 0add5a0..e2ed026 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -525,7 +525,7 @@ class AuthenticationViewModel extends ChangeNotifier { if (!_appState.isAuthenticated) { loginTypeEnum = (_appState.deviceTypeID == 1 ? LoginTypeEnum.face : LoginTypeEnum.fingerprint); print(loginTypeEnum); - checkActivationCode(otpTypeEnum: OTPTypeEnum.faceIDFingerprint, activationCode: null, onWrongActivationCode: (String? message) {}); + await checkActivationCode(otpTypeEnum: OTPTypeEnum.faceIDFingerprint, activationCode: null, onWrongActivationCode: (String? message) {}); insertPatientIMEIData((_appState.deviceTypeID == 1 ? LoginTypeEnum.face.toInt : LoginTypeEnum.fingerprint.toInt)); } else { // authenticated = true; diff --git a/lib/features/habib_wallet/models/habib_wallet_repo.dart b/lib/features/habib_wallet/models/habib_wallet_repo.dart new file mode 100644 index 0000000..5f47f6c --- /dev/null +++ b/lib/features/habib_wallet/models/habib_wallet_repo.dart @@ -0,0 +1,58 @@ +import 'package:dartz/dartz.dart'; +import 'package:hmg_patient_app_new/core/api/api_client.dart'; +import 'package:hmg_patient_app_new/core/api_consts.dart'; +import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; +import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; +import 'package:hmg_patient_app_new/services/logger_service.dart'; + +abstract class HabibWalletRepo { + Future>> getPatientBalanceAmount(); +} + +class HabibWalletRepoImp implements HabibWalletRepo { + final ApiClient apiClient; + final LoggerService loggerService; + + HabibWalletRepoImp({required this.loggerService, required this.apiClient}); + + @override + Future>> getPatientBalanceAmount() async { + Map mapDevice = {}; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + GET_PATIENT_AdVANCE_BALANCE_AMOUNT, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + // final list = response['ListPLO']; + // if (list == null || list.isEmpty) { + // throw Exception("lab list is empty"); + // } + + // final labOrders = list.map((item) => PatientLabOrdersResponseModel.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: response["TotalAdvanceBalanceAmount"], + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } +} diff --git a/lib/features/habib_wallet/models/habib_wallet_view_model.dart b/lib/features/habib_wallet/models/habib_wallet_view_model.dart new file mode 100644 index 0000000..64c6ddf --- /dev/null +++ b/lib/features/habib_wallet/models/habib_wallet_view_model.dart @@ -0,0 +1,39 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/features/habib_wallet/models/habib_wallet_repo.dart'; +import 'package:hmg_patient_app_new/services/error_handler_service.dart'; + +class HabibWalletViewModel extends ChangeNotifier { + bool isWalletAmountLoading = false; + num habibWalletAmount = 0; + + HabibWalletRepo habibWalletRepo; + ErrorHandlerService errorHandlerService; + + HabibWalletViewModel({required this.habibWalletRepo, required this.errorHandlerService}); + + initHabibWalletProvider() { + isWalletAmountLoading = true; + habibWalletAmount = 0; + notifyListeners(); + } + + Future getPatientBalanceAmount({Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await habibWalletRepo.getPatientBalanceAmount(); + + result.fold( + (failure) async => await errorHandlerService.handleError(failure: failure), + (apiResponse) { + if (apiResponse.messageStatus == 2) { + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + habibWalletAmount = apiResponse.data!; + isWalletAmountLoading = false; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } +} diff --git a/lib/features/lab/lab_repo.dart b/lib/features/lab/lab_repo.dart index ede2ec8..f205494 100644 --- a/lib/features/lab/lab_repo.dart +++ b/lib/features/lab/lab_repo.dart @@ -7,7 +7,7 @@ import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_ import 'package:hmg_patient_app_new/services/logger_service.dart'; abstract class LabRepo { - Future>>> getPatientLabOrders({required String patientId}); + Future>>> getPatientLabOrders(); } class LabRepoImp implements LabRepo { @@ -17,7 +17,7 @@ class LabRepoImp implements LabRepo { LabRepoImp({required this.loggerService, required this.apiClient}); @override - Future>>> getPatientLabOrders({required String patientId}) async { + Future>>> getPatientLabOrders() async { Map mapDevice = {}; try { diff --git a/lib/features/lab/lab_view_model.dart b/lib/features/lab/lab_view_model.dart index 52f470d..2e481f4 100644 --- a/lib/features/lab/lab_view_model.dart +++ b/lib/features/lab/lab_view_model.dart @@ -29,7 +29,7 @@ class LabViewModel extends ChangeNotifier { } Future getPatientLabOrders({Function(dynamic)? onSuccess, Function(String)? onError}) async { - final result = await labRepo.getPatientLabOrders(patientId: "1231755"); + final result = await labRepo.getPatientLabOrders(); result.fold( (failure) async => await errorHandlerService.handleError(failure: failure), diff --git a/lib/features/payfort/payfort_repo.dart b/lib/features/payfort/payfort_repo.dart index 565422b..9f836d1 100644 --- a/lib/features/payfort/payfort_repo.dart +++ b/lib/features/payfort/payfort_repo.dart @@ -53,7 +53,7 @@ class PayfortRepoImp implements PayfortRepo { } catch (e) { failure = DataParsingFailure(e.toString()); } - }, isAllowAny: true); + }, isAllowAny: true, isPaymentServices: true); if (failure != null) return Left(failure!); if (apiResponse == null) return Left(ServerFailure("Unknown error")); return Right(apiResponse!); @@ -80,7 +80,7 @@ class PayfortRepoImp implements PayfortRepo { } catch (e) { failure = DataParsingFailure(e.toString()); } - }, isAllowAny: true); + }, isAllowAny: true, isPaymentServices: true); if (failure != null) return Left(failure!); if (apiResponse == null) return Left(ServerFailure("Unknown error")); return Right(apiResponse!); @@ -109,7 +109,7 @@ class PayfortRepoImp implements PayfortRepo { } catch (e) { failure = DataParsingFailure(e.toString()); } - }, isAllowAny: true, isExternal: true); + }, isAllowAny: true, isExternal: true, isPaymentServices: true); if (failure != null) return Left(failure!); if (apiResponse == null) return Left(ServerFailure("Unknown error")); return Right(apiResponse!); @@ -139,7 +139,7 @@ class PayfortRepoImp implements PayfortRepo { } catch (e) { failure = DataParsingFailure(e.toString()); } - }, isAllowAny: true); + }, isAllowAny: true, isPaymentServices: true); if (failure != null) return Left(failure!); if (apiResponse == null) return Left(ServerFailure("Unknown error")); return Right(apiResponse!); diff --git a/lib/main.dart b/lib/main.dart index 70a2158..2739a6b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -9,6 +9,7 @@ import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; +import 'package:hmg_patient_app_new/features/habib_wallet/models/habib_wallet_view_model.dart'; import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; @@ -109,6 +110,12 @@ void main() async { errorHandlerService: getIt(), ), ), + ChangeNotifierProvider( + create: (_) => HabibWalletViewModel( + habibWalletRepo: getIt(), + errorHandlerService: getIt(), + ), + ), ChangeNotifierProvider( create: (_) => AuthenticationViewModel( authenticationRepo: getIt(), diff --git a/lib/presentation/appointments/appointment_payment_page.dart b/lib/presentation/appointments/appointment_payment_page.dart index 7f2ea9d..8430f05 100644 --- a/lib/presentation/appointments/appointment_payment_page.dart +++ b/lib/presentation/appointments/appointment_payment_page.dart @@ -55,6 +55,7 @@ class _AppointmentPaymentPageState extends State { void initState() { scheduleMicrotask(() { payfortViewModel.initPayfortViewModel(); + payfortViewModel.setIsApplePayConfigurationLoading(false); myAppointmentsViewModel.getPatientShareAppointment( widget.patientAppointmentHistoryResponseModel.projectID, widget.patientAppointmentHistoryResponseModel.clinicID, @@ -283,7 +284,7 @@ class _AppointmentPaymentPageState extends State { height: 80.h, fit: BoxFit.contain, ).paddingSymmetrical(24.h, 0.h).onPress(() { - payfortVM.setIsApplePayConfigurationLoading(true); + // payfortVM.setIsApplePayConfigurationLoading(true); startApplePay(); }), SizedBox(height: 12.h), @@ -344,62 +345,74 @@ class _AppointmentPaymentPageState extends State { // updateTamaraRequestStatus("Failed", "00", Utils.getAppointmentTransID(appo.projectID, appo.clinicID, appo.appointmentNo), tamaraOrderID, num.parse(selectedInstallments), appo); // } } else { - showCommonBottomSheet(context, - child: Utils.getLoadingWidget(), callBackFunc: (str) {}, title: "", height: ResponsiveExtension.screenHeight * 0.3, isCloseButtonVisible: false, isDismissible: false, isFullScreen: false); - await payfortViewModel.checkPaymentStatus( - transactionID: transID, - onSuccess: (apiResponse) async { - print(apiResponse.data); - if (payfortViewModel.payfortCheckPaymentStatusResponseModel!.responseMessage!.toLowerCase() == "success") { - await myAppointmentsViewModel.createAdvancePayment( - paymentMethodName: selectedPaymentMethod, - projectID: widget.patientAppointmentHistoryResponseModel.projectID, - clinicID: widget.patientAppointmentHistoryResponseModel.clinicID, - appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), - payedAmount: payfortViewModel.payfortCheckPaymentStatusResponseModel!.amount!, - paymentReference: payfortViewModel.payfortCheckPaymentStatusResponseModel!.fortId!, - patientID: "4767477", - patientType: 1, - onSuccess: (value) async { - print(value); - await myAppointmentsViewModel.addAdvanceNumberRequest( - advanceNumber: Utils.isVidaPlusProject(appState, widget.patientAppointmentHistoryResponseModel.projectID) - ? value.data['OnlineCheckInAppointments'][0]['AdvanceNumber_VP'].toString() - : value.data['OnlineCheckInAppointments'][0]['AdvanceNumber'].toString(), - paymentReference: payfortViewModel.payfortCheckPaymentStatusResponseModel!.fortId!, - appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), - onSuccess: (value) async { - if (widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment!) { - //TODO: Implement LiveCare Check-In API Call - } else { - await myAppointmentsViewModel.generateAppointmentQR( - clinicID: widget.patientAppointmentHistoryResponseModel.clinicID, - projectID: widget.patientAppointmentHistoryResponseModel.projectID, - appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), - isFollowUp: myAppointmentsViewModel.patientAppointmentShareResponseModel!.isFollowup!, - onSuccess: (apiResponse) { - Future.delayed(Duration(milliseconds: 500), () { - Navigator.of(context).pop(); - Navigator.pushAndRemoveUntil( - context, - FadePage( - page: LandingNavigation(), - ), - (r) => false); - Navigator.of(context).push( - FadePage(page: MyAppointmentsPage()), - ); - }); - }); - } - }); - }); - } else {} - }); + checkPaymentStatus(); // checkPaymentStatus(appo); } } + void checkPaymentStatus() async { + showCommonBottomSheet(context, + child: Utils.getLoadingWidget(), callBackFunc: (str) {}, title: "", height: ResponsiveExtension.screenHeight * 0.3, isCloseButtonVisible: false, isDismissible: false, isFullScreen: false); + await payfortViewModel.checkPaymentStatus( + transactionID: transID, + onSuccess: (apiResponse) async { + print(apiResponse.data); + if (payfortViewModel.payfortCheckPaymentStatusResponseModel!.responseMessage!.toLowerCase() == "success") { + await myAppointmentsViewModel.createAdvancePayment( + paymentMethodName: selectedPaymentMethod, + projectID: widget.patientAppointmentHistoryResponseModel.projectID, + clinicID: widget.patientAppointmentHistoryResponseModel.clinicID, + appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), + payedAmount: payfortViewModel.payfortCheckPaymentStatusResponseModel!.amount!, + paymentReference: payfortViewModel.payfortCheckPaymentStatusResponseModel!.fortId!, + patientID: "4767477", + patientType: 1, + onSuccess: (value) async { + print(value); + await myAppointmentsViewModel.addAdvanceNumberRequest( + advanceNumber: Utils.isVidaPlusProject(appState, widget.patientAppointmentHistoryResponseModel.projectID) + ? value.data['OnlineCheckInAppointments'][0]['AdvanceNumber_VP'].toString() + : value.data['OnlineCheckInAppointments'][0]['AdvanceNumber'].toString(), + paymentReference: payfortViewModel.payfortCheckPaymentStatusResponseModel!.fortId!, + appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), + onSuccess: (value) async { + if (widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment!) { + //TODO: Implement LiveCare Check-In API Call + } else { + await myAppointmentsViewModel.generateAppointmentQR( + clinicID: widget.patientAppointmentHistoryResponseModel.clinicID, + projectID: widget.patientAppointmentHistoryResponseModel.projectID, + appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), + isFollowUp: myAppointmentsViewModel.patientAppointmentShareResponseModel!.isFollowup!, + onSuccess: (apiResponse) { + Future.delayed(Duration(milliseconds: 500), () { + Navigator.of(context).pop(); + Navigator.pushAndRemoveUntil( + context, + FadePage( + page: LandingNavigation(), + ), + (r) => false); + Navigator.of(context).push( + FadePage(page: MyAppointmentsPage()), + ); + }); + }); + } + }); + }); + } else { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: "Payment Failed! Please try again.".needTranslation), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } + }); + } + openPaymentURL(String paymentMethod) { browser = MyInAppBrowser(onExitCallback: onBrowserExit, onLoadStartCallback: onBrowserLoadStart, context: context); transID = Utils.getAppointmentTransID( @@ -433,6 +446,8 @@ class _AppointmentPaymentPageState extends State { } startApplePay() async { + showCommonBottomSheet(context, + child: Utils.getLoadingWidget(), callBackFunc: (str) {}, title: "", height: ResponsiveExtension.screenHeight * 0.3, isCloseButtonVisible: false, isDismissible: false, isFullScreen: false); transID = Utils.getAppointmentTransID( widget.patientAppointmentHistoryResponseModel.projectID, widget.patientAppointmentHistoryResponseModel.clinicID, @@ -447,8 +462,7 @@ class _AppointmentPaymentPageState extends State { applePayInsertRequest.clientRequestID = transID; applePayInsertRequest.clinicID = widget.patientAppointmentHistoryResponseModel.clinicID; - //TODO: Need to pass dynamic params to the payment request instead of static values - applePayInsertRequest.currency = "SAR"; + applePayInsertRequest.currency = appState.getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED"; applePayInsertRequest.customerEmail = "CustID_${appState.getAuthenticatedUser()!.patientId.toString()}@HMG.com"; applePayInsertRequest.customerID = appState.getAuthenticatedUser()!.patientId.toString(); applePayInsertRequest.customerName = "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}"; @@ -460,8 +474,8 @@ class _AppointmentPaymentPageState extends State { applePayInsertRequest.serviceID = ServiceTypeEnum.appointmentPayment.getIdFromServiceEnum().toString(); applePayInsertRequest.channelID = 3; applePayInsertRequest.patientID = appState.getAuthenticatedUser()!.patientId.toString(); - applePayInsertRequest.patientTypeID = 1; - applePayInsertRequest.patientOutSA = 0; + applePayInsertRequest.patientTypeID = appState.getAuthenticatedUser()!.patientType; + applePayInsertRequest.patientOutSA = appState.getAuthenticatedUser()!.outSa; applePayInsertRequest.appointmentDate = widget.patientAppointmentHistoryResponseModel.appointmentDate; applePayInsertRequest.appointmentNo = widget.patientAppointmentHistoryResponseModel.appointmentNo; applePayInsertRequest.orderDescription = "Appointment Payment"; @@ -472,7 +486,7 @@ class _AppointmentPaymentPageState extends State { applePayInsertRequest.isSchedule = widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? "1" : "0"; applePayInsertRequest.language = appState.isArabic() ? 'ar' : 'en'; applePayInsertRequest.languageID = appState.isArabic() ? 1 : 2; - applePayInsertRequest.userName = 3628599; + applePayInsertRequest.userName = appState.getAuthenticatedUser()!.patientId; applePayInsertRequest.responseContinueURL = "http://hmg.com/Documents/success.html"; applePayInsertRequest.backClickUrl = "http://hmg.com/Documents/success.html"; applePayInsertRequest.paymentOption = "ApplePay"; @@ -499,12 +513,22 @@ class _AppointmentPaymentPageState extends State { merchantIdentifier: payfortViewModel.payfortProjectDetailsRespModel!.merchantIdentifier, applePayAccessCode: payfortViewModel.payfortProjectDetailsRespModel!.accessCode, applePayShaRequestPhrase: payfortViewModel.payfortProjectDetailsRespModel!.shaRequest, - currency: "SAR", + currency: appState.getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED", onFailed: (failureResult) async { log("failureResult: ${failureResult.message.toString()}"); + Navigator.of(context).pop(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: failureResult.message.toString()), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); }, onSucceeded: (successResult) async { + Navigator.of(context).pop(); log("successResult: ${successResult.responseMessage.toString()}"); + checkPaymentStatus(); }, // projectId: appo.projectID, // serviceTypeEnum: ServiceTypeEnum.appointmentPayment, diff --git a/lib/presentation/habib_wallet/habib_wallet_page.dart b/lib/presentation/habib_wallet/habib_wallet_page.dart new file mode 100644 index 0000000..92d62ec --- /dev/null +++ b/lib/presentation/habib_wallet/habib_wallet_page.dart @@ -0,0 +1,111 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/habib_wallet/models/habib_wallet_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/habib_wallet/recharge_wallet_page.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; +import 'package:provider/provider.dart'; + +class HabibWalletPage extends StatefulWidget { + const HabibWalletPage({super.key}); + + @override + State createState() => _HabibWalletState(); +} + +class _HabibWalletState extends State { + @override + Widget build(BuildContext context) { + AppState _appState = getIt.get(); + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + appBar: AppBar( + title: LocaleKeys.myWallet.tr(context: context).toText18(), + backgroundColor: AppColors.bgScaffoldColor, + ), + body: Padding( + padding: EdgeInsets.all(24.h), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.myWallet.tr(context: context).toText24(isBold: true), + SizedBox(height: 24.h), + Container( + width: double.infinity, + height: 180.h, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.blackBgColor, + borderRadius: 24, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "${_appState.getAuthenticatedUser()!.firstName!} ${_appState.getAuthenticatedUser()!.lastName!}".toText19(isBold: true, color: AppColors.whiteColor), + "MRN: ${_appState.getAuthenticatedUser()!.patientId!}".toText14(weight: FontWeight.w500, color: AppColors.greyTextColor), + ], + ), + Utils.buildSvgWithAssets(icon: AppAssets.habiblogo, width: 24.h, height: 24.h), + ], + ), + Spacer(), + LocaleKeys.balanceAmount.tr(context: context).toText14(weight: FontWeight.w500, color: AppColors.whiteColor), + Consumer(builder: (context, habibWalletVM, child) { + return Utils.getPaymentAmountWithSymbol(habibWalletVM.habibWalletAmount.toString().toText32(isBold: true, color: AppColors.whiteColor), AppColors.whiteColor, 13.h, + isExpanded: false) + .toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.h); + }), + ], + ), + ), + ), + SizedBox(height: 16.h), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CustomButton( + icon: AppAssets.recharge_icon, + iconSize: 21.h, + text: "Recharge".needTranslation, + onPressed: () { + Navigator.of(context).push( + FadePage( + page: RechargeWalletPage(), + ), + ); + }, + backgroundColor: AppColors.infoColor, + borderColor: AppColors.infoColor, + textColor: AppColors.whiteColor, + fontSize: 14, + fontWeight: FontWeight.bold, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + ), + ], + ), + ], + ), + ), + ), + ); + } +} diff --git a/lib/presentation/habib_wallet/recharge_wallet_page.dart b/lib/presentation/habib_wallet/recharge_wallet_page.dart new file mode 100644 index 0000000..0d8f16c --- /dev/null +++ b/lib/presentation/habib_wallet/recharge_wallet_page.dart @@ -0,0 +1,105 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/input_widget.dart'; + +class RechargeWalletPage extends StatefulWidget { + const RechargeWalletPage({super.key}); + + @override + State createState() => _RechargeWalletPageState(); +} + +class _RechargeWalletPageState extends State { + + FocusNode textFocusNode = FocusNode(); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + appBar: AppBar( + title: LocaleKeys.createAdvancedPayment.tr(context: context).toText18(), + backgroundColor: AppColors.bgScaffoldColor, + ), + body: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded( + child: SingleChildScrollView( + child: Padding( + padding: EdgeInsets.all(24.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.createAdvancedPayment.tr(context: context).toText20(isBold: true), + SizedBox(height: 24.h), + Container( + height: 135.h, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: false, + side: BorderSide(color: AppColors.textColor, width: 2.h), + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Enter an amount".needTranslation.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + Spacer(), + Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Utils.getPaymentAmountWithSymbol( + SizedBox( + width: 150.h, + child: TextInputWidget( + labelText: "", + hintText: "", + isEnable: true, + prefix: null, + isAllowRadius: true, + isBorderAllowed: false, + isAllowLeadingIcon: true, + autoFocus: true, + fontSize: 40, + padding: EdgeInsets.symmetric(horizontal: 8.h, vertical: 0.h), + focusNode: textFocusNode, + isWalletAmountInput: true, + // leadingIcon: AppAssets.student_card, + ), + ), + AppColors.textColor, + 13.h, + isExpanded: false), + const Spacer(), + "SAR".needTranslation.toText20(color: AppColors.greyTextColor, weight: FontWeight.w500), + ], + ), + ], + ), + ), + ), + ], + ), + ), + )), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: false, + ), + ), + ], + ), + ); + } +} diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index 55afffe..bf8dcc6 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; @@ -10,6 +12,7 @@ import 'package:hmg_patient_app_new/extensions/int_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; +import 'package:hmg_patient_app_new/features/habib_wallet/models/habib_wallet_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/authentication/quick_login.dart'; import 'package:hmg_patient_app_new/presentation/home/data/landing_page_data.dart'; @@ -35,22 +38,32 @@ class LandingPage extends StatefulWidget { class _LandingPageState extends State { late final AuthenticationViewModel authVM; + late final HabibWalletViewModel habibWalletVM; + + late AppState appState; @override void initState() { authVM = context.read(); + habibWalletVM = context.read(); authVM.savePushTokenToAppState(); if (mounted) { authVM.checkLastLoginStatus(() { showQuickLogin(context, false); }); } + scheduleMicrotask(() { + if (appState.isAuthenticated) { + habibWalletVM.initHabibWalletProvider(); + habibWalletVM.getPatientBalanceAmount(); + } + }); super.initState(); } @override Widget build(BuildContext context) { - AppState appState = getIt.get(); + appState = getIt.get(); NavigationService navigationService = getIt.get(); return Scaffold( diff --git a/lib/presentation/home/widgets/habib_wallet_card.dart b/lib/presentation/home/widgets/habib_wallet_card.dart index 80855dd..815337d 100644 --- a/lib/presentation/home/widgets/habib_wallet_card.dart +++ b/lib/presentation/home/widgets/habib_wallet_card.dart @@ -4,8 +4,12 @@ import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/habib_wallet/models/habib_wallet_view_model.dart'; +import 'package:hmg_patient_app_new/presentation/habib_wallet/habib_wallet_page.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; +import 'package:provider/provider.dart'; class HabibWalletCard extends StatelessWidget { const HabibWalletCard({super.key}); @@ -69,19 +73,21 @@ class HabibWalletCard extends StatelessWidget { ], ), SizedBox(height: 4.h), - Row( - children: [ - Utils.buildSvgWithAssets( - icon: AppAssets.saudi_riyal_icon, - iconColor: AppColors.dividerColor, - width: 24.h, - height: 24.h, - fit: BoxFit.contain, - ), - SizedBox(width: 8.h), - "200.18".toText32(isBold: true), - ], - ), + Consumer(builder: (context, habibWalletVM, child) { + return Row( + children: [ + Utils.buildSvgWithAssets( + icon: AppAssets.saudi_riyal_icon, + iconColor: AppColors.dividerColor, + width: 24.h, + height: 24.h, + fit: BoxFit.contain, + ), + SizedBox(width: 8.h), + habibWalletVM.habibWalletAmount.toString().toText32(isBold: true).toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.h), + ], + ); + }), Padding( padding: EdgeInsets.symmetric(horizontal: 50.h), child: Row( @@ -90,7 +96,13 @@ class HabibWalletCard extends StatelessWidget { SizedBox(width: 2.h), Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h), ], - ), + ).onPress(() { + Navigator.of(context).push( + FadePage( + page: HabibWalletPage(), + ), + ); + }), ), SizedBox(height: 16.h), Row( @@ -103,7 +115,7 @@ class HabibWalletCard extends StatelessWidget { CustomButton( icon: AppAssets.recharge_icon, iconSize: 18.h, - text: "Recharge", + text: "Recharge".needTranslation, onPressed: () {}, backgroundColor: AppColors.infoColor, borderColor: AppColors.infoColor, diff --git a/lib/widgets/input_widget.dart b/lib/widgets/input_widget.dart index 4fcf6f3..44e1327 100644 --- a/lib/widgets/input_widget.dart +++ b/lib/widgets/input_widget.dart @@ -5,9 +5,11 @@ import 'package:hmg_patient_app_new/core/app_export.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/dropdown/country_dropdown_widget.dart'; +import 'package:keyboard_actions/keyboard_actions.dart'; import '../core/dependencies.dart'; @@ -36,6 +38,8 @@ class TextInputWidget extends StatelessWidget { final String? errorMessage; Function(CountryEnum)? onCountryChange; final SelectionTypeEnum? selectionType; + final num? fontSize; + final bool? isWalletAmountInput; // final List countryList; // final Function(Country)? onCountryChange; @@ -63,10 +67,37 @@ class TextInputWidget extends StatelessWidget { this.errorMessage, this.onCountryChange, this.selectionType, + this.fontSize = 14, + this.isWalletAmountInput = false, // this.countryList = const [], // this.onCountryChange, }); + final FocusNode _focusNode = FocusNode(); + + KeyboardActionsConfig get _keyboardActionsConfig { + return KeyboardActionsConfig( + keyboardActionsPlatform: KeyboardActionsPlatform.ALL, + keyboardBarColor: const Color(0xFFCAD1D9), //Apple keyboard color + actions: [ + KeyboardActionsItem( + focusNode: focusNode ?? _focusNode, + toolbarButtons: [ + (node) { + return GestureDetector( + onTap: () => node.unfocus(), + child: Container( + padding: const EdgeInsets.all(12.0), + child: "Done".toText16(weight: FontWeight.w500, color: AppColors.infoColor), + ), + ); + } + ], + ), + ], + ); + } + @override Widget build(BuildContext context) { final errorColor = AppColors.primaryRedColor; @@ -184,40 +215,46 @@ class TextInputWidget extends StatelessWidget { } Widget _buildTextField(BuildContext context) { - return TextField( - enabled: isEnable, - scrollPadding: EdgeInsets.zero, - keyboardType: keyboardType, - controller: controller, - readOnly: isReadOnly, - textAlignVertical: TextAlignVertical.top, - textAlign: TextAlign.left, - textDirection: TextDirection.ltr, - onChanged: onChange, - focusNode: focusNode, - autofocus: autoFocus, - style: TextStyle(fontSize: 14.fSize, height: 21 / 14, fontWeight: FontWeight.w500, color: AppColors.textColor, letterSpacing: -0.2), - decoration: InputDecoration( - isDense: true, - hintText: hintText, - hintStyle: TextStyle(fontSize: 14.fSize, height: 21 / 16, fontWeight: FontWeight.w500, color: Color(0xff898A8D), letterSpacing: -0.2), - prefixIconConstraints: BoxConstraints(minWidth: 45.h), - prefixIcon: prefix == null - ? null - : Text( - "+" + prefix!, - style: TextStyle( - fontSize: 14.fSize, - height: 21 / 14, - fontWeight: FontWeight.w500, - color: Color(0xff2E303A), - letterSpacing: -0.2, + return KeyboardActions( + config: _keyboardActionsConfig, + disableScroll: true, + child: TextField( + enabled: isEnable, + scrollPadding: EdgeInsets.zero, + keyboardType: keyboardType, + controller: controller, + readOnly: isReadOnly, + textAlignVertical: TextAlignVertical.top, + textAlign: TextAlign.left, + textDirection: TextDirection.ltr, + onChanged: onChange, + focusNode: focusNode ?? _focusNode, + autofocus: autoFocus, + textInputAction: TextInputAction.done, + cursorHeight: isWalletAmountInput! ? 40.h : 18.h, + style: TextStyle(fontSize: fontSize!.fSize, height: isWalletAmountInput! ? 1 / 4 : 21 / 14, fontWeight: FontWeight.w500, color: AppColors.textColor, letterSpacing: -0.2), + decoration: InputDecoration( + isDense: true, + hintText: hintText, + hintStyle: TextStyle(fontSize: 14.fSize, height: 21 / 16, fontWeight: FontWeight.w500, color: Color(0xff898A8D), letterSpacing: -0.2), + prefixIconConstraints: BoxConstraints(minWidth: 45.h), + prefixIcon: prefix == null + ? null + : Text( + "+" + prefix!, + style: TextStyle( + fontSize: 14.fSize, + height: 21 / 14, + fontWeight: FontWeight.w500, + color: Color(0xff2E303A), + letterSpacing: -0.2, + ), ), - ), - contentPadding: EdgeInsets.zero, - border: InputBorder.none, - focusedBorder: InputBorder.none, - enabledBorder: InputBorder.none, + contentPadding: EdgeInsets.zero, + border: InputBorder.none, + focusedBorder: InputBorder.none, + enabledBorder: InputBorder.none, + ), ), ); } diff --git a/pubspec.yaml b/pubspec.yaml index afbac11..36326f4 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -75,6 +75,7 @@ dependencies: network_info_plus: ^6.1.4 flutter_nfc_kit: ^3.6.0 barcode_scan2: ^4.5.1 + keyboard_actions: ^4.2.0 dev_dependencies: flutter_test: From 01b6ab0e4b2103d6382c88dc445c8906d99d05b9 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Wed, 10 Sep 2025 14:29:19 +0300 Subject: [PATCH 03/20] habib wallet implementation contd. --- assets/images/svg/email_icon.svg | 4 + assets/images/svg/forward_chevron_icon.svg | 3 + assets/images/svg/my_account_icon.svg | 4 + assets/images/svg/notes_icon.svg | 9 + assets/images/svg/select_hospital_icon.svg | 5 + lib/core/app_assets.dart | 5 + .../habib_wallet/recharge_wallet_page.dart | 127 +++++++++- .../widgets/select-medical_file.dart | 232 ++++++++++++++++++ 8 files changed, 388 insertions(+), 1 deletion(-) create mode 100644 assets/images/svg/email_icon.svg create mode 100644 assets/images/svg/forward_chevron_icon.svg create mode 100644 assets/images/svg/my_account_icon.svg create mode 100644 assets/images/svg/notes_icon.svg create mode 100644 assets/images/svg/select_hospital_icon.svg create mode 100644 lib/presentation/habib_wallet/widgets/select-medical_file.dart diff --git a/assets/images/svg/email_icon.svg b/assets/images/svg/email_icon.svg new file mode 100644 index 0000000..9e052e7 --- /dev/null +++ b/assets/images/svg/email_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/svg/forward_chevron_icon.svg b/assets/images/svg/forward_chevron_icon.svg new file mode 100644 index 0000000..656d80e --- /dev/null +++ b/assets/images/svg/forward_chevron_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/my_account_icon.svg b/assets/images/svg/my_account_icon.svg new file mode 100644 index 0000000..4bfe5b1 --- /dev/null +++ b/assets/images/svg/my_account_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/svg/notes_icon.svg b/assets/images/svg/notes_icon.svg new file mode 100644 index 0000000..5d7ab0e --- /dev/null +++ b/assets/images/svg/notes_icon.svg @@ -0,0 +1,9 @@ + + + + + diff --git a/assets/images/svg/select_hospital_icon.svg b/assets/images/svg/select_hospital_icon.svg new file mode 100644 index 0000000..ce00f8c --- /dev/null +++ b/assets/images/svg/select_hospital_icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index a2cbc49..1db8810 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -93,6 +93,11 @@ class AppAssets { static const String checkin_location_icon = '$svgBasePath/checkin_location_icon.svg'; static const String checkin_nfc_icon = '$svgBasePath/checkin_nfc_icon.svg'; static const String checkin_qr_icon = '$svgBasePath/checkin_qr_icon.svg'; + static const String my_account_icon = '$svgBasePath/my_account_icon.svg'; + static const String select_hospital_icon = '$svgBasePath/select_hospital_icon.svg'; + static const String email_icon = '$svgBasePath/email_icon.svg'; + static const String notes_icon = '$svgBasePath/notes_icon.svg'; + static const String forward_chevron_icon = '$svgBasePath/forward_chevron_icon.svg'; //bottom navigation// static const String homeBottom = '$svgBasePath/home_bottom.svg'; diff --git a/lib/presentation/habib_wallet/recharge_wallet_page.dart b/lib/presentation/habib_wallet/recharge_wallet_page.dart index 0d8f16c..453b6a0 100644 --- a/lib/presentation/habib_wallet/recharge_wallet_page.dart +++ b/lib/presentation/habib_wallet/recharge_wallet_page.dart @@ -1,13 +1,20 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/input_widget.dart'; +import 'widgets/select-medical_file.dart'; + class RechargeWalletPage extends StatefulWidget { const RechargeWalletPage({super.key}); @@ -16,11 +23,11 @@ class RechargeWalletPage extends StatefulWidget { } class _RechargeWalletPageState extends State { - FocusNode textFocusNode = FocusNode(); @override Widget build(BuildContext context) { + AppState appState = getIt.get(); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, appBar: AppBar( @@ -87,6 +94,109 @@ class _RechargeWalletPageState extends State { ), ), ), + SizedBox(height: 24.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: false, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.my_account_icon, width: 40.h, height: 40.h), + SizedBox(width: 8.h), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.myAccount.tr(context: context).toText16(color: AppColors.textColor, weight: FontWeight.w500), + "${LocaleKeys.medicalFile.tr(context: context)}: ${appState.getAuthenticatedUser()!.patientId}".toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + ], + ), + ], + ), + Utils.buildSvgWithAssets(icon: AppAssets.arrow_down, width: 25.h, height: 25.h), + ], + ).onPress(() { + // showCommonBottomSheetWithoutHeight(context, title: "Select Medical File".needTranslation, child: SelectMedicalFile(), callBackFunc: () {}, isFullScreen: false); + showCommonBottomSheetWithoutHeight(context, title: "Select Medical File".needTranslation, child: const MultiPageBottomSheet(), callBackFunc: () {}, isFullScreen: false); + }), + SizedBox(height: 16.h), + Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h), + SizedBox(height: 16.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.select_hospital_icon, width: 40.h, height: 40.h), + SizedBox(width: 8.h), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.hospital.tr(context: context).toText16(color: AppColors.textColor, weight: FontWeight.w500), + "Olaya".toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + ], + ), + ], + ), + Utils.buildSvgWithAssets(icon: AppAssets.arrow_down, width: 25.h, height: 25.h), + ], + ), + SizedBox(height: 16.h), + Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h), + SizedBox(height: 16.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.email_icon, width: 40.h, height: 40.h), + SizedBox(width: 8.h), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.email.tr(context: context).toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), + "${appState.getAuthenticatedUser()!.emailAddress}".toText16(color: AppColors.textColor, weight: FontWeight.w500), + ], + ), + ], + ), + ], + ), + SizedBox(height: 16.h), + Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h), + SizedBox(height: 16.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.notes_icon, width: 40.h, height: 40.h), + SizedBox(width: 8.h), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.notes.tr(context: context).toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), + "Lorem Ipsum".toText16(color: AppColors.textColor, weight: FontWeight.w500), + ], + ), + ], + ), + ], + ), + SizedBox(height: 8.h), + ], + ), + ), + ), ], ), ), @@ -97,6 +207,21 @@ class _RechargeWalletPageState extends State { borderRadius: 24.h, hasShadow: false, ), + child: CustomButton( + text: LocaleKeys.next.tr(context: context), + onPressed: () {}, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 16, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 50.h, + icon: AppAssets.appointment_pay_icon, + iconColor: AppColors.whiteColor, + iconSize: 18.h, + ).paddingSymmetrical(24.h, 24.h), ), ], ), diff --git a/lib/presentation/habib_wallet/widgets/select-medical_file.dart b/lib/presentation/habib_wallet/widgets/select-medical_file.dart new file mode 100644 index 0000000..a6712f0 --- /dev/null +++ b/lib/presentation/habib_wallet/widgets/select-medical_file.dart @@ -0,0 +1,232 @@ +// import 'package:flutter/cupertino.dart'; +// import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +// +// class SelectMedicalFile extends StatelessWidget { +// const SelectMedicalFile({super.key}); +// +// @override +// Widget build(BuildContext context) { +// return Padding( +// padding: EdgeInsets.all(24.h), +// child: Column( +// crossAxisAlignment: CrossAxisAlignment.start, +// children: [ +// Container() +// ], +// ), +// ); +// } +// } + +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; + +class MultiPageBottomSheet extends StatefulWidget { + const MultiPageBottomSheet({Key? key}) : super(key: key); + + @override + State createState() => _MultiPageBottomSheetState(); +} + +class _MultiPageBottomSheetState extends State { + final PageController _pageController = PageController(); + int _currentPage = 0; + final int _totalPages = 3; + + late AppState appState; + + @override + void dispose() { + _pageController.dispose(); + super.dispose(); + } + + void _nextPage() { + if (_currentPage < _totalPages - 1) { + _pageController.animateToPage( + _currentPage + 2, + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + ); + } + } + + void _previousPage() { + if (_currentPage > 0) { + _pageController.previousPage( + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + ); + } + } + + @override + Widget build(BuildContext context) { + appState = getIt.get(); + return Container( + height: MediaQuery.of(context).size.height * 0.38, + child: Column( + children: [ + SizedBox(height: 12.h), + Expanded( + child: PageView( + controller: _pageController, + onPageChanged: (index) { + setState(() { + _currentPage = index; + }); + }, + children: [ + _buildPage1(), + _buildPage2(), + _buildPage3(), + ], + ), + ), + ], + ), + ); + } + + Widget _buildPage1() { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 16.h, + hasShadow: false, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.myMedicalFile.tr(context: context).toText16(color: AppColors.textColor, weight: FontWeight.w500), + "${LocaleKeys.fileno.tr(context: context)}: ${appState.getAuthenticatedUser()!.patientId}".toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), + ], + ), + Utils.buildSvgWithAssets(icon: AppAssets.forward_chevron_icon, iconColor: AppColors.textColor, width: 15.h, height: 15.h), + ], + ).paddingAll(16.h), + ).onPress(() { + Navigator.of(context).pop(); + }), + SizedBox(height: 16.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 16.h, + hasShadow: false, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.myMedicalFile.tr(context: context).toText16(color: AppColors.textColor, weight: FontWeight.w500), + "${LocaleKeys.fileno.tr(context: context)}: ${appState.getAuthenticatedUser()!.patientId}".toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + ], + ), + Utils.buildSvgWithAssets(icon: AppAssets.forward_chevron_icon, iconColor: AppColors.textColor, width: 15.h, height: 15.h), + ], + ).paddingAll(16.h), + ), + SizedBox(height: 16.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 16.h, + hasShadow: false, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.myMedicalFile.tr(context: context).toText16(color: AppColors.textColor, weight: FontWeight.w500), + "${LocaleKeys.fileno.tr(context: context)}: ${appState.getAuthenticatedUser()!.patientId}".toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + ], + ), + Utils.buildSvgWithAssets(icon: AppAssets.forward_chevron_icon, iconColor: AppColors.textColor, width: 15.h, height: 15.h), + ], + ).paddingAll(16.h), + ), + ], + ); + } + + Widget _buildPage2() { + return Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Page 2: Settings', + style: Theme.of(context).textTheme.headlineSmall, + ), + const SizedBox(height: 16), + SwitchListTile( + title: const Text('Enable notifications'), + value: true, + onChanged: (value) {}, + ), + SwitchListTile( + title: const Text('Dark mode'), + value: false, + onChanged: (value) {}, + ), + const ListTile( + leading: Icon(Icons.language), + title: Text('Language'), + subtitle: Text('English'), + trailing: Icon(Icons.arrow_forward_ios), + ), + ], + ), + ); + } + + Widget _buildPage3() { + return Padding( + padding: const EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + 'Page 3: Summary', + style: Theme.of(context).textTheme.headlineSmall, + ), + const SizedBox(height: 16), + const Card( + child: Padding( + padding: EdgeInsets.all(16.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text('Configuration Complete!'), + SizedBox(height: 8), + Text('Your settings have been saved successfully.'), + ], + ), + ), + ), + ], + ), + ); + } +} From 4269e99be915f7576c309da96c71ebce7d9201f9 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Wed, 10 Sep 2025 16:11:47 +0300 Subject: [PATCH 04/20] updates --- .../appointment_details_page.dart | 616 +++++++++--------- .../appointment_payment_page.dart | 254 ++++---- .../appointments/my_appointments_page.dart | 62 +- .../widgets/appointment_card.dart | 2 +- .../widgets/appointment_doctor_card.dart | 2 +- .../habib_wallet/habib_wallet_page.dart | 142 ++-- .../habib_wallet/recharge_wallet_page.dart | 317 +++++---- .../widgets/select-medical_file.dart | 16 +- .../home/widgets/habib_wallet_card.dart | 2 +- .../insurance/insurance_home_page.dart | 86 +-- .../insurance_update_details_card.dart | 2 +- .../widgets/patient_insurance_card.dart | 2 +- lib/presentation/lab/lab_orders_page.dart | 11 - .../prescription_detail_page.dart | 164 +++-- .../prescriptions_list_page.dart | 473 +++++++------- .../radiology/radiology_orders_page.dart | 18 +- 16 files changed, 1065 insertions(+), 1104 deletions(-) diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart index 7188620..dee8e40 100644 --- a/lib/presentation/appointments/appointment_details_page.dart +++ b/lib/presentation/appointments/appointment_details_page.dart @@ -5,8 +5,6 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; -import 'package:hmg_patient_app_new/core/app_state.dart'; -import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; @@ -20,6 +18,7 @@ import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/appointments/appointment_payment_page.dart'; import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart'; import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_doctor_card.dart'; +import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/presentation/prescriptions/prescription_detail_page.dart'; import 'package:hmg_patient_app_new/presentation/prescriptions/prescriptions_list_page.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; @@ -62,334 +61,333 @@ class _AppointmentDetailsPageState extends State { prescriptionsViewModel = Provider.of(context); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, - appBar: AppBar( - title: "Appointment Details".needTranslation.toText18(), - backgroundColor: AppColors.bgScaffoldColor, - ), body: Column( children: [ Expanded( - child: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - "Appointment Details".needTranslation.toText20(isBold: true), - if (AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)) - CustomButton( - text: "Report".needTranslation, - onPressed: () {}, - backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, - textColor: AppColors.primaryRedColor, - fontSize: 14, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, - iconSize: 16.h, - icon: AppAssets.report_icon, - iconColor: AppColors.primaryRedColor, - ) - ], - ), - SizedBox(height: 24.h), - AppointmentDoctorCard( - patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, - onAskDoctorTap: () {}, - onCancelTap: () async { - showCommonBottomSheet(context, - child: Utils.getLoadingWidget(), - callBackFunc: (str) {}, - title: "", - height: ResponsiveExtension.screenHeight * 0.3, - isCloseButtonVisible: false, - isDismissible: false, - isFullScreen: false); - await myAppointmentsViewModel.cancelAppointment( - patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, - onSuccess: (apiResponse) { - Navigator.of(context).pop(); - showCommonBottomSheet(context, - child: Utils.getSuccessWidget(loadingText: "Appointment Cancelled Successfully".needTranslation), - callBackFunc: (str) {}, - title: "", - height: ResponsiveExtension.screenHeight * 0.3, - isCloseButtonVisible: false, - isDismissible: false, - isFullScreen: false, - isSuccessDialog: true); - }); - Navigator.of(context).pop(); - Navigator.of(context).pop(); - }, - onRescheduleTap: () {}, - ), - SizedBox(height: 16.h), - !AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) - ? Column( - children: [ - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 20.h, - hasShadow: false, + child: CollapsingListView( + title: "Appointment Details".needTranslation, + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "Appointment Details".needTranslation.toText20(isBold: true), + if (AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)) + CustomButton( + text: "Report".needTranslation, + onPressed: () {}, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + iconSize: 16.h, + icon: AppAssets.report_icon, + iconColor: AppColors.primaryRedColor, + ) + ], + ), + SizedBox(height: 24.h), + AppointmentDoctorCard( + patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, + onAskDoctorTap: () {}, + onCancelTap: () async { + showCommonBottomSheet(context, + child: Utils.getLoadingWidget(), + callBackFunc: (str) {}, + title: "", + height: ResponsiveExtension.screenHeight * 0.3, + isCloseButtonVisible: false, + isDismissible: false, + isFullScreen: false); + await myAppointmentsViewModel.cancelAppointment( + patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, + onSuccess: (apiResponse) { + Navigator.of(context).pop(); + showCommonBottomSheet(context, + child: Utils.getSuccessWidget(loadingText: "Appointment Cancelled Successfully".needTranslation), + callBackFunc: (str) {}, + title: "", + height: ResponsiveExtension.screenHeight * 0.3, + isCloseButtonVisible: false, + isDismissible: false, + isFullScreen: false, + isSuccessDialog: true); + }); + Navigator.of(context).pop(); + Navigator.of(context).pop(); + }, + onRescheduleTap: () {}, + ), + SizedBox(height: 16.h), + !AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) + ? Column( + children: [ + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: false, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + "Appointment Status".needTranslation.toText16(isBold: true), + ], + ), + SizedBox(height: 4.h), + (!AppointmentType.isConfirmed(widget.patientAppointmentHistoryResponseModel) + ? "Not Confirmed".needTranslation.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500) + : "Confirmed".needTranslation.toText12(color: AppColors.successColor, fontWeight: FontWeight.w500)), + SizedBox(height: 16.h), + Stack( + children: [ + ClipRRect( + clipBehavior: Clip.hardEdge, + borderRadius: BorderRadius.circular(24), + child: Image.network( + "https://maps.googleapis.com/maps/api/staticmap?center=${widget.patientAppointmentHistoryResponseModel.latitude},${widget.patientAppointmentHistoryResponseModel.longitude}&zoom=14&size=350x165&maptype=roadmap&markers=color:red%7C${widget.patientAppointmentHistoryResponseModel.latitude},${widget.patientAppointmentHistoryResponseModel.longitude}&key=AIzaSyB6TERnxIr0yJ3qG4ULBZbu0sAD4tGqtng", + fit: BoxFit.contain, + ), + ), + Positioned( + bottom: 0, + child: SizedBox( + width: MediaQuery.of(context).size.width * 0.785, + child: CustomButton( + text: "Get Directions".needTranslation, + onPressed: () { + MapsLauncher.launchCoordinates(double.parse(widget.patientAppointmentHistoryResponseModel.latitude!), + double.parse(widget.patientAppointmentHistoryResponseModel.longitude!), widget.patientAppointmentHistoryResponseModel.projectName); + }, + backgroundColor: AppColors.textColor.withOpacity(0.8), + borderColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.01), + textColor: AppColors.whiteColor, + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12.h, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + icon: AppAssets.directions_icon, + iconColor: AppColors.whiteColor, + iconSize: 13.h, + ).paddingAll(12.h), + ), + ), + ], + ), + ], + ), + ), ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + SizedBox(height: 16.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: false, + ), + child: Row( + mainAxisSize: MainAxisSize.max, children: [ - Row( + Utils.buildSvgWithAssets(icon: AppAssets.prescription_reminder_icon, width: 35.h, height: 35.h), + SizedBox(width: 8.h), + Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Appointment Status".needTranslation.toText16(isBold: true), + LocaleKeys.setReminder.tr(context: context).toText13(isBold: true), + "Notify me before the appointment".needTranslation.toText11(color: AppColors.textColorLight, weight: FontWeight.w500), ], ), - SizedBox(height: 4.h), - (!AppointmentType.isConfirmed(widget.patientAppointmentHistoryResponseModel) - ? "Not Confirmed".needTranslation.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500) - : "Confirmed".needTranslation.toText12(color: AppColors.successColor, fontWeight: FontWeight.w500)), - SizedBox(height: 16.h), - Stack( - children: [ - ClipRRect( - clipBehavior: Clip.hardEdge, - borderRadius: BorderRadius.circular(24), - child: Image.network( - "https://maps.googleapis.com/maps/api/staticmap?center=${widget.patientAppointmentHistoryResponseModel.latitude},${widget.patientAppointmentHistoryResponseModel.longitude}&zoom=14&size=350x165&maptype=roadmap&markers=color:red%7C${widget.patientAppointmentHistoryResponseModel.latitude},${widget.patientAppointmentHistoryResponseModel.longitude}&key=AIzaSyB6TERnxIr0yJ3qG4ULBZbu0sAD4tGqtng", - fit: BoxFit.contain, - ), - ), - Positioned( - bottom: 0, - child: SizedBox( - width: MediaQuery.of(context).size.width * 0.785, - child: CustomButton( - text: "Get Directions".needTranslation, - onPressed: () { - MapsLauncher.launchCoordinates(double.parse(widget.patientAppointmentHistoryResponseModel.latitude!), - double.parse(widget.patientAppointmentHistoryResponseModel.longitude!), widget.patientAppointmentHistoryResponseModel.projectName); - }, - backgroundColor: AppColors.textColor.withOpacity(0.8), - borderColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.01), - textColor: AppColors.whiteColor, - fontSize: 14, - fontWeight: FontWeight.w500, - borderRadius: 12.h, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, - icon: AppAssets.directions_icon, - iconColor: AppColors.whiteColor, - iconSize: 13.h, - ).paddingAll(12.h), - ), - ), - ], + const Spacer(), + Switch( + activeColor: AppColors.successColor, + activeTrackColor: AppColors.successColor.withValues(alpha: .15), + value: widget.patientAppointmentHistoryResponseModel.hasReminder!, + onChanged: (newValue) { + setState(() { + myAppointmentsViewModel.setAppointmentReminder(newValue, widget.patientAppointmentHistoryResponseModel); + }); + }, ), ], - ), - ), - ), - SizedBox(height: 16.h), - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 20.h, - hasShadow: false, + ).paddingSymmetrical(16.h, 16.h), ), - child: Row( - mainAxisSize: MainAxisSize.max, + SizedBox(height: 16.h), + ], + ) + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Lab & Radiology".needTranslation.toText18(isBold: true), + SizedBox(height: 16.h), + GridView( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2, crossAxisSpacing: 13.h, mainAxisSpacing: 13.h, childAspectRatio: 7 / 6), + physics: NeverScrollableScrollPhysics(), + shrinkWrap: true, children: [ - Utils.buildSvgWithAssets(icon: AppAssets.prescription_reminder_icon, width: 35.h, height: 35.h), - SizedBox(width: 8.h), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LocaleKeys.setReminder.tr(context: context).toText13(isBold: true), - "Notify me before the appointment".needTranslation.toText11(color: AppColors.textColorLight, weight: FontWeight.w500), - ], + MedicalFileCard( + label: LocaleKeys.labResults.tr(context: context), + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.lab_result_icon, + iconSize: 40, + isLargeText: true, ), - const Spacer(), - Switch( - activeColor: AppColors.successColor, - activeTrackColor: AppColors.successColor.withValues(alpha: .15), - value: widget.patientAppointmentHistoryResponseModel.hasReminder!, - onChanged: (newValue) { - setState(() { - myAppointmentsViewModel.setAppointmentReminder(newValue, widget.patientAppointmentHistoryResponseModel); - }); - }, + MedicalFileCard( + label: LocaleKeys.radiology.tr(context: context), + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.radiology_icon, + iconSize: 40, + isLargeText: true, ), ], - ).paddingSymmetrical(16.h, 16.h), - ), - SizedBox(height: 16.h), - ], - ) - : Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - "Lab & Radiology".needTranslation.toText18(isBold: true), - SizedBox(height: 16.h), - GridView( - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 2, crossAxisSpacing: 13.h, mainAxisSpacing: 13.h, childAspectRatio: 7 / 6), - physics: NeverScrollableScrollPhysics(), - shrinkWrap: true, - children: [ - MedicalFileCard( - label: LocaleKeys.labResults.tr(context: context), - textColor: AppColors.blackColor, - backgroundColor: AppColors.whiteColor, - svgIcon: AppAssets.lab_result_icon, - iconSize: 40, - isLargeText: true, - ), - MedicalFileCard( - label: LocaleKeys.radiology.tr(context: context), - textColor: AppColors.blackColor, - backgroundColor: AppColors.whiteColor, - svgIcon: AppAssets.radiology_icon, - iconSize: 40, - isLargeText: true, - ), - ], - ), - LocaleKeys.prescriptions.tr().toText18(isBold: true), - SizedBox(height: 16.h), - Consumer(builder: (context, prescriptionVM, child) { - return prescriptionVM.isPrescriptionsDetailsLoading - ? const MoviesShimmerWidget() - : Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: Colors.white, - borderRadius: 20.h, - ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Column( - children: [ - ListView.separated( - itemCount: prescriptionVM.prescriptionDetailsList.length, - shrinkWrap: true, - padding: const EdgeInsets.only(left: 0, right: 8), - physics: NeverScrollableScrollPhysics(), - itemBuilder: (context, index) { - return AnimationConfiguration.staggeredList( - position: index, - duration: const Duration(milliseconds: 500), - child: SlideAnimation( - verticalOffset: 100.0, - child: FadeInAnimation( - child: Row( - children: [ - Utils.buildSvgWithAssets( - icon: AppAssets.prescription_item_icon, - width: 40.h, - height: 40.h, - ), - SizedBox(width: 8.h), - Row( - mainAxisSize: MainAxisSize.max, - children: [ - Column( - children: [ - SizedBox(width: 150.h, child: prescriptionVM.prescriptionDetailsList[index].itemDescription!.toText12(isBold: true, maxLine: 1)), - SizedBox( - width: 150.h, - child: - "Prescribed By: ${widget.patientAppointmentHistoryResponseModel.doctorTitle} ${widget.patientAppointmentHistoryResponseModel.doctorNameObj}" - .needTranslation - .toText10(weight: FontWeight.w500, color: AppColors.greyTextColor, letterSpacing: -0.4), - ), - ], - ), - SizedBox(width: 68.h), - Utils.buildSvgWithAssets( - icon: AppAssets.forward_arrow_icon, - iconColor: AppColors.blackColor, - width: 18.h, - height: 13.h, - fit: BoxFit.contain, - ), - ], - ), - ], + ), + LocaleKeys.prescriptions.tr().toText18(isBold: true), + SizedBox(height: 16.h), + Consumer(builder: (context, prescriptionVM, child) { + return prescriptionVM.isPrescriptionsDetailsLoading + ? const MoviesShimmerWidget() + : Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: Colors.white, + borderRadius: 20.h, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + children: [ + ListView.separated( + itemCount: prescriptionVM.prescriptionDetailsList.length, + shrinkWrap: true, + padding: const EdgeInsets.only(left: 0, right: 8), + physics: NeverScrollableScrollPhysics(), + itemBuilder: (context, index) { + return AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: Row( + children: [ + Utils.buildSvgWithAssets( + icon: AppAssets.prescription_item_icon, + width: 40.h, + height: 40.h, + ), + SizedBox(width: 8.h), + Row( + mainAxisSize: MainAxisSize.max, + children: [ + Column( + children: [ + SizedBox(width: 150.h, child: prescriptionVM.prescriptionDetailsList[index].itemDescription!.toText12(isBold: true, maxLine: 1)), + SizedBox( + width: 150.h, + child: + "Prescribed By: ${widget.patientAppointmentHistoryResponseModel.doctorTitle} ${widget.patientAppointmentHistoryResponseModel.doctorNameObj}" + .needTranslation + .toText10(weight: FontWeight.w500, color: AppColors.greyTextColor, letterSpacing: -0.4), + ), + ], + ), + SizedBox(width: 68.h), + Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon, + iconColor: AppColors.blackColor, + width: 18.h, + height: 13.h, + fit: BoxFit.contain, + ), + ], + ), + ], + ), ), ), + ); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + ).onPress(() { + prescriptionVM.setPrescriptionsDetailsLoading(); + Navigator.of(context).push( + FadePage( + page: PrescriptionDetailPage(prescriptionsResponseModel: getPrescriptionRequestModel()), ), ); - }, - separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), - ).onPress(() { - prescriptionVM.setPrescriptionsDetailsLoading(); - Navigator.of(context).push( - FadePage( - page: PrescriptionDetailPage(prescriptionsResponseModel: getPrescriptionRequestModel()), - ), - ); - }), - SizedBox(height: 16.h), - const Divider(color: AppColors.dividerColor), - SizedBox(height: 16.h), - Row( - children: [ - // Expanded( - // child: CustomButton( - // text: widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported! ? LocaleKeys.resendOrder.tr(context: context) : LocaleKeys.prescriptionDeliveryError.tr(context: context), - // onPressed: () {}, - // backgroundColor: AppColors.secondaryLightRedColor, - // borderColor: AppColors.secondaryLightRedColor, - // textColor: AppColors.primaryRedColor, - // fontSize: 14, - // fontWeight: FontWeight.w500, - // borderRadius: 12.h, - // height: 40.h, - // icon: AppAssets.appointment_calendar_icon, - // iconColor: AppColors.primaryRedColor, - // iconSize: 16.h, - // ), - // ), - // SizedBox(width: 16.h), - Expanded( - child: CustomButton( - text: "All Prescriptions".needTranslation, - onPressed: () { - Navigator.of(context) - .push( - FadePage( - page: PrescriptionsListPage(), - ), - ) - .then((val) { - prescriptionsViewModel.setPrescriptionsDetailsLoading(); - prescriptionsViewModel.getPrescriptionDetails(getPrescriptionRequestModel()); - }); - }, - backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, - textColor: AppColors.primaryRedColor, - fontSize: 14, - fontWeight: FontWeight.w500, - borderRadius: 12.h, - height: 40.h, - icon: AppAssets.requests, - iconColor: AppColors.primaryRedColor, - iconSize: 16.h, + }), + SizedBox(height: 16.h), + const Divider(color: AppColors.dividerColor), + SizedBox(height: 16.h), + Row( + children: [ + // Expanded( + // child: CustomButton( + // text: widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported! ? LocaleKeys.resendOrder.tr(context: context) : LocaleKeys.prescriptionDeliveryError.tr(context: context), + // onPressed: () {}, + // backgroundColor: AppColors.secondaryLightRedColor, + // borderColor: AppColors.secondaryLightRedColor, + // textColor: AppColors.primaryRedColor, + // fontSize: 14, + // fontWeight: FontWeight.w500, + // borderRadius: 12.h, + // height: 40.h, + // icon: AppAssets.appointment_calendar_icon, + // iconColor: AppColors.primaryRedColor, + // iconSize: 16.h, + // ), + // ), + // SizedBox(width: 16.h), + Expanded( + child: CustomButton( + text: "All Prescriptions".needTranslation, + onPressed: () { + Navigator.of(context) + .push( + FadePage( + page: PrescriptionsListPage(), + ), + ) + .then((val) { + prescriptionsViewModel.setPrescriptionsDetailsLoading(); + prescriptionsViewModel.getPrescriptionDetails(getPrescriptionRequestModel()); + }); + }, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12.h, + height: 40.h, + icon: AppAssets.requests, + iconColor: AppColors.primaryRedColor, + iconSize: 16.h, + ), ), - ), - ], - ), - ], + ], + ), + ], + ), ), - ), - ); - }), - ], - ), - ], - ).paddingSymmetrical(24.h, 24.h), + ); + }), + ], + ), + ], + ).paddingSymmetrical(24.h, 24.h), + ), ), ), Container( diff --git a/lib/presentation/appointments/appointment_payment_page.dart b/lib/presentation/appointments/appointment_payment_page.dart index 8430f05..baa2af3 100644 --- a/lib/presentation/appointments/appointment_payment_page.dart +++ b/lib/presentation/appointments/appointment_payment_page.dart @@ -9,7 +9,6 @@ import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/cache_consts.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; -import 'package:hmg_patient_app_new/features/authentication/models/resp_models/authenticated_user_resp_model.dart'; import 'package:hmg_patient_app_new/features/payfort/models/apple_pay_request_insert_model.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; @@ -22,6 +21,7 @@ import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/appointments/my_appointments_page.dart'; import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart'; import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.dart'; +import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/services/cache_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; @@ -72,131 +72,131 @@ class _AppointmentPaymentPageState extends State { payfortViewModel = Provider.of(context); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, - appBar: AppBar( - title: "Appointment Payment".needTranslation.toText18(), - backgroundColor: AppColors.bgScaffoldColor, - ), body: Consumer(builder: (context, myAppointmentsVM, child) { - return myAppointmentsVM.isAppointmentPatientShareLoading - ? const MoviesShimmerWidget().paddingAll(24.h) - : Column( - children: [ - Expanded( - child: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - "Appointment Payment".needTranslation.toText24(isBold: true).paddingSymmetrical(24.h, 0.h), - SizedBox(height: 24.h), - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 20.h, - hasShadow: false, - ), - child: Row( - mainAxisSize: MainAxisSize.max, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Image.asset(AppAssets.mada, width: 72.h, height: 25.h), - SizedBox(height: 16.h), - "Mada".needTranslation.toText16(isBold: true), - ], - ), - SizedBox(width: 8.h), - const Spacer(), - Utils.buildSvgWithAssets( - icon: AppAssets.forward_arrow_icon, - iconColor: AppColors.blackColor, - width: 18.h, - height: 13.h, - fit: BoxFit.contain, - ), - ], - ).paddingSymmetrical(16.h, 16.h), - ).paddingSymmetrical(24.h, 0.h).onPress(() { - selectedPaymentMethod = "MADA"; - openPaymentURL("mada"); - }), - SizedBox(height: 16.h), - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 20.h, - hasShadow: false, - ), - child: Row( - mainAxisSize: MainAxisSize.max, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - children: [ - Image.asset(AppAssets.visa, width: 50.h, height: 50.h), - SizedBox(width: 8.h), - Image.asset(AppAssets.Mastercard, width: 40.h, height: 40.h), - ], - ), - SizedBox(height: 16.h), - "Visa or Mastercard".needTranslation.toText16(isBold: true), - ], - ), - SizedBox(width: 8.h), - const Spacer(), - Utils.buildSvgWithAssets( - icon: AppAssets.forward_arrow_icon, - iconColor: AppColors.blackColor, - width: 18.h, - height: 13.h, - fit: BoxFit.contain, - ), - ], - ).paddingSymmetrical(16.h, 16.h), - ).paddingSymmetrical(24.h, 0.h).onPress(() { - selectedPaymentMethod = "VISA"; - openPaymentURL("visa"); - }), - SizedBox(height: 16.h), - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 20.h, - hasShadow: false, - ), - child: Row( - mainAxisSize: MainAxisSize.max, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Image.asset(AppAssets.tamara_en, width: 72.h, height: 25.h), - SizedBox(height: 16.h), - "Tamara".needTranslation.toText16(isBold: true), - ], - ), - SizedBox(width: 8.h), - const Spacer(), - Utils.buildSvgWithAssets( - icon: AppAssets.forward_arrow_icon, - iconColor: AppColors.blackColor, - width: 18.h, - height: 13.h, - fit: BoxFit.contain, - ), - ], - ).paddingSymmetrical(16.h, 16.h), - ).paddingSymmetrical(24.h, 0.h).onPress(() { - selectedPaymentMethod = "TAMARA"; - openPaymentURL("tamara"); - }), - ], - ), - ), - ), - Container( + return Column( + children: [ + Expanded( + child: CollapsingListView( + title: "Appointment Payment".needTranslation, + child: SingleChildScrollView( + child: myAppointmentsVM.isAppointmentPatientShareLoading + ? const MoviesShimmerWidget().paddingAll(24.h) + : Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 24.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: false, + ), + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Image.asset(AppAssets.mada, width: 72.h, height: 25.h), + SizedBox(height: 16.h), + "Mada".needTranslation.toText16(isBold: true), + ], + ), + SizedBox(width: 8.h), + const Spacer(), + Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon, + iconColor: AppColors.blackColor, + width: 18.h, + height: 13.h, + fit: BoxFit.contain, + ), + ], + ).paddingSymmetrical(16.h, 16.h), + ).paddingSymmetrical(24.h, 0.h).onPress(() { + selectedPaymentMethod = "MADA"; + openPaymentURL("mada"); + }), + SizedBox(height: 16.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: false, + ), + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Image.asset(AppAssets.visa, width: 50.h, height: 50.h), + SizedBox(width: 8.h), + Image.asset(AppAssets.Mastercard, width: 40.h, height: 40.h), + ], + ), + SizedBox(height: 16.h), + "Visa or Mastercard".needTranslation.toText16(isBold: true), + ], + ), + SizedBox(width: 8.h), + const Spacer(), + Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon, + iconColor: AppColors.blackColor, + width: 18.h, + height: 13.h, + fit: BoxFit.contain, + ), + ], + ).paddingSymmetrical(16.h, 16.h), + ).paddingSymmetrical(24.h, 0.h).onPress(() { + selectedPaymentMethod = "VISA"; + openPaymentURL("visa"); + }), + SizedBox(height: 16.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: false, + ), + child: Row( + mainAxisSize: MainAxisSize.max, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Image.asset(AppAssets.tamara_en, width: 72.h, height: 25.h), + SizedBox(height: 16.h), + "Tamara".needTranslation.toText16(isBold: true), + ], + ), + SizedBox(width: 8.h), + const Spacer(), + Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon, + iconColor: AppColors.blackColor, + width: 18.h, + height: 13.h, + fit: BoxFit.contain, + ), + ], + ).paddingSymmetrical(16.h, 16.h), + ).paddingSymmetrical(24.h, 0.h).onPress(() { + selectedPaymentMethod = "TAMARA"; + openPaymentURL("tamara"); + }), + ], + ), + ), + ), + ), + myAppointmentsVM.isAppointmentPatientShareLoading + ? SizedBox.shrink() + : Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.whiteColor, borderRadius: 24.h, @@ -292,8 +292,8 @@ class _AppointmentPaymentPageState extends State { ); }), ), - ], - ); + ], + ); }), ); } diff --git a/lib/presentation/appointments/my_appointments_page.dart b/lib/presentation/appointments/my_appointments_page.dart index 4a3a46e..5b59baf 100644 --- a/lib/presentation/appointments/my_appointments_page.dart +++ b/lib/presentation/appointments/my_appointments_page.dart @@ -1,17 +1,14 @@ import 'dart:async'; -import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; -import 'package:hmg_patient_app_new/core/app_state.dart'; -import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; -import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_card.dart'; +import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; import 'package:hmg_patient_app_new/widgets/shimmer/movies_shimmer_widget.dart'; @@ -41,36 +38,30 @@ class _MyAppointmentsPageState extends State { myAppointmentsViewModel = Provider.of(context); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, - appBar: AppBar( - title: "Appointments List".needTranslation.toText18(), - backgroundColor: AppColors.bgScaffoldColor, - ), - body: SingleChildScrollView( - child: Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - "Appointments List".needTranslation.toText24(isBold: true), - ], - ).paddingSymmetrical(24.h, 24.h), - CustomTabBar( - activeTextColor: Color(0xffED1C2B), - activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), - tabs: [ - CustomTabBarModel(null, "All Appt.".needTranslation), - CustomTabBarModel(null, "Upcoming".needTranslation), - CustomTabBarModel(null, "Completed".needTranslation), - ], - onTabChange: (index) { - print(index); - myAppointmentsViewModel.onTabChange(index); - }, - ).paddingSymmetrical(24.h, 0.h), - Consumer(builder: (context, myAppointmentsVM, child) { - return getSelectedTabData(myAppointmentsVM.selectedTabIndex, myAppointmentsVM); - }), - ], + body: CollapsingListView( + title: "Appointments List".needTranslation, + child: SingleChildScrollView( + child: Column( + children: [ + SizedBox(height: 16.h), + CustomTabBar( + activeTextColor: Color(0xffED1C2B), + activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), + tabs: [ + CustomTabBarModel(null, "All Appt.".needTranslation), + CustomTabBarModel(null, "Upcoming".needTranslation), + CustomTabBarModel(null, "Completed".needTranslation), + ], + onTabChange: (index) { + print(index); + myAppointmentsViewModel.onTabChange(index); + }, + ).paddingSymmetrical(24.h, 0.h), + Consumer(builder: (context, myAppointmentsVM, child) { + return getSelectedTabData(myAppointmentsVM.selectedTabIndex, myAppointmentsVM); + }), + ], + ), ), ), ); @@ -83,7 +74,6 @@ class _MyAppointmentsPageState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - SizedBox(height: 24.h), // Expandable list ListView.separated( shrinkWrap: true, @@ -127,7 +117,6 @@ class _MyAppointmentsPageState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - SizedBox(height: 24.h), // Expandable list ListView.separated( shrinkWrap: true, @@ -171,7 +160,6 @@ class _MyAppointmentsPageState extends State { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - SizedBox(height: 24.h), // Expandable list ListView.separated( shrinkWrap: true, diff --git a/lib/presentation/appointments/widgets/appointment_card.dart b/lib/presentation/appointments/widgets/appointment_card.dart index e9babeb..815443e 100644 --- a/lib/presentation/appointments/widgets/appointment_card.dart +++ b/lib/presentation/appointments/widgets/appointment_card.dart @@ -142,7 +142,7 @@ class _AppointmentCardState extends State { Wrap( direction: Axis.horizontal, spacing: 3.h, - runSpacing: -8.h, + runSpacing: 4.h, children: [ AppCustomChipWidget(labelText: widget.patientAppointmentHistoryResponseModel.clinicName!), AppCustomChipWidget(labelText: widget.patientAppointmentHistoryResponseModel.projectName!), diff --git a/lib/presentation/appointments/widgets/appointment_doctor_card.dart b/lib/presentation/appointments/widgets/appointment_doctor_card.dart index e6d7338..d7b0f68 100644 --- a/lib/presentation/appointments/widgets/appointment_doctor_card.dart +++ b/lib/presentation/appointments/widgets/appointment_doctor_card.dart @@ -51,7 +51,7 @@ class AppointmentDoctorCard extends StatelessWidget { Wrap( direction: Axis.horizontal, spacing: 3.h, - runSpacing: -8.h, + runSpacing: 4.h, children: [ AppCustomChipWidget(labelText: patientAppointmentHistoryResponseModel.clinicName!), AppCustomChipWidget(labelText: patientAppointmentHistoryResponseModel.projectName!), diff --git a/lib/presentation/habib_wallet/habib_wallet_page.dart b/lib/presentation/habib_wallet/habib_wallet_page.dart index 92d62ec..632b11d 100644 --- a/lib/presentation/habib_wallet/habib_wallet_page.dart +++ b/lib/presentation/habib_wallet/habib_wallet_page.dart @@ -10,6 +10,7 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/habib_wallet/models/habib_wallet_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/habib_wallet/recharge_wallet_page.dart'; +import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; @@ -28,81 +29,78 @@ class _HabibWalletState extends State { AppState _appState = getIt.get(); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, - appBar: AppBar( - title: LocaleKeys.myWallet.tr(context: context).toText18(), - backgroundColor: AppColors.bgScaffoldColor, - ), - body: Padding( - padding: EdgeInsets.all(24.h), - child: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LocaleKeys.myWallet.tr(context: context).toText24(isBold: true), - SizedBox(height: 24.h), - Container( - width: double.infinity, - height: 180.h, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.blackBgColor, - borderRadius: 24, - ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - "${_appState.getAuthenticatedUser()!.firstName!} ${_appState.getAuthenticatedUser()!.lastName!}".toText19(isBold: true, color: AppColors.whiteColor), - "MRN: ${_appState.getAuthenticatedUser()!.patientId!}".toText14(weight: FontWeight.w500, color: AppColors.greyTextColor), - ], - ), - Utils.buildSvgWithAssets(icon: AppAssets.habiblogo, width: 24.h, height: 24.h), - ], - ), - Spacer(), - LocaleKeys.balanceAmount.tr(context: context).toText14(weight: FontWeight.w500, color: AppColors.whiteColor), - Consumer(builder: (context, habibWalletVM, child) { - return Utils.getPaymentAmountWithSymbol(habibWalletVM.habibWalletAmount.toString().toText32(isBold: true, color: AppColors.whiteColor), AppColors.whiteColor, 13.h, - isExpanded: false) - .toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.h); - }), - ], + body: CollapsingListView( + title: LocaleKeys.myWallet.tr(), + child: Padding( + padding: EdgeInsets.all(24.h), + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + width: double.infinity, + height: 180.h, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.blackBgColor, + borderRadius: 24, ), - ), - ), - SizedBox(height: 16.h), - Row( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - CustomButton( - icon: AppAssets.recharge_icon, - iconSize: 21.h, - text: "Recharge".needTranslation, - onPressed: () { - Navigator.of(context).push( - FadePage( - page: RechargeWalletPage(), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "${_appState.getAuthenticatedUser()!.firstName!} ${_appState.getAuthenticatedUser()!.lastName!}".toText19(isBold: true, color: AppColors.whiteColor), + "MRN: ${_appState.getAuthenticatedUser()!.patientId!}".toText14(weight: FontWeight.w500, color: AppColors.greyTextColor), + ], + ), + Utils.buildSvgWithAssets(icon: AppAssets.habiblogo, width: 24.h, height: 24.h), + ], ), - ); - }, - backgroundColor: AppColors.infoColor, - borderColor: AppColors.infoColor, - textColor: AppColors.whiteColor, - fontSize: 14, - fontWeight: FontWeight.bold, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, + Spacer(), + LocaleKeys.balanceAmount.tr(context: context).toText14(weight: FontWeight.w500, color: AppColors.whiteColor), + Consumer(builder: (context, habibWalletVM, child) { + return Utils.getPaymentAmountWithSymbol(habibWalletVM.habibWalletAmount.toString().toText32(isBold: true, color: AppColors.whiteColor), AppColors.whiteColor, 13.h, + isExpanded: false) + .toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.h, width: 80.h, height: 40.h); + }), + ], + ), ), - ], - ), - ], + ), + SizedBox(height: 16.h), + Row( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + CustomButton( + icon: AppAssets.recharge_icon, + iconSize: 21.h, + text: "Recharge".needTranslation, + onPressed: () { + Navigator.of(context).push( + FadePage( + page: RechargeWalletPage(), + ), + ); + }, + backgroundColor: AppColors.infoColor, + borderColor: AppColors.infoColor, + textColor: AppColors.whiteColor, + fontSize: 14, + fontWeight: FontWeight.bold, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + ), + ], + ), + ], + ), ), ), ), diff --git a/lib/presentation/habib_wallet/recharge_wallet_page.dart b/lib/presentation/habib_wallet/recharge_wallet_page.dart index 453b6a0..ff1a505 100644 --- a/lib/presentation/habib_wallet/recharge_wallet_page.dart +++ b/lib/presentation/habib_wallet/recharge_wallet_page.dart @@ -8,6 +8,7 @@ import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; @@ -30,174 +31,172 @@ class _RechargeWalletPageState extends State { AppState appState = getIt.get(); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, - appBar: AppBar( - title: LocaleKeys.createAdvancedPayment.tr(context: context).toText18(), - backgroundColor: AppColors.bgScaffoldColor, - ), body: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Expanded( - child: SingleChildScrollView( - child: Padding( - padding: EdgeInsets.all(24.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LocaleKeys.createAdvancedPayment.tr(context: context).toText20(isBold: true), - SizedBox(height: 24.h), - Container( - height: 135.h, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.h, - hasShadow: false, - side: BorderSide(color: AppColors.textColor, width: 2.h), - ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - "Enter an amount".needTranslation.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), - Spacer(), - Row( - crossAxisAlignment: CrossAxisAlignment.end, - children: [ - Utils.getPaymentAmountWithSymbol( - SizedBox( - width: 150.h, - child: TextInputWidget( - labelText: "", - hintText: "", - isEnable: true, - prefix: null, - isAllowRadius: true, - isBorderAllowed: false, - isAllowLeadingIcon: true, - autoFocus: true, - fontSize: 40, - padding: EdgeInsets.symmetric(horizontal: 8.h, vertical: 0.h), - focusNode: textFocusNode, - isWalletAmountInput: true, - // leadingIcon: AppAssets.student_card, + child: CollapsingListView( + title: LocaleKeys.createAdvancedPayment.tr(context: context), + child: SingleChildScrollView( + child: Padding( + padding: EdgeInsets.all(24.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + height: 135.h, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: false, + side: BorderSide(color: AppColors.textColor, width: 2.h), + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Enter an amount".needTranslation.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + Spacer(), + Row( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Utils.getPaymentAmountWithSymbol( + SizedBox( + width: 150.h, + child: TextInputWidget( + labelText: "", + hintText: "", + isEnable: true, + prefix: null, + isAllowRadius: true, + isBorderAllowed: false, + isAllowLeadingIcon: true, + autoFocus: true, + fontSize: 40, + padding: EdgeInsets.symmetric(horizontal: 8.h, vertical: 0.h), + focusNode: textFocusNode, + isWalletAmountInput: true, + // leadingIcon: AppAssets.student_card, + ), ), - ), - AppColors.textColor, - 13.h, - isExpanded: false), - const Spacer(), - "SAR".needTranslation.toText20(color: AppColors.greyTextColor, weight: FontWeight.w500), - ], - ), - ], + AppColors.textColor, + 13.h, + isExpanded: false), + const Spacer(), + "SAR".needTranslation.toText20(color: AppColors.greyTextColor, weight: FontWeight.w500), + ], + ), + ], + ), ), ), - ), - SizedBox(height: 24.h), - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.h, - hasShadow: false, - ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - Utils.buildSvgWithAssets(icon: AppAssets.my_account_icon, width: 40.h, height: 40.h), - SizedBox(width: 8.h), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LocaleKeys.myAccount.tr(context: context).toText16(color: AppColors.textColor, weight: FontWeight.w500), - "${LocaleKeys.medicalFile.tr(context: context)}: ${appState.getAuthenticatedUser()!.patientId}".toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), - ], - ), - ], - ), - Utils.buildSvgWithAssets(icon: AppAssets.arrow_down, width: 25.h, height: 25.h), - ], - ).onPress(() { - // showCommonBottomSheetWithoutHeight(context, title: "Select Medical File".needTranslation, child: SelectMedicalFile(), callBackFunc: () {}, isFullScreen: false); - showCommonBottomSheetWithoutHeight(context, title: "Select Medical File".needTranslation, child: const MultiPageBottomSheet(), callBackFunc: () {}, isFullScreen: false); - }), - SizedBox(height: 16.h), - Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h), - SizedBox(height: 16.h), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - Utils.buildSvgWithAssets(icon: AppAssets.select_hospital_icon, width: 40.h, height: 40.h), - SizedBox(width: 8.h), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LocaleKeys.hospital.tr(context: context).toText16(color: AppColors.textColor, weight: FontWeight.w500), - "Olaya".toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), - ], - ), - ], - ), - Utils.buildSvgWithAssets(icon: AppAssets.arrow_down, width: 25.h, height: 25.h), - ], - ), - SizedBox(height: 16.h), - Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h), - SizedBox(height: 16.h), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - Utils.buildSvgWithAssets(icon: AppAssets.email_icon, width: 40.h, height: 40.h), - SizedBox(width: 8.h), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LocaleKeys.email.tr(context: context).toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), - "${appState.getAuthenticatedUser()!.emailAddress}".toText16(color: AppColors.textColor, weight: FontWeight.w500), - ], - ), - ], - ), - ], - ), - SizedBox(height: 16.h), - Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h), - SizedBox(height: 16.h), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Row( - children: [ - Utils.buildSvgWithAssets(icon: AppAssets.notes_icon, width: 40.h, height: 40.h), - SizedBox(width: 8.h), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LocaleKeys.notes.tr(context: context).toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), - "Lorem Ipsum".toText16(color: AppColors.textColor, weight: FontWeight.w500), - ], - ), - ], - ), - ], - ), - SizedBox(height: 8.h), - ], + SizedBox(height: 24.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: false, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.my_account_icon, width: 40.h, height: 40.h), + SizedBox(width: 8.h), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.myAccount.tr(context: context).toText16(color: AppColors.textColor, weight: FontWeight.w500), + "${LocaleKeys.medicalFile.tr(context: context)}: ${appState.getAuthenticatedUser()!.patientId}" + .toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + ], + ), + ], + ), + Utils.buildSvgWithAssets(icon: AppAssets.arrow_down, width: 25.h, height: 25.h), + ], + ).onPress(() { + // showCommonBottomSheetWithoutHeight(context, title: "Select Medical File".needTranslation, child: SelectMedicalFile(), callBackFunc: () {}, isFullScreen: false); + showCommonBottomSheetWithoutHeight(context, title: "Select Medical File".needTranslation, child: const MultiPageBottomSheet(), callBackFunc: () {}, isFullScreen: false); + }), + SizedBox(height: 16.h), + Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h), + SizedBox(height: 16.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.select_hospital_icon, width: 40.h, height: 40.h), + SizedBox(width: 8.h), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.hospital.tr(context: context).toText16(color: AppColors.textColor, weight: FontWeight.w500), + "Olaya".toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + ], + ), + ], + ), + Utils.buildSvgWithAssets(icon: AppAssets.arrow_down, width: 25.h, height: 25.h), + ], + ), + SizedBox(height: 16.h), + Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h), + SizedBox(height: 16.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.email_icon, width: 40.h, height: 40.h), + SizedBox(width: 8.h), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.email.tr(context: context).toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), + "${appState.getAuthenticatedUser()!.emailAddress}".toText16(color: AppColors.textColor, weight: FontWeight.w500), + ], + ), + ], + ), + ], + ), + SizedBox(height: 16.h), + Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h), + SizedBox(height: 16.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.notes_icon, width: 40.h, height: 40.h), + SizedBox(width: 8.h), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.notes.tr(context: context).toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), + "Lorem Ipsum".toText16(color: AppColors.textColor, weight: FontWeight.w500), + ], + ), + ], + ), + ], + ), + SizedBox(height: 8.h), + ], + ), ), ), - ), - ], + ], + ), ), ), )), diff --git a/lib/presentation/habib_wallet/widgets/select-medical_file.dart b/lib/presentation/habib_wallet/widgets/select-medical_file.dart index a6712f0..a70edb3 100644 --- a/lib/presentation/habib_wallet/widgets/select-medical_file.dart +++ b/lib/presentation/habib_wallet/widgets/select-medical_file.dart @@ -136,8 +136,8 @@ class _MultiPageBottomSheetState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - LocaleKeys.myMedicalFile.tr(context: context).toText16(color: AppColors.textColor, weight: FontWeight.w500), - "${LocaleKeys.fileno.tr(context: context)}: ${appState.getAuthenticatedUser()!.patientId}".toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + LocaleKeys.familyTitle.tr(context: context).toText16(color: AppColors.textColor, weight: FontWeight.w500), + "Select a medical file from your family".needTranslation.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), ], ), Utils.buildSvgWithAssets(icon: AppAssets.forward_chevron_icon, iconColor: AppColors.textColor, width: 15.h, height: 15.h), @@ -157,14 +157,20 @@ class _MultiPageBottomSheetState extends State { Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - LocaleKeys.myMedicalFile.tr(context: context).toText16(color: AppColors.textColor, weight: FontWeight.w500), - "${LocaleKeys.fileno.tr(context: context)}: ${appState.getAuthenticatedUser()!.patientId}".toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + LocaleKeys.otherAccount.tr(context: context).toText16(color: AppColors.textColor, weight: FontWeight.w500), + "Any active medical file from HMG".toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), ], ), Utils.buildSvgWithAssets(icon: AppAssets.forward_chevron_icon, iconColor: AppColors.textColor, width: 15.h, height: 15.h), ], ).paddingAll(16.h), - ), + ).onPress(() { + _pageController.animateToPage( + 2, + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut, + ); + }), ], ); } diff --git a/lib/presentation/home/widgets/habib_wallet_card.dart b/lib/presentation/home/widgets/habib_wallet_card.dart index 815337d..9bb53f5 100644 --- a/lib/presentation/home/widgets/habib_wallet_card.dart +++ b/lib/presentation/home/widgets/habib_wallet_card.dart @@ -84,7 +84,7 @@ class HabibWalletCard extends StatelessWidget { fit: BoxFit.contain, ), SizedBox(width: 8.h), - habibWalletVM.habibWalletAmount.toString().toText32(isBold: true).toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.h), + habibWalletVM.habibWalletAmount.toString().toText32(isBold: true).toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.h, width: 80.h, height: 40.h), ], ); }), diff --git a/lib/presentation/insurance/insurance_home_page.dart b/lib/presentation/insurance/insurance_home_page.dart index 451eeee..f7aaf24 100644 --- a/lib/presentation/insurance/insurance_home_page.dart +++ b/lib/presentation/insurance/insurance_home_page.dart @@ -9,6 +9,7 @@ import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/insurance/widgets/patient_insurance_card.dart'; +import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/presentation/lab/search_lab_report.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; @@ -42,49 +43,48 @@ class _InsuranceHomePageState extends State { insuranceViewModel = Provider.of(context); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, - appBar: AppBar( - title: LocaleKeys.insurance.tr(context: context).toText18(), - backgroundColor: AppColors.bgScaffoldColor, - ), - body: SingleChildScrollView( - child: Consumer(builder: (context, insuranceVM, child) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - "${LocaleKeys.insurance.tr(context: context)} ${LocaleKeys.updateInsurance.tr(context: context)}".toText24(isBold: true), - CustomButton( - icon: AppAssets.insurance_history_icon, - iconColor: AppColors.primaryRedColor, - iconSize: 21.h, - text: LocaleKeys.history.tr(context: context), - onPressed: () { - insuranceVM.setIsInsuranceHistoryLoading(true); - insuranceVM.getPatientInsuranceCardHistory(); - showCommonBottomSheet(context, - child: InsuranceHistory(), callBackFunc: (str){}, title: "", height: ResponsiveExtension.screenHeight * 0.65, isCloseButtonVisible: false, isFullScreen: false); - }, - backgroundColor: AppColors.primaryRedColor.withOpacity(0.1), - borderColor: AppColors.primaryRedColor.withOpacity(0.0), - textColor: AppColors.primaryRedColor, - fontSize: 14, - fontWeight: FontWeight.w600, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, - ), - ], - ).paddingSymmetrical(24.h, 24.h), - insuranceVM.isInsuranceLoading - ? const MoviesShimmerWidget().paddingSymmetrical(24.h, 0) - : PatientInsuranceCard( - insuranceCardDetailsModel: insuranceVM.patientInsuranceList.first, - isInsuranceExpired: DateTime.now().isAfter(DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo))), - ], - ); - }), + body: CollapsingListView( + title: "${LocaleKeys.insurance.tr(context: context)} ${LocaleKeys.updateInsurance.tr(context: context)}", + child: SingleChildScrollView( + child: Consumer(builder: (context, insuranceVM, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "${LocaleKeys.insurance.tr(context: context)} ${LocaleKeys.updateInsurance.tr(context: context)}".toText24(isBold: true), + CustomButton( + icon: AppAssets.insurance_history_icon, + iconColor: AppColors.primaryRedColor, + iconSize: 21.h, + text: LocaleKeys.history.tr(context: context), + onPressed: () { + insuranceVM.setIsInsuranceHistoryLoading(true); + insuranceVM.getPatientInsuranceCardHistory(); + showCommonBottomSheet(context, + child: InsuranceHistory(), callBackFunc: (str) {}, title: "", height: ResponsiveExtension.screenHeight * 0.65, isCloseButtonVisible: false, isFullScreen: false); + }, + backgroundColor: AppColors.primaryRedColor.withOpacity(0.1), + borderColor: AppColors.primaryRedColor.withOpacity(0.0), + textColor: AppColors.primaryRedColor, + fontSize: 14, + fontWeight: FontWeight.w600, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + ), + ], + ).paddingSymmetrical(24.h, 24.h), + insuranceVM.isInsuranceLoading + ? const MoviesShimmerWidget().paddingSymmetrical(24.h, 0) + : PatientInsuranceCard( + insuranceCardDetailsModel: insuranceVM.patientInsuranceList.first, + isInsuranceExpired: DateTime.now().isAfter(DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo))), + ], + ); + }), + ), ), ); } diff --git a/lib/presentation/insurance/widgets/insurance_update_details_card.dart b/lib/presentation/insurance/widgets/insurance_update_details_card.dart index b6a38b2..aa04d8c 100644 --- a/lib/presentation/insurance/widgets/insurance_update_details_card.dart +++ b/lib/presentation/insurance/widgets/insurance_update_details_card.dart @@ -69,7 +69,7 @@ class PatientInsuranceCardUpdateCard extends StatelessWidget { Wrap( direction: Axis.horizontal, spacing: 4.h, - runSpacing: -8.h, + runSpacing: 4.h, children: [ AppCustomChipWidget( icon: AppAssets.doctor_calendar_icon, diff --git a/lib/presentation/insurance/widgets/patient_insurance_card.dart b/lib/presentation/insurance/widgets/patient_insurance_card.dart index 720c452..cf8b7d7 100644 --- a/lib/presentation/insurance/widgets/patient_insurance_card.dart +++ b/lib/presentation/insurance/widgets/patient_insurance_card.dart @@ -75,7 +75,7 @@ class PatientInsuranceCard extends StatelessWidget { Wrap( direction: Axis.horizontal, spacing: 4.h, - runSpacing: -8.h, + runSpacing: 4.h, children: [ AppCustomChipWidget( icon: AppAssets.doctor_calendar_icon, diff --git a/lib/presentation/lab/lab_orders_page.dart b/lib/presentation/lab/lab_orders_page.dart index edb401c..f2e6ad6 100644 --- a/lib/presentation/lab/lab_orders_page.dart +++ b/lib/presentation/lab/lab_orders_page.dart @@ -4,26 +4,15 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; -import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; -import 'package:hmg_patient_app_new/core/utils/date_util.dart'; -import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; -import 'package:hmg_patient_app_new/core/utils/utils.dart'; -import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; -import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; -// import 'package:hmg_patient_app_new/presentation/lab/collapse_example.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; import 'package:hmg_patient_app_new/presentation/lab/search_lab_report.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; -import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; -import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/chip/custom_chip_widget.dart'; -import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; -import 'package:hmg_patient_app_new/widgets/shimmer/movies_shimmer_widget.dart'; import 'package:provider/provider.dart'; import 'collapsing_list_view.dart'; diff --git a/lib/presentation/prescriptions/prescription_detail_page.dart b/lib/presentation/prescriptions/prescription_detail_page.dart index 8f17c54..b3ace3d 100644 --- a/lib/presentation/prescriptions/prescription_detail_page.dart +++ b/lib/presentation/prescriptions/prescription_detail_page.dart @@ -13,6 +13,7 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/prescriptions/models/resp_models/patient_prescriptions_response_model.dart'; import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/presentation/prescriptions/prescription_item_view.dart'; import 'package:hmg_patient_app_new/presentation/prescriptions/prescription_reminder_view.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; @@ -53,94 +54,91 @@ class _PrescriptionDetailPageState extends State { prescriptionsViewModel = Provider.of(context, listen: false); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, - appBar: AppBar( - title: LocaleKeys.prescriptions.tr(context: context).toText18(), - backgroundColor: AppColors.bgScaffoldColor, - ), body: Column( children: [ Expanded( - child: SingleChildScrollView( - child: Consumer(builder: (context, prescriptionVM, child) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LocaleKeys.prescriptions.tr(context: context).toText24(isBold: true).paddingSymmetrical(24.h, 0.h), - SizedBox(height: 24.h), - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 20.h, - hasShadow: true, - ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisSize: MainAxisSize.min, - children: [ - Image.network( - widget.prescriptionsResponseModel.doctorImageURL!, - width: 24.h, - height: 24.h, - fit: BoxFit.fill, - ).circle(100), - SizedBox(width: 8.h), - Expanded(child: widget.prescriptionsResponseModel.doctorName!.toText16(isBold: true)), - ], - ), - SizedBox(height: 16.h), - Wrap( - direction: Axis.horizontal, - spacing: 4.h, - runSpacing: -8.h, - children: [ - AppCustomChipWidget( - icon: AppAssets.doctor_calendar_icon, - labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(widget.prescriptionsResponseModel.appointmentDate), false), - ), - AppCustomChipWidget( - labelText: widget.prescriptionsResponseModel.clinicDescription!, - ), - AppCustomChipWidget( - icon: AppAssets.rating_icon, - iconColor: AppColors.ratingColorYellow, - labelText: "Rating: ${widget.prescriptionsResponseModel.decimalDoctorRate}".needTranslation, - ), - AppCustomChipWidget( - labelText: widget.prescriptionsResponseModel.name!, - ), - ], - ), - ], + child: CollapsingListView( + title: LocaleKeys.prescriptions.tr(context: context), + child: SingleChildScrollView( + child: Consumer(builder: (context, prescriptionVM, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 24.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: true, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Image.network( + widget.prescriptionsResponseModel.doctorImageURL!, + width: 24.h, + height: 24.h, + fit: BoxFit.fill, + ).circle(100), + SizedBox(width: 8.h), + Expanded(child: widget.prescriptionsResponseModel.doctorName!.toText16(isBold: true)), + ], + ), + SizedBox(height: 16.h), + Wrap( + direction: Axis.horizontal, + spacing: 4.h, + runSpacing: 4.h, + children: [ + AppCustomChipWidget( + icon: AppAssets.doctor_calendar_icon, + labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(widget.prescriptionsResponseModel.appointmentDate), false), + ), + AppCustomChipWidget( + labelText: widget.prescriptionsResponseModel.clinicDescription!, + ), + AppCustomChipWidget( + icon: AppAssets.rating_icon, + iconColor: AppColors.ratingColorYellow, + labelText: "Rating: ${widget.prescriptionsResponseModel.decimalDoctorRate}".needTranslation, + ), + AppCustomChipWidget( + labelText: widget.prescriptionsResponseModel.name!, + ), + ], + ), + ], + ), ), - ), - ).paddingSymmetrical(24.h, 0.h), - SizedBox(height: 16.h), - ListView.builder( - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - itemCount: prescriptionVM.isPrescriptionsDetailsLoading ? 5 : prescriptionVM.prescriptionDetailsList.length, - itemBuilder: (context, index) { - return prescriptionVM.isPrescriptionsDetailsLoading - ? PrescriptionItemView(prescriptionVM: prescriptionVM, index: index, isLoading: true) - : AnimationConfiguration.staggeredList( - position: index, - duration: const Duration(milliseconds: 500), - child: SlideAnimation( - verticalOffset: 100.0, - child: FadeInAnimation( - child: PrescriptionItemView(prescriptionVM: prescriptionVM, index: index), + ).paddingSymmetrical(24.h, 0.h), + ListView.builder( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: prescriptionVM.isPrescriptionsDetailsLoading ? 5 : prescriptionVM.prescriptionDetailsList.length, + itemBuilder: (context, index) { + return prescriptionVM.isPrescriptionsDetailsLoading + ? PrescriptionItemView(prescriptionVM: prescriptionVM, index: index, isLoading: true) + : AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: PrescriptionItemView(prescriptionVM: prescriptionVM, index: index), + ), ), - ), - ); - }, - ).paddingSymmetrical(24.h, 0.h), - ], - ); - }), + ); + }, + ).paddingSymmetrical(24.h, 0.h), + ], + ); + }), + ), ), ), Container( diff --git a/lib/presentation/prescriptions/prescriptions_list_page.dart b/lib/presentation/prescriptions/prescriptions_list_page.dart index 9574f28..148afb1 100644 --- a/lib/presentation/prescriptions/prescriptions_list_page.dart +++ b/lib/presentation/prescriptions/prescriptions_list_page.dart @@ -11,6 +11,7 @@ import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/presentation/prescriptions/prescription_detail_page.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; @@ -43,256 +44,252 @@ class _PrescriptionsListPageState extends State { prescriptionsViewModel = Provider.of(context, listen: false); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, - appBar: AppBar( - title: LocaleKeys.prescriptions.tr(context: context).toText18(), - backgroundColor: AppColors.bgScaffoldColor, - ), - body: SingleChildScrollView( - child: Consumer(builder: (context, model, child) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LocaleKeys.prescriptions.tr(context: context).toText24(isBold: true).paddingSymmetrical(24.h, 0.h), - SizedBox(height: 16.h), - // Build Tab Bar - SizedBox(height: 16.h), - // Clinic & Hospital Sort - Row( - children: [ - CustomButton( - text: LocaleKeys.byClinic.tr(context: context), - onPressed: () { - model.setIsSortByClinic(true); - }, - backgroundColor: model.isSortByClinic ? AppColors.bgRedLightColor : AppColors.whiteColor, - borderColor: model.isSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withOpacity(0.2), - textColor: model.isSortByClinic ? AppColors.primaryRedColor : AppColors.blackColor, - fontSize: 12, - fontWeight: FontWeight.w500, - borderRadius: 10, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, - ), - SizedBox(width: 8.h), - CustomButton( - text: LocaleKeys.byHospital.tr(context: context), - onPressed: () { - model.setIsSortByClinic(false); - }, - backgroundColor: model.isSortByClinic ? AppColors.whiteColor : AppColors.bgRedLightColor, - borderColor: model.isSortByClinic ? AppColors.textColor.withOpacity(0.2) : AppColors.primaryRedColor, - textColor: model.isSortByClinic ? AppColors.blackColor : AppColors.primaryRedColor, - fontSize: 12, - fontWeight: FontWeight.w500, - borderRadius: 10, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, - ), - ], - ).paddingSymmetrical(24.h, 0.h), - SizedBox(height: 20.h), - // Expandable list - ListView.builder( - itemCount: model.isPrescriptionsOrdersLoading ? 4 : model.patientPrescriptionOrdersViewList.length, - physics: NeverScrollableScrollPhysics(), - shrinkWrap: true, - padding: const EdgeInsets.only(left: 0, right: 8), - itemBuilder: (context, index) { - final isExpanded = expandedIndex == index; - return model.isPrescriptionsOrdersLoading - ? const MoviesShimmerWidget() - : AnimationConfiguration.staggeredList( - position: index, - duration: const Duration(milliseconds: 500), - child: SlideAnimation( - verticalOffset: 100.0, - child: FadeInAnimation( - child: AnimatedContainer( - duration: Duration(milliseconds: 300), - curve: Curves.easeInOut, - margin: EdgeInsets.symmetric(vertical: 8.h), - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: true), - child: InkWell( - onTap: () { - setState(() { - expandedIndex = isExpanded ? null : index; - }); - }, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: EdgeInsets.all(16.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - CustomButton( - text: "${model.patientPrescriptionOrdersViewList[index].prescriptionsList!.length} Prescriptions Available", - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - Icon(isExpanded ? Icons.expand_less : Icons.expand_more), - ], - ), - SizedBox(height: 8.h), - model.patientPrescriptionOrdersViewList[index].filterName!.toText16(isBold: true) - ], + body: CollapsingListView( + title: LocaleKeys.prescriptions.tr(context: context), + child: SingleChildScrollView( + child: Consumer(builder: (context, model, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 16.h), + // Clinic & Hospital Sort + Row( + children: [ + CustomButton( + text: LocaleKeys.byClinic.tr(context: context), + onPressed: () { + model.setIsSortByClinic(true); + }, + backgroundColor: model.isSortByClinic ? AppColors.bgRedLightColor : AppColors.whiteColor, + borderColor: model.isSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withOpacity(0.2), + textColor: model.isSortByClinic ? AppColors.primaryRedColor : AppColors.blackColor, + fontSize: 12, + fontWeight: FontWeight.w500, + borderRadius: 10, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + ), + SizedBox(width: 8.h), + CustomButton( + text: LocaleKeys.byHospital.tr(context: context), + onPressed: () { + model.setIsSortByClinic(false); + }, + backgroundColor: model.isSortByClinic ? AppColors.whiteColor : AppColors.bgRedLightColor, + borderColor: model.isSortByClinic ? AppColors.textColor.withOpacity(0.2) : AppColors.primaryRedColor, + textColor: model.isSortByClinic ? AppColors.blackColor : AppColors.primaryRedColor, + fontSize: 12, + fontWeight: FontWeight.w500, + borderRadius: 10, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + ), + ], + ).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 20.h), + // Expandable list + ListView.builder( + itemCount: model.isPrescriptionsOrdersLoading ? 4 : model.patientPrescriptionOrdersViewList.length, + physics: NeverScrollableScrollPhysics(), + shrinkWrap: true, + padding: const EdgeInsets.only(left: 0, right: 8), + itemBuilder: (context, index) { + final isExpanded = expandedIndex == index; + return model.isPrescriptionsOrdersLoading + ? const MoviesShimmerWidget() + : AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: AnimatedContainer( + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + margin: EdgeInsets.symmetric(vertical: 8.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: true), + child: InkWell( + onTap: () { + setState(() { + expandedIndex = isExpanded ? null : index; + }); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + CustomButton( + text: "${model.patientPrescriptionOrdersViewList[index].prescriptionsList!.length} Prescriptions Available", + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 10, + fontWeight: FontWeight.w500, + borderRadius: 8, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 30.h, + ), + Icon(isExpanded ? Icons.expand_less : Icons.expand_more), + ], + ), + SizedBox(height: 8.h), + model.patientPrescriptionOrdersViewList[index].filterName!.toText16(isBold: true) + ], + ), ), - ), - AnimatedSwitcher( - duration: Duration(milliseconds: 500), - switchInCurve: Curves.easeIn, - switchOutCurve: Curves.easeOut, - transitionBuilder: (Widget child, Animation animation) { - return FadeTransition( - opacity: animation, - child: SizeTransition( - sizeFactor: animation, - axisAlignment: 0.0, - child: child, - ), - ); - }, - child: isExpanded - ? Container( - key: ValueKey(index), - padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 8.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ...model.patientPrescriptionOrdersViewList[index].prescriptionsList!.map((prescription) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisSize: MainAxisSize.min, - children: [ - Image.network( - prescription.doctorImageURL!, - width: 24.h, - height: 24.h, - fit: BoxFit.fill, - ).circle(100), - SizedBox(width: 8.h), - Expanded(child: prescription.doctorName!.toText14(weight: FontWeight.w500)), - ], - ), - SizedBox(height: 8.h), - Row( - children: [ - CustomButton( - text: DateUtil.formatDateToDate(DateUtil.convertStringToDate(prescription.appointmentDate), false), - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 24.h, - ), - SizedBox(width: 8.h), - CustomButton( - text: model.isSortByClinic ? prescription.name! : prescription.clinicDescription!, - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 24.h, - ), - ], - ), - SizedBox(height: 8.h), - Row( - children: [ - Expanded( - flex: 6, - child: CustomButton( - text: prescription.isHomeMedicineDeliverySupported! - ? LocaleKeys.resendOrder.tr(context: context) - : LocaleKeys.prescriptionDeliveryError.tr(context: context), + AnimatedSwitcher( + duration: Duration(milliseconds: 500), + switchInCurve: Curves.easeIn, + switchOutCurve: Curves.easeOut, + transitionBuilder: (Widget child, Animation animation) { + return FadeTransition( + opacity: animation, + child: SizeTransition( + sizeFactor: animation, + axisAlignment: 0.0, + child: child, + ), + ); + }, + child: isExpanded + ? Container( + key: ValueKey(index), + padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 8.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ...model.patientPrescriptionOrdersViewList[index].prescriptionsList!.map((prescription) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Image.network( + prescription.doctorImageURL!, + width: 24.h, + height: 24.h, + fit: BoxFit.fill, + ).circle(100), + SizedBox(width: 8.h), + Expanded(child: prescription.doctorName!.toText14(weight: FontWeight.w500)), + ], + ), + SizedBox(height: 8.h), + Row( + children: [ + CustomButton( + text: DateUtil.formatDateToDate(DateUtil.convertStringToDate(prescription.appointmentDate), false), + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 10, + fontWeight: FontWeight.w500, + borderRadius: 8, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 24.h, + ), + SizedBox(width: 8.h), + CustomButton( + text: model.isSortByClinic ? prescription.name! : prescription.clinicDescription!, onPressed: () {}, - backgroundColor: prescription.isHomeMedicineDeliverySupported! ? AppColors.successColor.withOpacity(0.15) : AppColors.greyF7Color, - borderColor: AppColors.successColor.withOpacity(0.01), - textColor: prescription.isHomeMedicineDeliverySupported! ? AppColors.successColor : AppColors.textColor.withOpacity(0.35), - fontSize: prescription.isHomeMedicineDeliverySupported! ? 14 : 12, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 10, fontWeight: FontWeight.w500, - borderRadius: 12, + borderRadius: 8, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, - icon: AppAssets.prescription_refill_icon, - iconColor: prescription.isHomeMedicineDeliverySupported! ? AppColors.successColor : AppColors.textColor.withOpacity(0.35), - iconSize: 14.h, + height: 24.h, ), - ), - SizedBox(width: 8.h), - Expanded( - flex: 1, - child: Container( - height: 40.h, - width: 40.h, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.textColor, - borderRadius: 10.h, + ], + ), + SizedBox(height: 8.h), + Row( + children: [ + Expanded( + flex: 6, + child: CustomButton( + text: prescription.isHomeMedicineDeliverySupported! + ? LocaleKeys.resendOrder.tr(context: context) + : LocaleKeys.prescriptionDeliveryError.tr(context: context), + onPressed: () {}, + backgroundColor: prescription.isHomeMedicineDeliverySupported! ? AppColors.successColor.withOpacity(0.15) : AppColors.greyF7Color, + borderColor: AppColors.successColor.withOpacity(0.01), + textColor: prescription.isHomeMedicineDeliverySupported! ? AppColors.successColor : AppColors.textColor.withOpacity(0.35), + fontSize: prescription.isHomeMedicineDeliverySupported! ? 14 : 12, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + icon: AppAssets.prescription_refill_icon, + iconColor: prescription.isHomeMedicineDeliverySupported! ? AppColors.successColor : AppColors.textColor.withOpacity(0.35), + iconSize: 14.h, ), - child: Padding( - padding: EdgeInsets.all(8.h), - child: Utils.buildSvgWithAssets( - icon: AppAssets.forward_arrow_icon, - width: 10.h, - height: 10.h, - fit: BoxFit.contain, + ), + SizedBox(width: 8.h), + Expanded( + flex: 1, + child: Container( + height: 40.h, + width: 40.h, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.textColor, + borderRadius: 10.h, ), - ), - ).onPress(() { - model.setPrescriptionsDetailsLoading(); - Navigator.of(context).push( - FadePage( - page: PrescriptionDetailPage(prescriptionsResponseModel: prescription), + child: Padding( + padding: EdgeInsets.all(8.h), + child: Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon, + width: 10.h, + height: 10.h, + fit: BoxFit.contain, + ), ), - ); - }), - ), - ], - ), - SizedBox(height: 12.h), - Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h), - SizedBox(height: 12.h), - ], - ); - }).toList(), - ], - ), - ) - : SizedBox.shrink(), - ), - ], + ).onPress(() { + model.setPrescriptionsDetailsLoading(); + Navigator.of(context).push( + FadePage( + page: PrescriptionDetailPage(prescriptionsResponseModel: prescription), + ), + ); + }), + ), + ], + ), + SizedBox(height: 12.h), + Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h), + SizedBox(height: 12.h), + ], + ); + }).toList(), + ], + ), + ) + : SizedBox.shrink(), + ), + ], + ), ), ), ), ), - ), - ); - }, - ).paddingSymmetrical(24.h, 0.h), - ], - ); - }), + ); + }, + ).paddingSymmetrical(24.h, 0.h), + ], + ); + }), + ), ), ); } diff --git a/lib/presentation/radiology/radiology_orders_page.dart b/lib/presentation/radiology/radiology_orders_page.dart index d1b3830..19d0908 100644 --- a/lib/presentation/radiology/radiology_orders_page.dart +++ b/lib/presentation/radiology/radiology_orders_page.dart @@ -11,6 +11,7 @@ import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; +import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/shimmer/movies_shimmer_widget.dart'; @@ -43,27 +44,14 @@ class _RadiologyOrdersPageState extends State { radiologyViewModel = Provider.of(context); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, - appBar: AppBar( - title: LocaleKeys.radiology.tr(context: context).toText18(), - backgroundColor: AppColors.bgScaffoldColor, - ), - body: Padding( - padding: EdgeInsets.all(24.h), + body: CollapsingListView( + title: LocaleKeys.radiology.tr(context: context), child: SingleChildScrollView( child: Consumer( builder: (context, model, child) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - LocaleKeys.radiology.tr(context: context).toText24(isBold: true), - Utils.buildSvgWithAssets(icon: AppAssets.search_icon), - ], - ), - SizedBox(height: 16.h), - // Build Tab Bar SizedBox(height: 16.h), // Expandable list ListView.builder( From a5f64798e0a7b3578e52f00ba58d8e6760dce32f Mon Sep 17 00:00:00 2001 From: Sultan khan Date: Wed, 10 Sep 2025 16:34:28 +0300 Subject: [PATCH 05/20] login fixes --- .../authentication/authentication_view_model.dart | 9 ++++++--- lib/presentation/lab/lab_orders_page.dart | 1 - 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index c82037f..505391d 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -191,7 +191,7 @@ class AuthenticationViewModel extends ChangeNotifier { (failure) async { // LoadingUtils.hideFullScreenLoader(); // await _errorHandlerService.handleError(failure: failure); - LoadingUtils.hideFullScreenLoader(); + LoaderBottomSheet.hideLoader(); _navigationService.pushPage(page: LoginScreen()); }, (apiResponse) { @@ -238,6 +238,7 @@ class AuthenticationViewModel extends ChangeNotifier { _navigationService.pushPage(page: SavedLogin()); // _navigationService.pushPage(page: LoginScreen()); } else { + print("print login........"); LoaderBottomSheet.hideLoader(); _navigationService.pushPage(page: LoginScreen()); } @@ -451,8 +452,9 @@ class AuthenticationViewModel extends ChangeNotifier { bool isUserAgreedBefore = await checkIfUserAgreedBefore(request: request); //updating the last login type in app state to show the fingerprint/face id option on home screen - _appState.getSelectDeviceByImeiRespModelElement!.logInType = loginTypeEnum.toInt; - + if( _appState.getSelectDeviceByImeiRespModelElement !=null) { + _appState.getSelectDeviceByImeiRespModelElement!.logInType = loginTypeEnum.toInt; + } LoaderBottomSheet.hideLoader(); insertPatientIMEIData(loginTypeEnum.toInt); clearDefaultInputValues(); @@ -738,6 +740,7 @@ class AuthenticationViewModel extends ChangeNotifier { deviceTypeId: _appState.getDeviceTypeID(), patientId: _appState.getAuthenticatedUser()!.patientId!, patientIdentificationNo: _appState.getAuthenticatedUser()!.patientIdentificationNo!, + identificationNo: _appState.getAuthenticatedUser()!.patientIdentificationNo!, firstName: _appState.getAuthenticatedUser()!.firstName!, lastName: _appState.getAuthenticatedUser()!.lastName!, patientTypeId: _appState.getAuthenticatedUser()!.patientType, diff --git a/lib/presentation/lab/lab_orders_page.dart b/lib/presentation/lab/lab_orders_page.dart index cf90fd8..0391f6a 100644 --- a/lib/presentation/lab/lab_orders_page.dart +++ b/lib/presentation/lab/lab_orders_page.dart @@ -14,7 +14,6 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; -import 'package:hmg_patient_app_new/presentation/lab/collapse_example.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; import 'package:hmg_patient_app_new/presentation/lab/search_lab_report.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; From 28da6e0a2c5e52243878fbc75eddba76de2a8c76 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Wed, 10 Sep 2025 16:38:09 +0300 Subject: [PATCH 06/20] updates --- lib/core/api_consts.dart | 2 +- .../authentication_view_model.dart | 51 +++++++++---------- .../appointments/my_appointments_page.dart | 1 + 3 files changed, 25 insertions(+), 29 deletions(-) diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 1e22ded..f54fd26 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -726,7 +726,7 @@ const DEACTIVATE_ACCOUNT = 'Services/Patients.svc/REST/PatientAppleActivation_In class ApiConsts { static const maxSmallScreen = 660; - static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.prod; + static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.uat; // static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index c82037f..515c2d1 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -80,7 +80,7 @@ class AuthenticationViewModel extends ChangeNotifier { String errorMsg = ''; final FocusNode myFocusNode = FocusNode(); var healthId; - int getDeviceLastLogin =1; + int getDeviceLastLogin = 1; Future onLoginPressed() async { try { @@ -292,12 +292,11 @@ class AuthenticationViewModel extends ChangeNotifier { } else if (apiResponse.messageStatus == 1) { if (apiResponse.data['isSMSSent']) { _appState.setAppAuthToken = apiResponse.data['LogInTokenID']; - await sendActivationCode( + await sendActivationCode( otpTypeEnum: otpTypeEnum, phoneNumber: phoneNumberController.text, nationalIdOrFileNumber: nationalIdController.text, ); - } else { if (apiResponse.data['IsAuthenticated']) { await checkActivationCode( @@ -305,7 +304,6 @@ class AuthenticationViewModel extends ChangeNotifier { onWrongActivationCode: (String? message) {}, activationCode: null, //todo silent login case halded on the repo itself.. ); - } } } @@ -392,7 +390,7 @@ class AuthenticationViewModel extends ChangeNotifier { countryCode: selectedCountrySignup.countryCode, loginType: loginTypeEnum.toInt) .toJson(); - LoaderBottomSheet.showLoader(); + LoaderBottomSheet.showLoader(); bool isForRegister = (_appState.getUserRegistrationPayload.healthId != null || _appState.getUserRegistrationPayload.patientOutSa == true); if (isForRegister) { if (_appState.getUserRegistrationPayload.patientOutSa == true) request['DOB'] = _appState.getUserRegistrationPayload.dob; @@ -423,7 +421,6 @@ class AuthenticationViewModel extends ChangeNotifier { resultEither.fold((failure) async => await _errorHandlerService.handleError(failure: failure), (apiResponse) async { final activation = CheckActivationCode.fromJson(apiResponse.data as Map); - if (activation.errorCode == '699') { // Todo: Hide Loader // GifLoaderDialogUtils.hideDialog(context); @@ -441,7 +438,6 @@ class AuthenticationViewModel extends ChangeNotifier { // Navigator.popUntil(context, (route) => Utils.route(route, equalsTo: RegisterNew)); return; } else { - if (activation.list != null && activation.list!.isNotEmpty) { _appState.setAuthenticatedUser(activation.list!.first); } @@ -451,7 +447,9 @@ class AuthenticationViewModel extends ChangeNotifier { bool isUserAgreedBefore = await checkIfUserAgreedBefore(request: request); //updating the last login type in app state to show the fingerprint/face id option on home screen - _appState.getSelectDeviceByImeiRespModelElement!.logInType = loginTypeEnum.toInt; + if (_appState.getSelectDeviceByImeiRespModelElement != null) { + _appState.getSelectDeviceByImeiRespModelElement!.logInType = loginTypeEnum.toInt; + } LoaderBottomSheet.hideLoader(); insertPatientIMEIData(loginTypeEnum.toInt); @@ -548,12 +546,12 @@ class AuthenticationViewModel extends ChangeNotifier { loginTypeEnum = (_appState.deviceTypeID == 1 ? LoginTypeEnum.face : LoginTypeEnum.fingerprint); if (!_appState.isAuthenticated) { //commenting this api to check either the same flow working or not because this api does not needed further if work fine we will remove this - // await getPatientDeviceData(loginTypeEnum.toInt); + // await getPatientDeviceData(loginTypeEnum.toInt); await checkActivationCode(otpTypeEnum: OTPTypeEnum.faceIDFingerprint, activationCode: null, onWrongActivationCode: (String? message) {}); - await insertPatientIMEIData(loginTypeEnum.toInt); + await insertPatientIMEIData(loginTypeEnum.toInt); } else { // authenticated = true; - await insertPatientIMEIData(loginTypeEnum.toInt); + await insertPatientIMEIData(loginTypeEnum.toInt); } LoaderBottomSheet.hideLoader(); notifyListeners(); @@ -784,33 +782,30 @@ class AuthenticationViewModel extends ChangeNotifier { log("Insert IMEI Failed"); } }); - } - Future getPatientDeviceData(int loginType) async { final resultEither = await _authenticationRepo.getPatientDeviceData( patientDeviceDataRequest: GetUserMobileDeviceData( - deviceToken: _appState.deviceToken, - deviceTypeId: _appState.getDeviceTypeID(), - patientId: _appState.getSelectDeviceByImeiRespModelElement!.patientId!, - patientType: _appState.getSelectDeviceByImeiRespModelElement!.patientType, - patientOutSa:_appState.getSelectDeviceByImeiRespModelElement!.outSa == true ?1 :0, - loginType: loginType, - languageId: _appState.getLanguageID(), - latitude: _appState.userLat, - longitude: _appState.userLong, - mobileNo:_appState.getSelectDeviceByImeiRespModelElement!.mobile! , - patientMobileNumber:int.parse(_appState.getSelectDeviceByImeiRespModelElement!.mobile!), - nationalId:_appState.getSelectDeviceByImeiRespModelElement!.identificationNo) + deviceToken: _appState.deviceToken, + deviceTypeId: _appState.getDeviceTypeID(), + patientId: _appState.getSelectDeviceByImeiRespModelElement!.patientId!, + patientType: _appState.getSelectDeviceByImeiRespModelElement!.patientType, + patientOutSa: _appState.getSelectDeviceByImeiRespModelElement!.outSa == true ? 1 : 0, + loginType: loginType, + languageId: _appState.getLanguageID(), + latitude: _appState.userLat, + longitude: _appState.userLong, + mobileNo: _appState.getSelectDeviceByImeiRespModelElement!.mobile!, + patientMobileNumber: int.parse(_appState.getSelectDeviceByImeiRespModelElement!.mobile!), + nationalId: _appState.getSelectDeviceByImeiRespModelElement!.identificationNo) .toJson()); resultEither.fold((failure) async => await _errorHandlerService.handleError(failure: failure), (apiResponse) async { if (apiResponse.messageStatus == 1) { - dynamic deviceInfo= apiResponse.data['List_MobileLoginInfo']; - getDeviceLastLogin = deviceInfo['LoginType']; + dynamic deviceInfo = apiResponse.data['List_MobileLoginInfo']; + getDeviceLastLogin = deviceInfo['LoginType']; } }); - } @override diff --git a/lib/presentation/appointments/my_appointments_page.dart b/lib/presentation/appointments/my_appointments_page.dart index 5b59baf..bead9e6 100644 --- a/lib/presentation/appointments/my_appointments_page.dart +++ b/lib/presentation/appointments/my_appointments_page.dart @@ -76,6 +76,7 @@ class _MyAppointmentsPageState extends State { children: [ // Expandable list ListView.separated( + padding: EdgeInsets.only(top: 24.h), shrinkWrap: true, physics: NeverScrollableScrollPhysics(), itemCount: myAppointmentsVM.isMyAppointmentsLoading From 41ca0e27d97e5d8755fa241e15da6e31cd63f839 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Wed, 10 Sep 2025 16:39:42 +0300 Subject: [PATCH 07/20] updates --- .../appointments/my_appointments_page.dart | 44 +++++++++---------- 1 file changed, 22 insertions(+), 22 deletions(-) diff --git a/lib/presentation/appointments/my_appointments_page.dart b/lib/presentation/appointments/my_appointments_page.dart index bead9e6..3ea4399 100644 --- a/lib/presentation/appointments/my_appointments_page.dart +++ b/lib/presentation/appointments/my_appointments_page.dart @@ -168,32 +168,32 @@ class _MyAppointmentsPageState extends State { itemCount: myAppointmentsVM.isMyAppointmentsLoading ? 5 : myAppointmentsVM.patientArrivedAppointmentsHistoryList.isNotEmpty - ? myAppointmentsVM.patientArrivedAppointmentsHistoryList.length - : 1, + ? myAppointmentsVM.patientArrivedAppointmentsHistoryList.length + : 1, itemBuilder: (context, index) { return myAppointmentsVM.isMyAppointmentsLoading ? const MoviesShimmerWidget().paddingSymmetrical(24.h, 0.h) : myAppointmentsVM.patientArrivedAppointmentsHistoryList.isNotEmpty - ? AnimationConfiguration.staggeredList( - position: index, - duration: const Duration(milliseconds: 500), - child: SlideAnimation( - verticalOffset: 100.0, - child: FadeInAnimation( - child: AnimatedContainer( - duration: Duration(milliseconds: 300), - curve: Curves.easeInOut, - margin: EdgeInsets.symmetric(vertical: 8.h), - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), - child: AppointmentCard( - patientAppointmentHistoryResponseModel: myAppointmentsVM.patientArrivedAppointmentsHistoryList[index], - myAppointmentsViewModel: myAppointmentsViewModel, - ), - ).paddingSymmetrical(24.h, 0.h), - ), - ), - ) - : Utils.getNoDataWidget(context); + ? AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: AnimatedContainer( + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + margin: EdgeInsets.symmetric(vertical: 8.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), + child: AppointmentCard( + patientAppointmentHistoryResponseModel: myAppointmentsVM.patientArrivedAppointmentsHistoryList[index], + myAppointmentsViewModel: myAppointmentsViewModel, + ), + ).paddingSymmetrical(24.h, 0.h), + ), + ), + ) + : Utils.getNoDataWidget(context); }, separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), ), From 8c5506415804a1c786def3f1e30423b0838c63fb Mon Sep 17 00:00:00 2001 From: aamir-csol Date: Wed, 10 Sep 2025 16:42:22 +0300 Subject: [PATCH 08/20] signup completed --- lib/core/api_consts.dart | 1 + lib/core/app_state.dart | 1 + lib/core/utils/request_utils.dart | 50 ++-- .../authentication/authentication_repo.dart | 36 ++- .../authentication_view_model.dart | 85 ++++--- .../registration_payload_model.dart | 238 ++++++++++++++---- lib/presentation/authentication/register.dart | 2 +- .../authentication/register_step2.dart | 6 +- 8 files changed, 318 insertions(+), 101 deletions(-) diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index daa094e..a603cf9 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -804,6 +804,7 @@ class ApiConsts { static final String insertPatientMobileData = 'Services/MobileNotifications.svc/REST/Insert_PatientMobileDeviceInfo'; static final String getPrivileges = 'Services/Patients.svc/REST/Service_Privilege'; + static final String registerUser = 'Services/Authentication.svc/REST/PatientRegistration'; // static values for Api static final double appVersionID = 18.7; diff --git a/lib/core/app_state.dart b/lib/core/app_state.dart index 3c4555e..52500bf 100644 --- a/lib/core/app_state.dart +++ b/lib/core/app_state.dart @@ -5,6 +5,7 @@ import 'package:hmg_patient_app_new/core/common_models/privilege/HMCProjectListM import 'package:hmg_patient_app_new/core/common_models/privilege/PrivilegeModel.dart'; import 'package:hmg_patient_app_new/core/common_models/privilege/ProjectDetailListModel.dart'; import 'package:hmg_patient_app_new/core/common_models/privilege/VidaPlusProjectListModel.dart'; +import 'package:hmg_patient_app_new/features/authentication/models/request_models/send_activation_request_model.dart'; import 'package:hmg_patient_app_new/features/authentication/models/resp_models/authenticated_user_resp_model.dart'; import 'package:hmg_patient_app_new/features/authentication/models/resp_models/check_user_staus_nhic_response_model.dart'; import 'package:hmg_patient_app_new/features/authentication/models/resp_models/select_device_by_imei.dart'; diff --git a/lib/core/utils/request_utils.dart b/lib/core/utils/request_utils.dart index 21b5952..fb6624d 100644 --- a/lib/core/utils/request_utils.dart +++ b/lib/core/utils/request_utils.dart @@ -1,3 +1,5 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:hijri_gregorian_calendar/hijri_gregorian_calendar.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; @@ -63,7 +65,7 @@ class RequestUtils { required String? deviceToken, required bool patientOutSA, required String? loginTokenID, - required var registeredData, + required RegistrationDataModelPayload registeredData, int? patientId, required String nationIdText, required String countryCode, @@ -84,8 +86,8 @@ class RequestUtils { if (registeredData != null) { request.searchType = registeredData.searchType ?? 1; - request.patientID = registeredData.patientID ?? 0; - request.patientIdentificationID = request.nationalID = registeredData.patientIdentificationID ?? '0'; + request.patientID = registeredData.patientId ?? 0; + request.patientIdentificationID = request.nationalID = (registeredData.patientIdentificationId ?? 0); request.dob = registeredData.dob; request.isRegister = registeredData.isRegister; } else { @@ -135,9 +137,10 @@ class RequestUtils { request.patientIdentificationID = request.nationalID = payload["PatientIdentificationID"] ?? '0'; request.dob = payload["DOB"]; request.isRegister = payload["isRegister"]; - request.healthId = _appState.getNHICUserData.healthId; + request.healthId = patientOutSA ? null : _appState.getNHICUserData.healthId; request.isHijri = payload["IsHijri"]; request.deviceToken = _appState.deviceToken; + request.projectOutSA = patientOutSA; } else { request.searchType = isFileNo ? 2 : 1; request.patientID = patientId ?? 0; @@ -176,20 +179,25 @@ class RequestUtils { return request; } - static dynamic getUserSignupCompletionRequest() { + static dynamic getUserSignupCompletionRequest({String? fullName, String? emailAddress, GenderTypeEnum? gender, MaritalStatusTypeEnum? maritalStatus}) { AppState appState = getIt.get(); bool isDubai = appState.getUserRegistrationPayload.patientOutSa == 1 ? true : false; - List names = appState.getUserRegistrationPayload.fullName != null ? appState.getUserRegistrationPayload.fullName!.split(" ") : []; + List names = fullName != null ? fullName.split(" ") : []; var dob = appState.getUserRegistrationPayload.dob; + final DateFormat dateFormat1 = DateFormat('MM/dd/yyyy'); + final DateFormat dateFormat2 = DateFormat('dd/MM/yyyy'); + DateTime gregorianDate = dateFormat2.parse(dob!); + HijriGregDate hijriDate = HijriGregConverter.gregorianToHijri(gregorianDate); + String? date = "${hijriDate.day}/${hijriDate.month}/${hijriDate.year}"; return { "Patientobject": { "TempValue": true, "PatientIdentificationType": - (isDubai ? appState.getNHICUserData.idNumber!.substring(0, 1) : appState.getUserRegistrationPayload.patientIdentificationId?.toString().substring(0, 1)) == "1" ? 1 : 2, - "PatientIdentificationNo": isDubai ? appState.getNHICUserData.idNumber! : appState.getUserRegistrationPayload.patientIdentificationId, + (isDubai ? appState.getUserRegistrationPayload.patientIdentificationId?.toString().substring(0, 1) : appState.getNHICUserData.idNumber!.substring(0, 1)) == "1" ? 1 : 2, + "PatientIdentificationNo": isDubai ? appState.getUserRegistrationPayload.patientIdentificationId.toString() : appState.getNHICUserData.idNumber.toString(), "MobileNumber": appState.getUserRegistrationPayload.patientMobileNumber ?? 0, "PatientOutSA": (appState.getUserRegistrationPayload.zipCode == CountryEnum.saudiArabia.countryCode || appState.getUserRegistrationPayload.zipCode == '+966') ? 0 : 1, "FirstNameN": isDubai ? "..." : appState.getNHICUserData.firstNameAr, @@ -198,19 +206,19 @@ class RequestUtils { "MiddleName": isDubai ? "..." : appState.getNHICUserData.secondNameEn, "LastNameN": isDubai ? "..." : appState.getNHICUserData.lastNameAr, "LastName": isDubai ? (names.length > 1 ? names[1] : "...") : appState.getNHICUserData.lastNameEn, - "StrDateofBirth": dob, + "StrDateofBirth": dateFormat1.format(dateFormat2.parse(dob)), "DateofBirth": DateUtil.convertISODateToJsonDate((dob ?? "").replaceAll('/', '-')), - "Gender": isDubai ? (appState.getUserRegistrationPayload.gender == GenderTypeEnum.male ? 1 : 2) : (appState.getNHICUserData.gender == 'M' ? 1 : 2), - "NationalityID": isDubai ? "UAE" : appState.getUserRegistrationPayload.nationalityCode, + "Gender": isDubai ? (gender == GenderTypeEnum.male ? 1 : 2) : (appState.getNHICUserData.gender == 'M' ? 1 : 2), + "NationalityID": isDubai ? "UAE" : appState.getNHICUserData.nationalityCode, "eHealthIDField": isDubai ? null : appState.getNHICUserData.healthId, - "DateofBirthN": DateTime.now(), - "EmailAddress": appState.getUserRegistrationPayload.emailAddress, - "SourceType": (appState.getUserRegistrationPayload.zipCode == CountryEnum.saudiArabia.countryCode || appState.getUserRegistrationPayload.zipCode == '+966') ? 1 : 2, + "DateofBirthN": date, + "EmailAddress": emailAddress, + "SourceType": (appState.getUserRegistrationPayload.zipCode == CountryEnum.saudiArabia.countryCode || appState.getUserRegistrationPayload.zipCode == '+966') ? "1" : "2", "PreferredLanguage": appState.getLanguageCode() == "ar" ? (isDubai ? "1" : 1) : (isDubai ? "2" : 2), "Marital": isDubai - ? (appState.getUserRegistrationPayload.maritalStatus == MaritalStatusTypeEnum.single + ? (maritalStatus == MaritalStatusTypeEnum.single ? '0' - : appState.getUserRegistrationPayload.maritalStatus == MaritalStatusTypeEnum.married + : maritalStatus == MaritalStatusTypeEnum.married ? '1' : '2') : (appState.getNHICUserData.maritalStatusCode == 'U' @@ -219,12 +227,16 @@ class RequestUtils { ? '1' : '2'), }, - "PatientIdentificationID": isDubai ? appState.getUserRegistrationPayload.patientIdentificationId : appState.getNHICUserData.idNumber, + "PatientIdentificationID": isDubai ? appState.getUserRegistrationPayload.patientIdentificationId.toString() : appState.getNHICUserData.idNumber.toString(), "PatientMobileNumber": appState.getUserRegistrationPayload.patientMobileNumber.toString()[0] == '0' ? appState.getUserRegistrationPayload.patientMobileNumber : '0${appState.getUserRegistrationPayload.patientMobileNumber}', - if (isDubai) "DOB": dob, - if (isDubai) "IsHijri": appState.getUserRegistrationPayload.isHijri, + "DOB": dob, + "IsHijri": appState.getUserRegistrationPayload.isHijri, + "PatientOutSA": (appState.getUserRegistrationPayload.zipCode == CountryEnum.saudiArabia.countryCode || appState.getUserRegistrationPayload.zipCode == '+966') ? 0 : 1, + "isDentalAllowedBackend": appState.getUserRegistrationPayload.isDentalAllowedBackend, + "ZipCode": appState.getUserRegistrationPayload.zipCode, + if (!isDubai) "HealthId": appState.getNHICUserData.healthId, }; } } diff --git a/lib/features/authentication/authentication_repo.dart b/lib/features/authentication/authentication_repo.dart index d81094c..22ca90f 100644 --- a/lib/features/authentication/authentication_repo.dart +++ b/lib/features/authentication/authentication_repo.dart @@ -32,6 +32,8 @@ abstract class AuthenticationRepo { Future>> checkUserStatus({required dynamic commonAuthanticatedRequest}); + Future>> registerUser({required dynamic registrationPayloadDataModelRequest}); + Future>> insertPatientIMEIData({required dynamic patientIMEIDataRequest}); Future>> insertPatientDeviceData({required dynamic patientDeviceDataRequest}); @@ -182,7 +184,6 @@ class AuthenticationRepoImp implements AuthenticationRepo { newRequest.projectOutSA = newRequest.zipCode == '966' ? false : true; newRequest.isDentalAllowedBackend = false; newRequest.forRegisteration = newRequest.isRegister ?? false; - newRequest.isRegister = false; } @@ -357,6 +358,39 @@ class AuthenticationRepoImp implements AuthenticationRepo { } } + @override + Future>> registerUser({required dynamic registrationPayloadDataModelRequest}) async { + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + ApiConsts.registerUser, + body: registrationPayloadDataModelRequest, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: response, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } + @override Future> insertPatientIMEIData({required patientIMEIDataRequest}) { try { diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index 6d5f3c1..7122482 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -107,6 +107,8 @@ class AuthenticationViewModel extends ChangeNotifier { isTermsAccepted = false; selectedCountrySignup = CountryEnum.saudiArabia; pickedCountryByUAEUser = null; + _appState.setUserRegistrationPayload = RegistrationDataModelPayload(); + _appState.setNHICUserData = CheckUserStatusResponseNHIC(); } void onCountryChange(CountryEnum country) { @@ -308,40 +310,27 @@ class AuthenticationViewModel extends ChangeNotifier { } Future sendActivationCode({required OTPTypeEnum otpTypeEnum, required String nationalIdOrFileNumber, required String phoneNumber, dynamic payload}) async { - bool isForRegister = checkIsUserComingForRegister(request: payload); - bool isPatientOutSA = isPatientOutsideSA(request: payload); - bool isFileNo = isPatientHasFile(request: payload); - var request = RequestUtils.getCommonRequestSendActivationCode( otpTypeEnum: otpTypeEnum, mobileNumber: phoneNumber, selectedLoginType: otpTypeEnum.toInt(), zipCode: selectedCountrySignup.countryCode, nationalId: int.parse(nationalIdOrFileNumber), - isFileNo: isFileNo, + isFileNo: isPatientHasFile(request: payload), patientId: 0, - isForRegister: isForRegister, - patientOutSA: isPatientOutSA, + isForRegister: checkIsUserComingForRegister(request: payload), + patientOutSA: isPatientOutsideSA(request: payload), payload: payload); // TODO: GET APP SMS SIGNATURE HERE request.sMSSignature = "enKTDcqbOVd"; - // GifLoaderDialogUtils.showMyDialog(context); - // bool isForRegister = healthId != null || isDubai; - // if (isForRegister) { - // if (!isDubai) { - // request.dob = dob; //isHijri == 1 ? dob : dateFormat2.format(dateFormat.parse(dob)); - // } - // request.healthId = healthId; - // request.isHijri = calenderType.toInt; - // } else { - // // request.dob = ""; - // // request.healthId = ""; - // // request.isHijri = 0; - // } + if (checkIsUserComingForRegister(request: payload)) { + _appState.setUserRegistrationPayload = RegistrationDataModelPayload.fromJson(payload); + print("====== Demo =========="); + } - final resultEither = await _authenticationRepo.sendActivationCodeRepo(sendActivationCodeReq: request, isRegister: isForRegister, languageID: 'er'); + final resultEither = await _authenticationRepo.sendActivationCodeRepo(sendActivationCodeReq: request, isRegister: checkIsUserComingForRegister(request: payload), languageID: 'er'); resultEither.fold( (failure) async => await _errorHandlerService.handleError(failure: failure), @@ -377,19 +366,29 @@ class AuthenticationViewModel extends ChangeNotifier { phoneNumber: phoneNumberController.text, otpTypeEnum: otpTypeEnum, deviceToken: _appState.deviceToken, - patientOutSA: true, + patientOutSA: _appState.getUserRegistrationPayload.projectOutSa == 1 ? true : false, loginTokenID: _appState.appAuthToken, - registeredData: null, + registeredData: _appState.getUserRegistrationPayload, nationIdText: nationalIdController.text, countryCode: selectedCountrySignup.countryCode, loginType: loginTypeEnum.toInt) .toJson(); - bool isForRegister = (_appState.getUserRegistrationPayload.healthId != null || _appState.getUserRegistrationPayload.patientOutSa == true); + bool isForRegister = (_appState.getUserRegistrationPayload.healthId != null || _appState.getUserRegistrationPayload.patientOutSa == 1) ? true : false; if (isForRegister) { - if (_appState.getUserRegistrationPayload.patientOutSa == true) request['DOB'] = _appState.getUserRegistrationPayload.dob; + if (_appState.getUserRegistrationPayload.patientOutSa == 0) request['DOB'] = _appState.getUserRegistrationPayload.dob; request['HealthId'] = _appState.getUserRegistrationPayload.healthId; request['IsHijri'] = _appState.getUserRegistrationPayload.isHijri; + request["PatientOutSA"] = _appState.getUserRegistrationPayload.projectOutSa == true ? 1 : 0; + request["ForRegisteration"] = _appState.getUserRegistrationPayload.isRegister; + request["isRegister"] = false; + + // if (request.containsKey("OTP_SendType")) { + // request.remove("OTP_SendType"); + // print("====== Demo: Removed OTP_SendType for Register state"); + // } + + print("====== Req"); final resultEither = await _authenticationRepo.checkActivationCodeRepo(newRequest: request, activationCode: activationCode.toString(), isRegister: true); @@ -591,11 +590,27 @@ class AuthenticationViewModel extends ChangeNotifier { } Future onRegistrationComplete() async { - var request = RequestUtils.getUserSignupCompletionRequest(); - clearDefaultInputValues(); + var request = RequestUtils.getUserSignupCompletionRequest(fullName: nameController.text, emailAddress: emailController.text, gender: genderType, maritalStatus: maritalStatus); + // print("============= Final Payload ==============="); print(request); - print("==========================================="); + print("=================== ===================="); + + final resultEither = await _authenticationRepo.registerUser(registrationPayloadDataModelRequest: request); + resultEither.fold((failure) async => await _errorHandlerService.handleError(failure: failure), (apiResponse) async { + if (apiResponse.data is String) { + //TODO: Here We Need to Show a Dialog Of Something in the case of Fail With OK and Cancel and the Display Variable WIll be result. + } else { + print(apiResponse.data as Map); + if (apiResponse.data["MessageStatus"] == 1) { + //TODO: Here We Need to Show a Dialog Of Something in the case of Success. + clearDefaultInputValues(); // This will Clear All Default Values Of User. + _navigationService.pushAndReplace(AppRoutes.loginScreen); + } + } + }); + + return; } Future checkUserStatusForRegistration({required dynamic response, required dynamic request}) async { @@ -616,7 +631,8 @@ class AuthenticationViewModel extends ChangeNotifier { sendActivationCode( otpTypeEnum: OTPTypeEnumExtension.fromInt(request["OTP_SendType"]), nationalIdOrFileNumber: request["PatientIdentificationID"].toString(), - phoneNumber: request["PatientMobileNumber"].toString()); + phoneNumber: request["PatientMobileNumber"].toString(), + payload: request); } else { print("=======IN SA======="); chekUserNHICData(request: request); @@ -630,13 +646,12 @@ class AuthenticationViewModel extends ChangeNotifier { bool isPatientOutsideSA({required dynamic request}) { bool isOutSideSa = false; - if (request is Map && request.containsKey("PatientOutSA")) { - if (request["PatientOutSA"] == true) { - isOutSideSa = true; - } else { - isOutSideSa = false; - } + if (request["PatientOutSA"] == true || request["PatientOutSA"] == 1) { + isOutSideSa = true; + } else { + isOutSideSa = false; } + print(isOutSideSa); return isOutSideSa; } diff --git a/lib/features/authentication/models/request_models/registration_payload_model.dart b/lib/features/authentication/models/request_models/registration_payload_model.dart index 93c409b..bbb31f9 100644 --- a/lib/features/authentication/models/request_models/registration_payload_model.dart +++ b/lib/features/authentication/models/request_models/registration_payload_model.dart @@ -1,20 +1,128 @@ +// import 'dart:convert'; +// +// import 'package:hmg_patient_app_new/core/enums.dart'; +// +// class RegistrationDataModelPayload { +// int? patientMobileNumber; +// String? zipCode; +// int? searchType; +// int? patientId; +// int? patientIdentificationId; +// int? otpSendType; +// int? patientOutSa; +// bool? isDentalAllowedBackend; +// String? dob; +// int? isHijri; +// bool? forRegister; +// bool? isRegister; +// String? healthId; +// String? emailAddress; +// String? nationalityCode; +// GenderTypeEnum? gender; +// MaritalStatusTypeEnum? maritalStatus; +// String? fullName; +// +// RegistrationDataModelPayload({ +// this.patientMobileNumber, +// this.zipCode, +// this.searchType, +// this.patientId, +// this.patientIdentificationId, +// this.otpSendType, +// this.patientOutSa, +// this.isDentalAllowedBackend, +// this.dob, +// this.isHijri, +// this.forRegister, +// this.isRegister, +// this.healthId, +// this.emailAddress, +// this.nationalityCode, +// this.gender, +// this.maritalStatus, +// this.fullName, +// }); +// +// factory RegistrationDataModelPayload.fromRawJson(String str) => RegistrationDataModelPayload.fromJson(json.decode(str)); +// +// String toRawJson() => json.encode(toJson()); +// +// factory RegistrationDataModelPayload.fromJson(Map json) => RegistrationDataModelPayload( +// patientMobileNumber: json["PatientMobileNumber"], +// zipCode: json["ZipCode"], +// searchType: json["SearchType"], +// patientId: json["PatientID"], +// patientIdentificationId: json["PatientIdentificationID"], +// otpSendType: json["OTP_SendType"], +// patientOutSa: json["PatientOutSA"], +// isDentalAllowedBackend: json["isDentalAllowedBackend"], +// dob: json["DOB"], +// isHijri: json["IsHijri"], +// forRegister: json["forRegister"], +// isRegister: json["isRegister"], +// healthId: json["healthId"], +// emailAddress: json["emailAddress"], +// nationalityCode: json["nationalityCode"], +// gender: json["gender"], +// maritalStatus: json["maritalStatus"], +// fullName: json["fullName"], +// ); +// +// Map toJson() => { +// "PatientMobileNumber": patientMobileNumber, +// "ZipCode": zipCode, +// "SearchType": searchType, +// "PatientID": patientId, +// "PatientIdentificationID": patientIdentificationId, +// "OTP_SendType": otpSendType, +// "PatientOutSA": patientOutSa, +// "isDentalAllowedBackend": isDentalAllowedBackend, +// "DOB": dob, +// "IsHijri": isHijri, +// "forRegister": forRegister, +// "isRegister": isRegister, +// "healthId": healthId, +// "emailAddress": emailAddress, +// "nationalityCode": nationalityCode, +// "gender": gender, +// "maritalStatus": maritalStatus, +// "fullName": fullName, +// }; +// } + + import 'dart:convert'; import 'package:hmg_patient_app_new/core/enums.dart'; class RegistrationDataModelPayload { int? patientMobileNumber; + String? mobileNo; + String? deviceToken; + bool? projectOutSa; + int? loginType; String? zipCode; + bool? isRegister; + String? logInTokenId; int? searchType; int? patientId; + int? nationalId; int? patientIdentificationId; int? otpSendType; + String? languageId; + String? versionId; + String? channel; + String? ipAdress; + String? generalid; int? patientOutSa; bool? isDentalAllowedBackend; + String? deviceTypeId; + String? smsSignature; String? dob; int? isHijri; - bool? forRegister; - bool? isRegister; + String? patientType; + String? latitude; + String? longitude; String? healthId; String? emailAddress; String? nationalityCode; @@ -22,19 +130,35 @@ class RegistrationDataModelPayload { MaritalStatusTypeEnum? maritalStatus; String? fullName; + RegistrationDataModelPayload({ this.patientMobileNumber, + this.mobileNo, + this.deviceToken, + this.projectOutSa, + this.loginType, this.zipCode, + this.isRegister, + this.logInTokenId, this.searchType, this.patientId, + this.nationalId, this.patientIdentificationId, this.otpSendType, + this.languageId, + this.versionId, + this.channel, + this.ipAdress, + this.generalid, this.patientOutSa, this.isDentalAllowedBackend, + this.deviceTypeId, + this.smsSignature, this.dob, this.isHijri, - this.forRegister, - this.isRegister, + this.patientType, + this.latitude, + this.longitude, this.healthId, this.emailAddress, this.nationalityCode, @@ -48,44 +172,74 @@ class RegistrationDataModelPayload { String toRawJson() => json.encode(toJson()); factory RegistrationDataModelPayload.fromJson(Map json) => RegistrationDataModelPayload( - patientMobileNumber: json["PatientMobileNumber"], - zipCode: json["ZipCode"], - searchType: json["SearchType"], - patientId: json["PatientID"], - patientIdentificationId: json["PatientIdentificationID"], - otpSendType: json["OTP_SendType"], - patientOutSa: json["PatientOutSA"], - isDentalAllowedBackend: json["isDentalAllowedBackend"], - dob: json["DOB"], - isHijri: json["IsHijri"], - forRegister: json["forRegister"], - isRegister: json["isRegister"], - healthId: json["healthId"], - emailAddress: json["emailAddress"], - nationalityCode: json["nationalityCode"], - gender: json["gender"], - maritalStatus: json["maritalStatus"], - fullName: json["fullName"], - ); + patientMobileNumber: json["PatientMobileNumber"], + mobileNo: json["MobileNo"], + deviceToken: json["DeviceToken"], + projectOutSa: json["ProjectOutSA"], + loginType: json["LoginType"], + zipCode: json["ZipCode"], + isRegister: json["isRegister"], + logInTokenId: json["LogInTokenID"], + searchType: json["SearchType"], + patientId: json["PatientID"], + nationalId: json["NationalID"], + patientIdentificationId: json["PatientIdentificationID"], + otpSendType: json["OTP_SendType"], + languageId: json["LanguageID"], + versionId: json["VersionID"], + channel: json["Channel"], + ipAdress: json["IPAdress"], + generalid: json["generalid"], + patientOutSa: json["PatientOutSA"], + isDentalAllowedBackend: json["isDentalAllowedBackend"], + deviceTypeId: json["DeviceTypeID"], + smsSignature: json["SMSSignature"], + dob: json["DOB"], + isHijri: json["IsHijri"], + patientType: json["PatientType"], + latitude: json["Latitude"], + longitude: json["Longitude"], + healthId: json["healthId"], + emailAddress: json["emailAddress"], + nationalityCode: json["nationalityCode"], + gender: json["gender"], + maritalStatus: json["maritalStatus"], + fullName: json["fullName"], + ); Map toJson() => { - "PatientMobileNumber": patientMobileNumber, - "ZipCode": zipCode, - "SearchType": searchType, - "PatientID": patientId, - "PatientIdentificationID": patientIdentificationId, - "OTP_SendType": otpSendType, - "PatientOutSA": patientOutSa, - "isDentalAllowedBackend": isDentalAllowedBackend, - "DOB": dob, - "IsHijri": isHijri, - "forRegister": forRegister, - "isRegister": isRegister, - "healthId": healthId, - "emailAddress": emailAddress, - "nationalityCode": nationalityCode, - "gender": gender, - "maritalStatus": maritalStatus, - "fullName": fullName, - }; + "PatientMobileNumber": patientMobileNumber, + "MobileNo": mobileNo, + "DeviceToken": deviceToken, + "ProjectOutSA": projectOutSa, + "LoginType": loginType, + "ZipCode": zipCode, + "isRegister": isRegister, + "LogInTokenID": logInTokenId, + "SearchType": searchType, + "PatientID": patientId, + "NationalID": nationalId, + "PatientIdentificationID": patientIdentificationId, + "OTP_SendType": otpSendType, + "LanguageID": languageId, + "VersionID": versionId, + "Channel": channel, + "IPAdress": ipAdress, + "generalid": generalid, + "PatientOutSA": patientOutSa, + "isDentalAllowedBackend": isDentalAllowedBackend, + "DeviceTypeID": deviceTypeId, + "SMSSignature": smsSignature, + "DOB": dob, + "IsHijri": isHijri, + "PatientType": patientType, + "Latitude": latitude, + "Longitude": longitude, + "healthId": healthId, + "emailAddress": emailAddress, + "nationalityCode": nationalityCode, + "gender": gender, + "maritalStatus": maritalStatus, + "fullName": fullName, + }; } diff --git a/lib/presentation/authentication/register.dart b/lib/presentation/authentication/register.dart index f238582..efb0a18 100644 --- a/lib/presentation/authentication/register.dart +++ b/lib/presentation/authentication/register.dart @@ -213,7 +213,7 @@ class _RegisterNew extends State { countryCode: authVM.selectedCountrySignup.countryCode, initialPhoneNumber: "", textController: authVM.phoneNumberController, - isEnableCountryDropdown: true, + isEnableCountryDropdown: false, onCountryChange: authVM.onCountryChange, onChange: authVM.onPhoneNumberChange, buttons: [ diff --git a/lib/presentation/authentication/register_step2.dart b/lib/presentation/authentication/register_step2.dart index da46888..0757420 100644 --- a/lib/presentation/authentication/register_step2.dart +++ b/lib/presentation/authentication/register_step2.dart @@ -217,8 +217,8 @@ class _RegisterNew extends State { TextInputWidget( labelText: LocaleKeys.mobileNumber.tr(), hintText: (appState.getUserRegistrationPayload.patientMobileNumber.toString() ?? ""), - controller: authVM!.phoneNumberController, - isEnable: true, + controller: null, + isEnable: false, prefix: null, isAllowRadius: false, isBorderAllowed: false, @@ -255,7 +255,7 @@ class _RegisterNew extends State { icon: AppAssets.cancel, onPressed: () { Navigator.of(context).pop(); - authVM!.clearDefaultInputValues(); + // authVM!.clearDefaultInputValues(); }, backgroundColor: AppColors.secondaryLightRedColor, borderColor: AppColors.secondaryLightRedColor, From 2b18e08e64acaad24aae84440c7bd5dde77ad077 Mon Sep 17 00:00:00 2001 From: aamir-csol Date: Wed, 10 Sep 2025 16:44:19 +0300 Subject: [PATCH 09/20] signup completed --- lib/presentation/authentication/register_step2.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/presentation/authentication/register_step2.dart b/lib/presentation/authentication/register_step2.dart index 0757420..d7071a4 100644 --- a/lib/presentation/authentication/register_step2.dart +++ b/lib/presentation/authentication/register_step2.dart @@ -234,13 +234,13 @@ class _RegisterNew extends State { labelText: LocaleKeys.dob.tr(), hintText: authVM!.isUserFromUAE() ? appState.getUserRegistrationPayload.dob! : appState.getNHICUserData.dateOfBirth ?? "", controller: authVM!.isUserFromUAE() ? authVM!.dobController : null, - isEnable: authVM!.isUserFromUAE() ? true : false, + isEnable: false, prefix: null, isBorderAllowed: false, isAllowLeadingIcon: true, isReadOnly: true, leadingIcon: AppAssets.birthday_cake, - selectionType: authVM!.isUserFromUAE() ? SelectionTypeEnum.calendar : null, + selectionType: null, ).paddingSymmetrical(0.h, 16.h), ], ), From 70810fa14ca2a66faf81c0dc1094cd931a46d24d Mon Sep 17 00:00:00 2001 From: aamir-csol Date: Wed, 10 Sep 2025 17:01:06 +0300 Subject: [PATCH 10/20] signup completed --- .../authentication_view_model.dart | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index 7122482..0cf38f5 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -15,6 +15,7 @@ import 'package:hmg_patient_app_new/core/utils/loading_utils.dart'; import 'package:hmg_patient_app_new/core/utils/request_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/core/utils/validation_utils.dart'; +import 'package:hmg_patient_app_new/extensions/context_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_repo.dart'; import 'package:hmg_patient_app_new/features/authentication/models/request_models/check_activation_code_register_request_model.dart'; @@ -30,6 +31,7 @@ import 'package:hmg_patient_app_new/services/dialog_service.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; import 'package:hmg_patient_app_new/services/localauth_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; +import 'package:hmg_patient_app_new/widgets/bottomsheet/exception_bottom_sheet.dart'; import 'models/request_models/insert_patient_mobile_deviceinfo.dart'; import 'models/request_models/patient_insert_device_imei_request.dart'; @@ -615,19 +617,31 @@ class AuthenticationViewModel extends ChangeNotifier { Future checkUserStatusForRegistration({required dynamic response, required dynamic request}) async { if (response is Map) { - _appState.setAppAuthToken = response["LogInTokenID"]; if (response["MessageStatus"] == 2) { print(response["ErrorEndUserMessage"]); return; } if (response['hasFile'] == true) { //TODO: Show Here Ok And Cancel Dialog and On OKPress it will go for sendActivationCode + + _navigationService.context?.showBottomSheet( + child: ExceptionBottomSheet( + message: response["ErrorMessage"], + showCancel: true, + showOKButton: true, + onOkPressed: () { + _navigationService.popUntilNamed(AppRoutes.loginScreen); + }, + onCancelPressed: () { + _navigationService.pop(); + }, + )); } else { request['forRegister'] = true; request['isRegister'] = true; + _appState.setAppAuthToken = response['LogInTokenID']; if (isPatientOutsideSA(request: response)) { print("=======OUT SA======="); - _appState.setAppAuthToken = response['LogInTokenID']; sendActivationCode( otpTypeEnum: OTPTypeEnumExtension.fromInt(request["OTP_SendType"]), nationalIdOrFileNumber: request["PatientIdentificationID"].toString(), From 9fbb8c3418948170875aa876b966f46e765cfd96 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Wed, 10 Sep 2025 17:02:03 +0300 Subject: [PATCH 11/20] updates --- .../appointment_details_page.dart | 47 +++++++------- .../appointments/my_appointments_page.dart | 1 - .../insurance/insurance_home_page.dart | 65 ++++++++++--------- 3 files changed, 59 insertions(+), 54 deletions(-) diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart index dee8e40..18b778a 100644 --- a/lib/presentation/appointments/appointment_details_page.dart +++ b/lib/presentation/appointments/appointment_details_page.dart @@ -66,33 +66,34 @@ class _AppointmentDetailsPageState extends State { Expanded( child: CollapsingListView( title: "Appointment Details".needTranslation, + report: () {}, child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - "Appointment Details".needTranslation.toText20(isBold: true), - if (AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)) - CustomButton( - text: "Report".needTranslation, - onPressed: () {}, - backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, - textColor: AppColors.primaryRedColor, - fontSize: 14, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, - iconSize: 16.h, - icon: AppAssets.report_icon, - iconColor: AppColors.primaryRedColor, - ) - ], - ), - SizedBox(height: 24.h), + // Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // "Appointment Details".needTranslation.toText20(isBold: true), + // if (AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)) + // CustomButton( + // text: "Report".needTranslation, + // onPressed: () {}, + // backgroundColor: AppColors.secondaryLightRedColor, + // borderColor: AppColors.secondaryLightRedColor, + // textColor: AppColors.primaryRedColor, + // fontSize: 14, + // fontWeight: FontWeight.w500, + // borderRadius: 12, + // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + // height: 40.h, + // iconSize: 16.h, + // icon: AppAssets.report_icon, + // iconColor: AppColors.primaryRedColor, + // ) + // ], + // ), + // SizedBox(height: 24.h), AppointmentDoctorCard( patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, onAskDoctorTap: () {}, diff --git a/lib/presentation/appointments/my_appointments_page.dart b/lib/presentation/appointments/my_appointments_page.dart index 3ea4399..333c8b1 100644 --- a/lib/presentation/appointments/my_appointments_page.dart +++ b/lib/presentation/appointments/my_appointments_page.dart @@ -97,7 +97,6 @@ class _MyAppointmentsPageState extends State { child: AnimatedContainer( duration: Duration(milliseconds: 300), curve: Curves.easeInOut, - margin: EdgeInsets.symmetric(vertical: 8.h), decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), child: AppointmentCard( patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList[index], diff --git a/lib/presentation/insurance/insurance_home_page.dart b/lib/presentation/insurance/insurance_home_page.dart index f7aaf24..dd63e55 100644 --- a/lib/presentation/insurance/insurance_home_page.dart +++ b/lib/presentation/insurance/insurance_home_page.dart @@ -40,47 +40,52 @@ class _InsuranceHomePageState extends State { @override Widget build(BuildContext context) { - insuranceViewModel = Provider.of(context); + insuranceViewModel = Provider.of(context, listen: false); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( title: "${LocaleKeys.insurance.tr(context: context)} ${LocaleKeys.updateInsurance.tr(context: context)}", + history: () { + insuranceViewModel.setIsInsuranceHistoryLoading(true); + insuranceViewModel.getPatientInsuranceCardHistory(); + showCommonBottomSheet(context, + child: InsuranceHistory(), callBackFunc: (str) {}, title: "", height: ResponsiveExtension.screenHeight * 0.65, isCloseButtonVisible: false, isFullScreen: false); + }, child: SingleChildScrollView( child: Consumer(builder: (context, insuranceVM, child) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - "${LocaleKeys.insurance.tr(context: context)} ${LocaleKeys.updateInsurance.tr(context: context)}".toText24(isBold: true), - CustomButton( - icon: AppAssets.insurance_history_icon, - iconColor: AppColors.primaryRedColor, - iconSize: 21.h, - text: LocaleKeys.history.tr(context: context), - onPressed: () { - insuranceVM.setIsInsuranceHistoryLoading(true); - insuranceVM.getPatientInsuranceCardHistory(); - showCommonBottomSheet(context, - child: InsuranceHistory(), callBackFunc: (str) {}, title: "", height: ResponsiveExtension.screenHeight * 0.65, isCloseButtonVisible: false, isFullScreen: false); - }, - backgroundColor: AppColors.primaryRedColor.withOpacity(0.1), - borderColor: AppColors.primaryRedColor.withOpacity(0.0), - textColor: AppColors.primaryRedColor, - fontSize: 14, - fontWeight: FontWeight.w600, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, - ), - ], - ).paddingSymmetrical(24.h, 24.h), + // Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // "${LocaleKeys.insurance.tr(context: context)} ${LocaleKeys.updateInsurance.tr(context: context)}".toText24(isBold: true), + // CustomButton( + // icon: AppAssets.insurance_history_icon, + // iconColor: AppColors.primaryRedColor, + // iconSize: 21.h, + // text: LocaleKeys.history.tr(context: context), + // onPressed: () { + // }, + // backgroundColor: AppColors.primaryRedColor.withOpacity(0.1), + // borderColor: AppColors.primaryRedColor.withOpacity(0.0), + // textColor: AppColors.primaryRedColor, + // fontSize: 14, + // fontWeight: FontWeight.w600, + // borderRadius: 12, + // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + // height: 40.h, + // ), + // ], + // ).paddingSymmetrical(24.h, 24.h), insuranceVM.isInsuranceLoading ? const MoviesShimmerWidget().paddingSymmetrical(24.h, 0) - : PatientInsuranceCard( - insuranceCardDetailsModel: insuranceVM.patientInsuranceList.first, - isInsuranceExpired: DateTime.now().isAfter(DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo))), + : Padding( + padding: EdgeInsets.only(top: 24.h), + child: PatientInsuranceCard( + insuranceCardDetailsModel: insuranceVM.patientInsuranceList.first, + isInsuranceExpired: DateTime.now().isAfter(DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo))), + ), ], ); }), From d87562caf54f0d2169fabf31cc78df707459473b Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Wed, 10 Sep 2025 17:33:44 +0300 Subject: [PATCH 12/20] updates --- lib/core/utils/request_utils.dart | 2 +- .../authentication_view_model.dart | 27 ++++++++++++------- 2 files changed, 18 insertions(+), 11 deletions(-) diff --git a/lib/core/utils/request_utils.dart b/lib/core/utils/request_utils.dart index fb6624d..fc9815d 100644 --- a/lib/core/utils/request_utils.dart +++ b/lib/core/utils/request_utils.dart @@ -65,7 +65,7 @@ class RequestUtils { required String? deviceToken, required bool patientOutSA, required String? loginTokenID, - required RegistrationDataModelPayload registeredData, + RegistrationDataModelPayload? registeredData, int? patientId, required String nationIdText, required String countryCode, diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index b59fd13..2c6cad8 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -82,7 +82,7 @@ class AuthenticationViewModel extends ChangeNotifier { String errorMsg = ''; final FocusNode myFocusNode = FocusNode(); var healthId; - int getDeviceLastLogin =1; + int getDeviceLastLogin = 1; Future onLoginPressed() async { try { @@ -297,12 +297,11 @@ class AuthenticationViewModel extends ChangeNotifier { } else if (apiResponse.messageStatus == 1) { if (apiResponse.data['isSMSSent']) { _appState.setAppAuthToken = apiResponse.data['LogInTokenID']; - await sendActivationCode( + await sendActivationCode( otpTypeEnum: otpTypeEnum, phoneNumber: phoneNumberController.text, nationalIdOrFileNumber: nationalIdController.text, ); - } else { if (apiResponse.data['IsAuthenticated']) { await checkActivationCode( @@ -310,7 +309,6 @@ class AuthenticationViewModel extends ChangeNotifier { onWrongActivationCode: (String? message) {}, activationCode: null, //todo silent login case halded on the repo itself.. ); - } } } @@ -325,10 +323,13 @@ class AuthenticationViewModel extends ChangeNotifier { selectedLoginType: otpTypeEnum.toInt(), zipCode: selectedCountrySignup.countryCode, nationalId: int.parse(nationalIdOrFileNumber), - isFileNo: isPatientHasFile(request: payload), + // isFileNo: isPatientHasFile(request: payload), + isFileNo: false, patientId: 0, - isForRegister: checkIsUserComingForRegister(request: payload), - patientOutSA: isPatientOutsideSA(request: payload), + isForRegister: false, + // isForRegister: checkIsUserComingForRegister(request: payload), + patientOutSA: selectedCountrySignup.countryCode == CountryEnum.saudiArabia ? false : true, + // patientOutSA: isPatientOutsideSA(request: payload), payload: payload); // TODO: GET APP SMS SIGNATURE HERE @@ -373,19 +374,25 @@ class AuthenticationViewModel extends ChangeNotifier { required OTPTypeEnum otpTypeEnum, required Function(String? message) onWrongActivationCode, }) async { + bool isForRegister = (_appState.getUserRegistrationPayload.healthId != null || _appState.getUserRegistrationPayload.patientOutSa == true); + final request = RequestUtils.getCommonRequestWelcome( phoneNumber: phoneNumberController.text, otpTypeEnum: otpTypeEnum, deviceToken: _appState.deviceToken, - patientOutSA: _appState.getUserRegistrationPayload.projectOutSa == 1 ? true : false, + // patientOutSA: _appState.getUserRegistrationPayload.projectOutSa == 1 ? true : false, + patientOutSA: isForRegister + ? _appState.getUserRegistrationPayload.projectOutSa == 1 + ? true + : false + : _appState.getSelectDeviceByImeiRespModelElement!.outSa!, loginTokenID: _appState.appAuthToken, - registeredData: _appState.getUserRegistrationPayload, + registeredData: isForRegister ? _appState.getUserRegistrationPayload : null, nationIdText: nationalIdController.text, countryCode: selectedCountrySignup.countryCode, loginType: loginTypeEnum.toInt) .toJson(); LoaderBottomSheet.showLoader(); - bool isForRegister = (_appState.getUserRegistrationPayload.healthId != null || _appState.getUserRegistrationPayload.patientOutSa == true); if (isForRegister) { if (_appState.getUserRegistrationPayload.patientOutSa == 0) request['DOB'] = _appState.getUserRegistrationPayload.dob; request['HealthId'] = _appState.getUserRegistrationPayload.healthId; From 5620cc4442c30abc2939ac9ea242821c09e2cc47 Mon Sep 17 00:00:00 2001 From: Haroon Amjad <> Date: Wed, 10 Sep 2025 22:44:44 +0300 Subject: [PATCH 13/20] medical file implementation contd. --- assets/images/svg/alarm_clock_icon.svg | 5 + lib/core/app_assets.dart | 1 + .../my_appointments/my_appointments_repo.dart | 49 ++ .../my_appointments_view_model.dart | 10 + .../appointment_details_page.dart | 4 +- .../appointments/my_appointments_page.dart | 2 +- lib/presentation/home/landing_page.dart | 41 +- .../medical_file/medical_file_page.dart | 506 ++++++++++++------ .../medical_file/widgets/lab_rad_card.dart | 65 +++ .../medical_file_appointment_card.dart | 181 +++++++ 10 files changed, 680 insertions(+), 184 deletions(-) create mode 100644 assets/images/svg/alarm_clock_icon.svg create mode 100644 lib/presentation/medical_file/widgets/lab_rad_card.dart create mode 100644 lib/presentation/medical_file/widgets/medical_file_appointment_card.dart diff --git a/assets/images/svg/alarm_clock_icon.svg b/assets/images/svg/alarm_clock_icon.svg new file mode 100644 index 0000000..50b64bd --- /dev/null +++ b/assets/images/svg/alarm_clock_icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index 02160a2..23945d0 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -99,6 +99,7 @@ class AppAssets { static const String notes_icon = '$svgBasePath/notes_icon.svg'; static const String forward_chevron_icon = '$svgBasePath/forward_chevron_icon.svg'; static const String logout = '$svgBasePath/logout.svg'; + static const String alarm_clock_icon = '$svgBasePath/alarm_clock_icon.svg'; //bottom navigation// static const String homeBottom = '$svgBasePath/home_bottom.svg'; diff --git a/lib/features/my_appointments/my_appointments_repo.dart b/lib/features/my_appointments/my_appointments_repo.dart index 804f9f9..a5528cf 100644 --- a/lib/features/my_appointments/my_appointments_repo.dart +++ b/lib/features/my_appointments/my_appointments_repo.dart @@ -32,6 +32,8 @@ abstract class MyAppointmentsRepo { Future>> sendCheckInNfcRequest( {required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, required String scannedCode, required int checkInType}); + + Future>>> getPatientAppointmentsForTimeLine(); } class MyAppointmentsRepoImp implements MyAppointmentsRepo { @@ -395,4 +397,51 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future>>> getPatientAppointmentsForTimeLine() async { + Map mapDevice = { + "isDentalAllowedBackend": false, + "PatientTypeID": 1, + "IsComingFromCOC": false, + "PatientType": 1, + "IsForTimeLine": true, + }; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + GET_PATIENT_APPOINTMENT_HISTORY, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['AppoimentAllHistoryResultList']; + // if (list == null || list.isEmpty) { + // throw Exception("Appointments list is empty"); + // } + + final appointmentsList = list.map((item) => PatientAppointmentHistoryResponseModel.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: appointmentsList, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } } diff --git a/lib/features/my_appointments/my_appointments_view_model.dart b/lib/features/my_appointments/my_appointments_view_model.dart index 4a921da..d3121af 100644 --- a/lib/features/my_appointments/my_appointments_view_model.dart +++ b/lib/features/my_appointments/my_appointments_view_model.dart @@ -12,12 +12,15 @@ class MyAppointmentsViewModel extends ChangeNotifier { bool isMyAppointmentsLoading = false; bool isAppointmentPatientShareLoading = false; + bool isTimeLineAppointmentsLoading = false; List patientAppointmentsHistoryList = []; List patientUpcomingAppointmentsHistoryList = []; List patientArrivedAppointmentsHistoryList = []; + List patientTimelineAppointmentsList = []; + PatientAppointmentShareResponseModel? patientAppointmentShareResponseModel; MyAppointmentsViewModel({required this.myAppointmentsRepo, required this.errorHandlerService}); @@ -31,8 +34,10 @@ class MyAppointmentsViewModel extends ChangeNotifier { patientAppointmentsHistoryList.clear(); patientUpcomingAppointmentsHistoryList.clear(); patientArrivedAppointmentsHistoryList.clear(); + patientTimelineAppointmentsList.clear(); isMyAppointmentsLoading = true; isAppointmentPatientShareLoading = true; + isTimeLineAppointmentsLoading = true; notifyListeners(); } @@ -46,6 +51,11 @@ class MyAppointmentsViewModel extends ChangeNotifier { notifyListeners(); } + setIsPatientTimeLineAppointmentLoading(bool val) { + isTimeLineAppointmentsLoading = val; + notifyListeners(); + } + setAppointmentReminder(bool value, PatientAppointmentHistoryResponseModel item) { int index = patientAppointmentsHistoryList.indexOf(item); if (index != -1) { diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart index 18b778a..ee2a042 100644 --- a/lib/presentation/appointments/appointment_details_page.dart +++ b/lib/presentation/appointments/appointment_details_page.dart @@ -57,8 +57,8 @@ class _AppointmentDetailsPageState extends State { @override Widget build(BuildContext context) { - myAppointmentsViewModel = Provider.of(context); - prescriptionsViewModel = Provider.of(context); + myAppointmentsViewModel = Provider.of(context, listen: false); + prescriptionsViewModel = Provider.of(context, listen: false); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: Column( diff --git a/lib/presentation/appointments/my_appointments_page.dart b/lib/presentation/appointments/my_appointments_page.dart index 333c8b1..5a3205d 100644 --- a/lib/presentation/appointments/my_appointments_page.dart +++ b/lib/presentation/appointments/my_appointments_page.dart @@ -35,7 +35,7 @@ class _MyAppointmentsPageState extends State { @override Widget build(BuildContext context) { - myAppointmentsViewModel = Provider.of(context); + myAppointmentsViewModel = Provider.of(context, listen: false); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index d148b83..b63b404 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -13,6 +13,8 @@ import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/features/habib_wallet/models/habib_wallet_view_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/authentication/quick_login.dart'; import 'package:hmg_patient_app_new/presentation/home/data/landing_page_data.dart'; @@ -43,6 +45,8 @@ class _LandingPageState extends State { late final HabibWalletViewModel habibWalletVM; late AppState appState; + late MyAppointmentsViewModel myAppointmentsViewModel; + late PrescriptionsViewModel prescriptionsViewModel; @override void initState() { @@ -51,13 +55,16 @@ class _LandingPageState extends State { authVM.savePushTokenToAppState(); if (mounted) { authVM.checkLastLoginStatus(() { - showQuickLogin(context); + showQuickLogin(context); }); } scheduleMicrotask(() { if (appState.isAuthenticated) { habibWalletVM.initHabibWalletProvider(); habibWalletVM.getPatientBalanceAmount(); + myAppointmentsViewModel.initAppointmentsViewModel(); + myAppointmentsViewModel.getPatientAppointments(true, false); + prescriptionsViewModel.initPrescriptionsViewModel(); } }); super.initState(); @@ -67,7 +74,8 @@ class _LandingPageState extends State { Widget build(BuildContext context) { appState = getIt.get(); NavigationService navigationService = getIt.get(); - + myAppointmentsViewModel = Provider.of(context, listen: false); + prescriptionsViewModel = Provider.of(context, listen: false); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: SingleChildScrollView( @@ -338,29 +346,22 @@ class _LandingPageState extends State { void showQuickLogin(BuildContext context) { showCommonBottomSheetWithoutHeight( - context, title: "", isCloseButtonVisible: false, - child: - StatefulBuilder( - builder: (context, setState) { - return QuickLogin( - isDone: isDone, - onPressed: () { - // sharedPref.setBool(HAS_ENABLED_QUICK_LOGIN, true); - authVM.loginWithFingerPrintFace((){ - - isDone = true; - setState(() { - + child: StatefulBuilder(builder: (context, setState) { + return QuickLogin( + isDone: isDone, + onPressed: () { + // sharedPref.setBool(HAS_ENABLED_QUICK_LOGIN, true); + authVM.loginWithFingerPrintFace(() { + isDone = true; + setState(() {}); }); - }); - - }, - ); + }, + ); }), - // height: isDone == false ? ResponsiveExtension.screenHeight * 0.5 : ResponsiveExtension.screenHeight * 0.3, + // height: isDone == false ? ResponsiveExtension.screenHeight * 0.5 : ResponsiveExtension.screenHeight * 0.3, isFullScreen: false, callBackFunc: () { isDone = true; diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index 47951c4..4860bb1 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -2,18 +2,25 @@ import 'dart:async'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/appointments/my_appointments_page.dart'; import 'package:hmg_patient_app_new/presentation/insurance/widgets/patient_insurance_card.dart'; +import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/presentation/medical_file/widgets/lab_rad_card.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/widgets/medical_file_card.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; @@ -23,6 +30,8 @@ import 'package:hmg_patient_app_new/widgets/shimmer/movies_shimmer_widget.dart'; import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; import 'package:provider/provider.dart'; +import 'widgets/medical_file_appointment_card.dart'; + class MedicalFilePage extends StatefulWidget { MedicalFilePage({super.key}); @@ -33,6 +42,7 @@ class MedicalFilePage extends StatefulWidget { class _MedicalFilePageState extends State { late InsuranceViewModel insuranceViewModel; late AppState appState; + late MyAppointmentsViewModel myAppointmentsViewModel; int currentIndex = 0; @@ -46,174 +56,174 @@ class _MedicalFilePageState extends State { @override Widget build(BuildContext context) { - insuranceViewModel = Provider.of(context); + insuranceViewModel = Provider.of(context, listen: false); + myAppointmentsViewModel = Provider.of(context, listen: false); appState = getIt.get(); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, - appBar: AppBar( - title: const Text('Appointments'), - backgroundColor: AppColors.bgScaffoldColor, - ), body: SingleChildScrollView( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LocaleKeys.medicalFile.tr(context: context).toText22(isBold: true).paddingSymmetrical(24.h, 0.0), - SizedBox(height: 16.h), - TextInputWidget( - labelText: LocaleKeys.search.tr(context: context), - hintText: "Type any record", - controller: TextEditingController(), - keyboardType: TextInputType.number, - isEnable: true, - prefix: null, - autoFocus: false, - isBorderAllowed: false, - isAllowLeadingIcon: true, - padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 8.h), - leadingIcon: AppAssets.student_card, - ).paddingSymmetrical(24.h, 0.0), - SizedBox(height: 16.h), - Container( - width: double.infinity, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24, - ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Column( + child: Padding( + padding: EdgeInsets.only(top: 80.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.medicalFile.tr(context: context).toText22(isBold: true).paddingSymmetrical(24.h, 0.0), + SizedBox(height: 16.h), + TextInputWidget( + labelText: LocaleKeys.search.tr(context: context), + hintText: "Type any record", + controller: TextEditingController(), + keyboardType: TextInputType.number, + isEnable: true, + prefix: null, + autoFocus: false, + isBorderAllowed: false, + isAllowLeadingIcon: true, + padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 8.h), + leadingIcon: AppAssets.student_card, + ).paddingSymmetrical(24.h, 0.0), + SizedBox(height: 16.h), + Container( + width: double.infinity, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Image.asset( + appState.getAuthenticatedUser()?.gender == 1 ? AppAssets.male_img : AppAssets.femaleImg, + width: 56.h, + height: 56.h, + ), + SizedBox(width: 8.h), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}".toText18(isBold: true), + SizedBox(height: 4.h), + Row( + children: [ + CustomButton( + icon: AppAssets.file_icon, + iconColor: AppColors.blackColor, + iconSize: 12.h, + text: "File no: ${appState.getAuthenticatedUser()!.patientId}", + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 10, + fontWeight: FontWeight.normal, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 30.h, + ), + SizedBox(width: 4.h), + CustomButton( + text: LocaleKeys.verified.tr(context: context), + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 10, + fontWeight: FontWeight.normal, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 30.h, + ), + ], + ), + ], + ) + ], + ), + SizedBox(height: 16.h), + Divider(color: AppColors.dividerColor, height: 1.h), + SizedBox(height: 16.h), + Row( + children: [ + CustomButton( + text: "${appState.getAuthenticatedUser()!.age} Years Old", + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 10, + fontWeight: FontWeight.normal, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 30.h, + ), + SizedBox(width: 4.h), + CustomButton( + icon: AppAssets.blood_icon, + iconColor: AppColors.primaryRedColor, + iconSize: 13.h, + text: "Blood: ${appState.getUserBloodGroup}", + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 10, + fontWeight: FontWeight.normal, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 30.h, + ), + // SizedBox(width: 4.h), + // CustomButton( + // icon: AppAssets.insurance_active_icon, + // iconColor: AppColors.successColor, + // iconSize: 13.h, + // text: "Insurance Active", + // onPressed: () {}, + // backgroundColor: AppColors.bgGreenColor.withOpacity(0.20), + // borderColor: AppColors.bgGreenColor.withOpacity(0.0), + // textColor: AppColors.blackColor, + // fontSize: 10, + // fontWeight: FontWeight.normal, + // borderRadius: 12, + // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + // height: 30.h, + // ), + ], + ), + SizedBox(height: 8.h), + ], + ), + ), + ).paddingSymmetrical(24.h, 0.0), + SizedBox(height: 16.h), + Consumer(builder: (context, medicalFileVM, child) { + return Column( children: [ - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Image.asset( - appState.getAuthenticatedUser()?.gender == 1 ? AppAssets.male_img : AppAssets.femaleImg, - width: 56.h, - height: 56.h, - ), - SizedBox(width: 8.h), - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}".toText18(isBold: true), - SizedBox(height: 4.h), - Row( - children: [ - CustomButton( - icon: AppAssets.file_icon, - iconColor: AppColors.blackColor, - iconSize: 12.h, - text: "File no: ${appState.getAuthenticatedUser()!.patientId}", - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.normal, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - SizedBox(width: 4.h), - CustomButton( - text: LocaleKeys.verified.tr(context: context), - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.normal, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - ], - ), - ], - ) + CustomTabBar( + activeTextColor: Color(0xffED1C2B), + activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), + tabs: [ + CustomTabBarModel(AppAssets.myFilesBottom, LocaleKeys.general.tr(context: context).needTranslation), + CustomTabBarModel(AppAssets.insurance, LocaleKeys.insurance.tr(context: context)), + CustomTabBarModel(AppAssets.requests, LocaleKeys.request.tr(context: context).needTranslation), + CustomTabBarModel(AppAssets.more, "More".needTranslation), ], - ), - SizedBox(height: 16.h), - Divider(color: AppColors.dividerColor, height: 1.h), - SizedBox(height: 16.h), - Row( - children: [ - CustomButton( - text: "${appState.getAuthenticatedUser()!.age} Years Old", - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.normal, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - SizedBox(width: 4.h), - CustomButton( - icon: AppAssets.blood_icon, - iconColor: AppColors.primaryRedColor, - iconSize: 13.h, - text: "Blood: ${appState.getUserBloodGroup}", - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.normal, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - // SizedBox(width: 4.h), - // CustomButton( - // icon: AppAssets.insurance_active_icon, - // iconColor: AppColors.successColor, - // iconSize: 13.h, - // text: "Insurance Active", - // onPressed: () {}, - // backgroundColor: AppColors.bgGreenColor.withOpacity(0.20), - // borderColor: AppColors.bgGreenColor.withOpacity(0.0), - // textColor: AppColors.blackColor, - // fontSize: 10, - // fontWeight: FontWeight.normal, - // borderRadius: 12, - // padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - // height: 30.h, - // ), - ], - ), - SizedBox(height: 8.h), + onTabChange: (index) { + print(index); + medicalFileVM.onTabChanged(index); + }, + ).paddingSymmetrical(24.h, 0.0), + SizedBox(height: 24.h), + getSelectedTabData(medicalFileVM.selectedTabIndex), ], - ), - ), - ).paddingSymmetrical(24.h, 0.0), - SizedBox(height: 16.h), - Consumer(builder: (context, medicalFileVM, child) { - return Column( - children: [ - CustomTabBar( - activeTextColor: Color(0xffED1C2B), - activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), - tabs: [ - CustomTabBarModel(AppAssets.myFilesBottom, LocaleKeys.general.tr(context: context).needTranslation), - CustomTabBarModel(AppAssets.insurance, LocaleKeys.insurance.tr(context: context)), - CustomTabBarModel(AppAssets.requests, LocaleKeys.request.tr(context: context).needTranslation), - CustomTabBarModel(AppAssets.more, "More".needTranslation), - ], - onTabChange: (index) { - print(index); - medicalFileVM.onTabChanged(index); - }, - ).paddingSymmetrical(24.h, 0.0), - SizedBox(height: 24.h), - getSelectedTabData(medicalFileVM.selectedTabIndex), - ], - ); - }), - ], + ); + }), + ], + ), ), ), ); @@ -224,6 +234,7 @@ class _MedicalFilePageState extends State { case 0: //General Tab Data return Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -244,6 +255,179 @@ class _MedicalFilePageState extends State { ), ); }), + Consumer(builder: (context, myAppointmentsVM, child) { + return SizedBox( + height: 200.h, + child: ListView.separated( + scrollDirection: Axis.horizontal, + padding: EdgeInsets.only(top: 16.h, left: 24.h, right: 24.h, bottom: 0.h), + shrinkWrap: true, + itemCount: myAppointmentsVM.isMyAppointmentsLoading ? 5 : myAppointmentsVM.patientAppointmentsHistoryList.length, + itemBuilder: (context, index) { + return AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + horizontalOffset: 100.0, + child: FadeInAnimation( + child: AnimatedContainer( + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + child: myAppointmentsVM.isMyAppointmentsLoading + ? MedicalFileAppointmentCard( + patientAppointmentHistoryResponseModel: PatientAppointmentHistoryResponseModel(), + myAppointmentsViewModel: myAppointmentsVM, + ) + : MedicalFileAppointmentCard( + patientAppointmentHistoryResponseModel: myAppointmentsVM.patientAppointmentsHistoryList[index], + myAppointmentsViewModel: myAppointmentsViewModel, + ), + ), + ), + ), + ); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 12.h), + ), + ); + }), + SizedBox(height: 24.h), + "Lab & Radiology".needTranslation.toText18(isBold: true).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 16.h), + Row( + children: [ + Expanded( + child: LabRadCard( + icon: AppAssets.lab_result_icon, + labelText: LocaleKeys.labResults.tr(context: context), + labOrderTests: ["Complete blood count", "Creatinine", "Blood Sugar"], + ), + ), + SizedBox(width: 16.h), + Expanded( + child: LabRadCard( + icon: AppAssets.radiology_icon, + labelText: LocaleKeys.radiology.tr(context: context), + labOrderTests: ["Chest X-ray", "Abdominal Ultrasound", "Dental X-ray"], + ), + ), + ], + ).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 24.h), + "Active Medications & Prescriptions".needTranslation.toText18(isBold: true).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 16.h), + Consumer(builder: (context, prescriptionVM, child) { + return prescriptionVM.isPrescriptionsOrdersLoading + ? const MoviesShimmerWidget() + : Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: Colors.white, + borderRadius: 20.h, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + children: [ + ListView.separated( + itemCount: prescriptionVM.patientPrescriptionOrders.length, + shrinkWrap: true, + padding: const EdgeInsets.only(left: 0, right: 8), + physics: NeverScrollableScrollPhysics(), + itemBuilder: (context, index) { + return AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: Row( + children: [ + Utils.buildSvgWithAssets( + icon: AppAssets.prescription_item_icon, + width: 40.h, + height: 40.h, + ), + SizedBox(width: 8.h), + Row( + mainAxisSize: MainAxisSize.max, + children: [ + Column( + children: [ + // SizedBox(width: 150.h, child: prescriptionVM.prescriptionDetailsList[index].itemDescription!.toText12(isBold: true, maxLine: 1)), + // SizedBox( + // width: 150.h, + // child: + // "Prescribed By: ${widget.patientAppointmentHistoryResponseModel.doctorTitle} ${widget.patientAppointmentHistoryResponseModel.doctorNameObj}" + // .needTranslation + // .toText10(weight: FontWeight.w500, color: AppColors.greyTextColor, letterSpacing: -0.4), + // ), + ], + ), + SizedBox(width: 68.h), + Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon, + iconColor: AppColors.blackColor, + width: 18.h, + height: 13.h, + fit: BoxFit.contain, + ), + ], + ), + ], + ), + ), + ), + ); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + ).onPress(() { + prescriptionVM.setPrescriptionsDetailsLoading(); + // Navigator.of(context).push( + // FadePage( + // page: PrescriptionDetailPage(prescriptionsResponseModel: getPrescriptionRequestModel()), + // ), + // ); + }), + SizedBox(height: 16.h), + const Divider(color: AppColors.dividerColor), + SizedBox(height: 16.h), + Row( + children: [ + Expanded( + child: CustomButton( + text: "All Prescriptions".needTranslation, + onPressed: () { + // Navigator.of(context) + // .push( + // FadePage( + // page: PrescriptionsListPage(), + // ), + // ) + // .then((val) { + // prescriptionsViewModel.setPrescriptionsDetailsLoading(); + // prescriptionsViewModel.getPrescriptionDetails(getPrescriptionRequestModel()); + // }); + }, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12.h, + height: 40.h, + icon: AppAssets.requests, + iconColor: AppColors.primaryRedColor, + iconSize: 16.h, + ), + ), + ], + ), + ], + ), + ), + ).paddingSymmetrical(24.h, 0.h); + }), + SizedBox(height: 24.h), ], ); case 1: diff --git a/lib/presentation/medical_file/widgets/lab_rad_card.dart b/lib/presentation/medical_file/widgets/lab_rad_card.dart new file mode 100644 index 0000000..aa53b2f --- /dev/null +++ b/lib/presentation/medical_file/widgets/lab_rad_card.dart @@ -0,0 +1,65 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; + +class LabRadCard extends StatelessWidget { + LabRadCard({super.key, required this.icon, required this.labelText, required this.labOrderTests}); + + String icon; + String labelText; + List labOrderTests = []; + + @override + Widget build(BuildContext context) { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: false), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Utils.buildSvgWithAssets( + icon: icon, + width: 40.h, + height: 40.h, + fit: BoxFit.contain, + ).toShimmer2(isShow: false, radius: 12.h), + SizedBox(width: 8.h), + labelText.toText14(isBold: true).toShimmer2(isShow: false, radius: 6.h, height: 32.h), + ], + ), + SizedBox(height: 16.h), + ListView.separated( + scrollDirection: Axis.vertical, + padding: EdgeInsets.zero, + physics: NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemBuilder: (cxt, index) { + return labOrderTests[index].toText12(isBold: true, maxLine: 1).toShimmer2(isShow: false, radius: 6.h, height: 24.h, width: 120.h); + }, + separatorBuilder: (cxt, index) => SizedBox(height: 8.h), + itemCount: labOrderTests.length, + ), + SizedBox(height: 16.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox.shrink(), + Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon, + width: 15.h, + height: 15.h, + fit: BoxFit.contain, + iconColor: AppColors.textColor + ).toShimmer2(isShow: false, radius: 12.h), + ], + ) + ], + ).paddingAll(16.h), + ); + } +} diff --git a/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart b/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart new file mode 100644 index 0000000..48cc328 --- /dev/null +++ b/lib/presentation/medical_file/widgets/medical_file_appointment_card.dart @@ -0,0 +1,181 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/utils/appointment_type.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/appointments/appointment_details_page.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; + +class MedicalFileAppointmentCard extends StatelessWidget { + MedicalFileAppointmentCard({super.key, required this.patientAppointmentHistoryResponseModel, required this.myAppointmentsViewModel}); + + PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel; + MyAppointmentsViewModel myAppointmentsViewModel; + + @override + Widget build(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + CustomButton( + text: DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false), + onPressed: () {}, + backgroundColor: AppointmentType.isArrived(patientAppointmentHistoryResponseModel) ? AppColors.greyColor : AppColors.secondaryLightRedColor, + borderColor: AppointmentType.isArrived(patientAppointmentHistoryResponseModel) ? AppColors.greyLightColor : AppColors.secondaryLightRedColor, + textColor: AppointmentType.isArrived(patientAppointmentHistoryResponseModel) ? AppColors.textColor : AppColors.primaryRedColor, + fontSize: 12, + fontWeight: FontWeight.w500, + borderRadius: 12.h, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + icon: AppointmentType.isArrived(patientAppointmentHistoryResponseModel) ? AppAssets.appointment_calendar_icon : AppAssets.alarm_clock_icon, + iconColor: AppointmentType.isArrived(patientAppointmentHistoryResponseModel) ? AppColors.textColor : AppColors.primaryRedColor, + iconSize: 16.h, + ).toShimmer2(isShow: myAppointmentsViewModel.isMyAppointmentsLoading), + SizedBox(height: 16.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), + width: 200.h, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Image.network( + patientAppointmentHistoryResponseModel.doctorImageURL ?? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png", + width: 25.h, + height: 27.h, + fit: BoxFit.fill, + ).circle(100).toShimmer2(isShow: myAppointmentsViewModel.isMyAppointmentsLoading), + SizedBox(width: 8.h), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + (patientAppointmentHistoryResponseModel.doctorNameObj ?? "").toText14(isBold: true, maxlines: 1).toShimmer2(isShow: myAppointmentsViewModel.isMyAppointmentsLoading), + (patientAppointmentHistoryResponseModel.clinicName ?? "") + .toText12(maxLine: 1, fontWeight: FontWeight.w500, color: AppColors.greyTextColor) + .toShimmer2(isShow: myAppointmentsViewModel.isMyAppointmentsLoading), + ], + ), + ), + ], + ), + SizedBox(height: 12.h), + Row( + children: [ + myAppointmentsViewModel.isMyAppointmentsLoading + ? Container().toShimmer2(isShow: true, height: 40.h, width: 100.h, radius: 12.h) + : Expanded( + flex: 6, + child: AppointmentType.isArrived(patientAppointmentHistoryResponseModel) + ? getArrivedAppointmentButton(context).toShimmer2(isShow: myAppointmentsViewModel.isMyAppointmentsLoading) + : CustomButton( + text: AppointmentType.getNextActionText(patientAppointmentHistoryResponseModel.nextAction), + onPressed: () { + Navigator.of(context) + .push(FadePage( + page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel), + )) + .then((val) { + // widget.myAppointmentsViewModel.initAppointmentsViewModel(); + // widget.myAppointmentsViewModel.getPatientAppointments(true, false); + }); + }, + backgroundColor: AppointmentType.getNextActionButtonColor(patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.15), + borderColor: AppointmentType.getNextActionButtonColor(patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.01), + textColor: AppointmentType.getNextActionTextColor(patientAppointmentHistoryResponseModel.nextAction), + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + icon: AppointmentType.getNextActionIcon(patientAppointmentHistoryResponseModel.nextAction), + iconColor: AppointmentType.getNextActionTextColor(patientAppointmentHistoryResponseModel.nextAction), + iconSize: 14.h, + ).toShimmer2(isShow: myAppointmentsViewModel.isMyAppointmentsLoading), + ), + SizedBox(width: 8.h), + Expanded( + flex: 2, + child: Container( + height: 40.h, + width: 40.h, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.textColor, + borderRadius: 10.h, + ), + child: Padding( + padding: EdgeInsets.all(10.h), + child: Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon, + width: 10.h, + height: 10.h, + fit: BoxFit.contain, + ), + ), + ).toShimmer2(isShow: myAppointmentsViewModel.isMyAppointmentsLoading).onPress(() { + Navigator.of(context) + .push( + FadePage( + page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel), + ), + ) + .then((val) { + // widget.myAppointmentsViewModel.initAppointmentsViewModel(); + // widget.myAppointmentsViewModel.getPatientAppointments(true, false); + }); + }), + ), + ], + ), + ], + ).paddingAll(16.h), + ), + ], + ); + } + + Widget getArrivedAppointmentButton(BuildContext context) { + return DateTime.now().difference(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate)).inDays <= 15 + ? CustomButton( + text: LocaleKeys.askDoctor.tr(context: context), + onPressed: () {}, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + icon: AppAssets.ask_doctor_icon, + iconColor: AppColors.primaryRedColor, + iconSize: 16.h, + ) + : CustomButton( + text: "Rebook".needTranslation, + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + icon: AppAssets.rebook_appointment_icon, + iconColor: AppColors.blackColor, + iconSize: 16.h, + ); + } +} From 0c58c22ea552eb86dbb036d1a37d7ff1c7af98e0 Mon Sep 17 00:00:00 2001 From: aamir-csol Date: Thu, 11 Sep 2025 09:32:57 +0300 Subject: [PATCH 14/20] sign --- lib/core/api_consts.dart | 2 +- .../authentication_view_model.dart | 102 +++++++++--------- lib/widgets/loader/bottomsheet_loader.dart | 4 +- 3 files changed, 54 insertions(+), 54 deletions(-) diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index bad2e4c..42f10f4 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -726,7 +726,7 @@ const DEACTIVATE_ACCOUNT = 'Services/Patients.svc/REST/PatientAppleActivation_In class ApiConsts { static const maxSmallScreen = 660; - static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.prod; + static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.uat; // static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index 95f821f..2327c37 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -82,7 +82,7 @@ class AuthenticationViewModel extends ChangeNotifier { String errorMsg = ''; final FocusNode myFocusNode = FocusNode(); var healthId; - int getDeviceLastLogin =1; + int getDeviceLastLogin = 1; Future onLoginPressed() async { try { @@ -297,12 +297,7 @@ class AuthenticationViewModel extends ChangeNotifier { } else if (apiResponse.messageStatus == 1) { if (apiResponse.data['isSMSSent']) { _appState.setAppAuthToken = apiResponse.data['LogInTokenID']; - await sendActivationCode( - otpTypeEnum: otpTypeEnum, - phoneNumber: phoneNumberController.text, - nationalIdOrFileNumber: nationalIdController.text, - ); - + await sendActivationCode(otpTypeEnum: otpTypeEnum, phoneNumber: phoneNumberController.text, nationalIdOrFileNumber: nationalIdController.text, isForRegister: false); } else { if (apiResponse.data['IsAuthenticated']) { await checkActivationCode( @@ -310,7 +305,6 @@ class AuthenticationViewModel extends ChangeNotifier { onWrongActivationCode: (String? message) {}, activationCode: null, //todo silent login case halded on the repo itself.. ); - } } } @@ -318,18 +312,23 @@ class AuthenticationViewModel extends ChangeNotifier { ); } - Future sendActivationCode({required OTPTypeEnum otpTypeEnum, required String nationalIdOrFileNumber, required String phoneNumber, dynamic payload}) async { + Future sendActivationCode({required OTPTypeEnum otpTypeEnum, required String nationalIdOrFileNumber, required String phoneNumber, required bool isForRegister, dynamic payload}) async { var request = RequestUtils.getCommonRequestSendActivationCode( - otpTypeEnum: otpTypeEnum, - mobileNumber: phoneNumber, - selectedLoginType: otpTypeEnum.toInt(), - zipCode: selectedCountrySignup.countryCode, - nationalId: int.parse(nationalIdOrFileNumber), - isFileNo: isPatientHasFile(request: payload), - patientId: 0, - isForRegister: checkIsUserComingForRegister(request: payload), - patientOutSA: isPatientOutsideSA(request: payload), - payload: payload); + otpTypeEnum: otpTypeEnum, + mobileNumber: phoneNumber, + selectedLoginType: otpTypeEnum.toInt(), + zipCode: selectedCountrySignup.countryCode, + nationalId: int.parse(nationalIdOrFileNumber), + isFileNo: isForRegister ? isPatientHasFile(request: payload) : false, + patientId: 0, + isForRegister: isForRegister, + patientOutSA: isForRegister + ? isPatientOutsideSA(request: payload) + : selectedCountrySignup.countryCode == CountryEnum.saudiArabia + ? false + : true, + payload: payload, + ); // TODO: GET APP SMS SIGNATURE HERE request.sMSSignature = "enKTDcqbOVd"; @@ -384,7 +383,7 @@ class AuthenticationViewModel extends ChangeNotifier { countryCode: selectedCountrySignup.countryCode, loginType: loginTypeEnum.toInt) .toJson(); - LoaderBottomSheet.showLoader(); + LoaderBottomSheet.showLoader(); bool isForRegister = (_appState.getUserRegistrationPayload.healthId != null || _appState.getUserRegistrationPayload.patientOutSa == true); if (isForRegister) { if (_appState.getUserRegistrationPayload.patientOutSa == 0) request['DOB'] = _appState.getUserRegistrationPayload.dob; @@ -425,7 +424,6 @@ class AuthenticationViewModel extends ChangeNotifier { resultEither.fold((failure) async => await _errorHandlerService.handleError(failure: failure), (apiResponse) async { final activation = CheckActivationCode.fromJson(apiResponse.data as Map); - if (activation.errorCode == '699') { // Todo: Hide Loader // GifLoaderDialogUtils.hideDialog(context); @@ -434,7 +432,7 @@ class AuthenticationViewModel extends ChangeNotifier { return; } else if (activation.messageStatus == 2) { - LoaderBottomSheet.hideLoader(); + LoaderBottomSheet.hideLoader(); onWrongActivationCode(activation.errorEndUserMessage); return; } else if (_appState.getUserRegistrationPayload.isRegister == true) { @@ -443,7 +441,6 @@ class AuthenticationViewModel extends ChangeNotifier { // Navigator.popUntil(context, (route) => Utils.route(route, equalsTo: RegisterNew)); return; } else { - if (activation.list != null && activation.list!.isNotEmpty) { _appState.setAuthenticatedUser(activation.list!.first); } @@ -453,7 +450,7 @@ class AuthenticationViewModel extends ChangeNotifier { bool isUserAgreedBefore = await checkIfUserAgreedBefore(request: request); //updating the last login type in app state to show the fingerprint/face id option on home screen - if( _appState.getSelectDeviceByImeiRespModelElement !=null) { + if (_appState.getSelectDeviceByImeiRespModelElement != null) { _appState.getSelectDeviceByImeiRespModelElement!.logInType = loginTypeEnum.toInt; } LoaderBottomSheet.hideLoader(); @@ -551,12 +548,12 @@ class AuthenticationViewModel extends ChangeNotifier { loginTypeEnum = (_appState.deviceTypeID == 1 ? LoginTypeEnum.face : LoginTypeEnum.fingerprint); if (!_appState.isAuthenticated) { //commenting this api to check either the same flow working or not because this api does not needed further if work fine we will remove this - // await getPatientDeviceData(loginTypeEnum.toInt); + // await getPatientDeviceData(loginTypeEnum.toInt); await checkActivationCode(otpTypeEnum: OTPTypeEnum.faceIDFingerprint, activationCode: null, onWrongActivationCode: (String? message) {}); - await insertPatientIMEIData(loginTypeEnum.toInt); + await insertPatientIMEIData(loginTypeEnum.toInt); } else { // authenticated = true; - await insertPatientIMEIData(loginTypeEnum.toInt); + await insertPatientIMEIData(loginTypeEnum.toInt); } LoaderBottomSheet.hideLoader(); notifyListeners(); @@ -669,10 +666,12 @@ class AuthenticationViewModel extends ChangeNotifier { if (isPatientOutsideSA(request: response)) { print("=======OUT SA======="); sendActivationCode( - otpTypeEnum: OTPTypeEnumExtension.fromInt(request["OTP_SendType"]), - nationalIdOrFileNumber: request["PatientIdentificationID"].toString(), - phoneNumber: request["PatientMobileNumber"].toString(), - payload: request); + otpTypeEnum: OTPTypeEnumExtension.fromInt(request["OTP_SendType"]), + nationalIdOrFileNumber: request["PatientIdentificationID"].toString(), + phoneNumber: request["PatientMobileNumber"].toString(), + payload: request, + isForRegister: true, + ); } else { print("=======IN SA======="); chekUserNHICData(request: request); @@ -710,10 +709,12 @@ class AuthenticationViewModel extends ChangeNotifier { if (apiResponse.data is Map) { setNHICData(apiResponse.data, request); sendActivationCode( - otpTypeEnum: OTPTypeEnumExtension.fromInt(request["OTP_SendType"]), - nationalIdOrFileNumber: request["PatientIdentificationID"].toString(), - phoneNumber: request["PatientMobileNumber"].toString(), - payload: request); + otpTypeEnum: OTPTypeEnumExtension.fromInt(request["OTP_SendType"]), + nationalIdOrFileNumber: request["PatientIdentificationID"].toString(), + phoneNumber: request["PatientMobileNumber"].toString(), + payload: request, + isForRegister: true, + ); } }); @@ -813,33 +814,30 @@ class AuthenticationViewModel extends ChangeNotifier { log("Insert IMEI Failed"); } }); - } - Future getPatientDeviceData(int loginType) async { final resultEither = await _authenticationRepo.getPatientDeviceData( patientDeviceDataRequest: GetUserMobileDeviceData( - deviceToken: _appState.deviceToken, - deviceTypeId: _appState.getDeviceTypeID(), - patientId: _appState.getSelectDeviceByImeiRespModelElement!.patientId!, - patientType: _appState.getSelectDeviceByImeiRespModelElement!.patientType, - patientOutSa:_appState.getSelectDeviceByImeiRespModelElement!.outSa == true ?1 :0, - loginType: loginType, - languageId: _appState.getLanguageID(), - latitude: _appState.userLat, - longitude: _appState.userLong, - mobileNo:_appState.getSelectDeviceByImeiRespModelElement!.mobile! , - patientMobileNumber:int.parse(_appState.getSelectDeviceByImeiRespModelElement!.mobile!), - nationalId:_appState.getSelectDeviceByImeiRespModelElement!.identificationNo) + deviceToken: _appState.deviceToken, + deviceTypeId: _appState.getDeviceTypeID(), + patientId: _appState.getSelectDeviceByImeiRespModelElement!.patientId!, + patientType: _appState.getSelectDeviceByImeiRespModelElement!.patientType, + patientOutSa: _appState.getSelectDeviceByImeiRespModelElement!.outSa == true ? 1 : 0, + loginType: loginType, + languageId: _appState.getLanguageID(), + latitude: _appState.userLat, + longitude: _appState.userLong, + mobileNo: _appState.getSelectDeviceByImeiRespModelElement!.mobile!, + patientMobileNumber: int.parse(_appState.getSelectDeviceByImeiRespModelElement!.mobile!), + nationalId: _appState.getSelectDeviceByImeiRespModelElement!.identificationNo) .toJson()); resultEither.fold((failure) async => await _errorHandlerService.handleError(failure: failure), (apiResponse) async { if (apiResponse.messageStatus == 1) { - dynamic deviceInfo= apiResponse.data['List_MobileLoginInfo']; - getDeviceLastLogin = deviceInfo['LoginType']; + dynamic deviceInfo = apiResponse.data['List_MobileLoginInfo']; + getDeviceLastLogin = deviceInfo['LoginType']; } }); - } @override diff --git a/lib/widgets/loader/bottomsheet_loader.dart b/lib/widgets/loader/bottomsheet_loader.dart index 61f7f43..57dfa01 100644 --- a/lib/widgets/loader/bottomsheet_loader.dart +++ b/lib/widgets/loader/bottomsheet_loader.dart @@ -1,6 +1,8 @@ import 'package:flutter/material.dart'; import 'package:get_it/get_it.dart'; +import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; @@ -15,7 +17,7 @@ class LoaderBottomSheet { showModalBottomSheet( context: _navService.navigatorKey.currentContext!, - isDismissible: false, + isDismissible: ApiConsts.appEnvironmentType == AppEnvironmentTypeEnum.uat ? true : false, enableDrag: false, backgroundColor: Colors.transparent, builder: (_) { From 7c8d7b67a998012f49662ac7cc92b92436eac689 Mon Sep 17 00:00:00 2001 From: aamir-csol Date: Thu, 11 Sep 2025 10:45:26 +0300 Subject: [PATCH 15/20] signup finalize --- lib/core/utils/request_utils.dart | 11 +++++++++-- .../authentication/authentication_view_model.dart | 10 ++++++---- 2 files changed, 15 insertions(+), 6 deletions(-) diff --git a/lib/core/utils/request_utils.dart b/lib/core/utils/request_utils.dart index fc9815d..00bfc94 100644 --- a/lib/core/utils/request_utils.dart +++ b/lib/core/utils/request_utils.dart @@ -65,7 +65,7 @@ class RequestUtils { required String? deviceToken, required bool patientOutSA, required String? loginTokenID, - RegistrationDataModelPayload? registeredData, + RegistrationDataModelPayload? registeredData, int? patientId, required String nationIdText, required String countryCode, @@ -85,7 +85,12 @@ class RequestUtils { request.logInTokenID = loginTokenID ?? ""; if (registeredData != null) { - request.searchType = registeredData.searchType ?? 1; + //TODO: Issue Here if Not Signup + request.searchType = registeredData.searchType != null + ? registeredData.searchType + : fileNo + ? 1 + : 2; request.patientID = registeredData.patientId ?? 0; request.patientIdentificationID = request.nationalID = (registeredData.patientIdentificationId ?? 0); request.dob = registeredData.dob; @@ -95,9 +100,11 @@ class RequestUtils { request.patientID = patientId ?? int.parse(nationIdText); request.patientIdentificationID = request.nationalID = 0; request.searchType = 2; + //TODO: Issue HEre is Not Login } else { request.patientID = 0; request.searchType = 1; + //TODO: Issue HEre is Not Login request.patientIdentificationID = request.nationalID = (nationIdText.isNotEmpty ? int.parse(nationIdText) : 0); } request.isRegister = false; diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index ed7cb60..b2464f6 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -383,7 +383,11 @@ class AuthenticationViewModel extends ChangeNotifier { ? _appState.getUserRegistrationPayload.projectOutSa == 1 ? true : false - : _appState.getSelectDeviceByImeiRespModelElement!.outSa!, + : _appState.getSelectDeviceByImeiRespModelElement != null + ? _appState.getSelectDeviceByImeiRespModelElement!.outSa! + : selectedCountrySignup == CountryEnum.saudiArabia + ? false + : true, loginTokenID: _appState.appAuthToken, registeredData: isForRegister ? _appState.getUserRegistrationPayload : null, nationIdText: nationalIdController.text, @@ -438,7 +442,7 @@ class AuthenticationViewModel extends ChangeNotifier { return; } else if (activation.messageStatus == 2) { - LoaderBottomSheet.hideLoader(); + LoaderBottomSheet.hideLoader(); onWrongActivationCode(activation.errorEndUserMessage); return; } else if (_appState.getUserRegistrationPayload.isRegister == true) { @@ -820,10 +824,8 @@ class AuthenticationViewModel extends ChangeNotifier { log("Insert IMEI Failed"); } }); - } - Future getPatientDeviceData(int loginType) async { final resultEither = await _authenticationRepo.getPatientDeviceData( patientDeviceDataRequest: GetUserMobileDeviceData( From 151351c0bf3d17a27f2911f9ddd79334e0df64b3 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Thu, 11 Sep 2025 10:45:35 +0300 Subject: [PATCH 16/20] medical file updates --- assets/images/svg/all_medications_icon.svg | 5 + lib/core/app_assets.dart | 1 + .../lab/collapsing_list_view.dart | 8 +- .../medical_file/medical_file_page.dart | 112 ++++++++++-------- 4 files changed, 74 insertions(+), 52 deletions(-) create mode 100644 assets/images/svg/all_medications_icon.svg diff --git a/assets/images/svg/all_medications_icon.svg b/assets/images/svg/all_medications_icon.svg new file mode 100644 index 0000000..3d3449b --- /dev/null +++ b/assets/images/svg/all_medications_icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index 23945d0..f0d0254 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -100,6 +100,7 @@ class AppAssets { static const String forward_chevron_icon = '$svgBasePath/forward_chevron_icon.svg'; static const String logout = '$svgBasePath/logout.svg'; static const String alarm_clock_icon = '$svgBasePath/alarm_clock_icon.svg'; + static const String all_medications_icon = '$svgBasePath/all_medications_icon.svg'; //bottom navigation// static const String homeBottom = '$svgBasePath/home_bottom.svg'; diff --git a/lib/presentation/lab/collapsing_list_view.dart b/lib/presentation/lab/collapsing_list_view.dart index c7f1540..f84872d 100644 --- a/lib/presentation/lab/collapsing_list_view.dart +++ b/lib/presentation/lab/collapsing_list_view.dart @@ -21,8 +21,9 @@ class CollapsingListView extends StatelessWidget { VoidCallback? history; Widget? bottomChild; bool isClose; + bool isLeading; - CollapsingListView({required this.title, this.child, this.search, this.isClose = false, this.bottomChild, this.report, this.logout, this.history}); + CollapsingListView({required this.title, this.child, this.search, this.isClose = false, this.bottomChild, this.report, this.logout, this.history, this.isLeading = true}); @override Widget build(BuildContext context) { @@ -33,17 +34,18 @@ class CollapsingListView extends StatelessWidget { CustomScrollView( slivers: [ SliverAppBar( + automaticallyImplyLeading: false, pinned: true, expandedHeight: 100, stretch: true, systemOverlayStyle: SystemUiOverlayStyle(statusBarBrightness: Brightness.light), surfaceTintColor: Colors.transparent, backgroundColor: AppColors.bgScaffoldColor, - leading: IconButton( + leading: isLeading ? IconButton( icon: Utils.buildSvgWithAssets(icon: isClose ? AppAssets.closeBottomNav : AppAssets.arrow_back, width: 32.h, height: 32.h), padding: EdgeInsets.only(left: 12), onPressed: () => Navigator.pop(context), - ), + ) : SizedBox.shrink(), flexibleSpace: LayoutBuilder( builder: (context, constraints) { final double maxHeight = 100; diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index 4860bb1..37fce64 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -22,14 +22,17 @@ import 'package:hmg_patient_app_new/presentation/insurance/widgets/patient_insur import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/widgets/lab_rad_card.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/widgets/medical_file_card.dart'; +import 'package:hmg_patient_app_new/presentation/prescriptions/prescriptions_list_page.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; import 'package:hmg_patient_app_new/widgets/input_widget.dart'; import 'package:hmg_patient_app_new/widgets/shimmer/movies_shimmer_widget.dart'; import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; import 'package:provider/provider.dart'; +import '../prescriptions/prescription_detail_page.dart'; import 'widgets/medical_file_appointment_card.dart'; class MedicalFilePage extends StatefulWidget { @@ -61,13 +64,13 @@ class _MedicalFilePageState extends State { appState = getIt.get(); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, - body: SingleChildScrollView( - child: Padding( - padding: EdgeInsets.only(top: 80.0), + body: CollapsingListView( + title: LocaleKeys.medicalFile.tr(context: context), + isLeading: false, + child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - LocaleKeys.medicalFile.tr(context: context).toText22(isBold: true).paddingSymmetrical(24.h, 0.0), SizedBox(height: 16.h), TextInputWidget( labelText: LocaleKeys.search.tr(context: context), @@ -342,37 +345,36 @@ class _MedicalFilePageState extends State { child: FadeInAnimation( child: Row( children: [ - Utils.buildSvgWithAssets( - icon: AppAssets.prescription_item_icon, - width: 40.h, - height: 40.h, - ), - SizedBox(width: 8.h), - Row( - mainAxisSize: MainAxisSize.max, - children: [ - Column( - children: [ - // SizedBox(width: 150.h, child: prescriptionVM.prescriptionDetailsList[index].itemDescription!.toText12(isBold: true, maxLine: 1)), - // SizedBox( - // width: 150.h, - // child: - // "Prescribed By: ${widget.patientAppointmentHistoryResponseModel.doctorTitle} ${widget.patientAppointmentHistoryResponseModel.doctorNameObj}" - // .needTranslation - // .toText10(weight: FontWeight.w500, color: AppColors.greyTextColor, letterSpacing: -0.4), - // ), - ], - ), - SizedBox(width: 68.h), - Utils.buildSvgWithAssets( - icon: AppAssets.forward_arrow_icon, - iconColor: AppColors.blackColor, - width: 18.h, - height: 13.h, - fit: BoxFit.contain, - ), - ], + Image.network( + prescriptionVM.patientPrescriptionOrders[index].doctorImageURL!, + width: 63.h, + height: 63.h, + fit: BoxFit.fill, + ).circle(100), + SizedBox(width: 16.h), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + prescriptionVM.patientPrescriptionOrders[index].doctorName!.toText16(isBold: true), + SizedBox(height: 4.h), + Wrap( + direction: Axis.horizontal, + spacing: 3.h, + runSpacing: 4.h, + children: [ + AppCustomChipWidget(labelText: prescriptionVM.patientPrescriptionOrders[index].clinicDescription!), + AppCustomChipWidget( + icon: AppAssets.doctor_calendar_icon, + labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(prescriptionVM.patientPrescriptionOrders[index].appointmentDate), false), + ), + ], + ), + ], + ), ), + SizedBox(width: 40.h), + Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, width: 15.h, height: 15.h, fit: BoxFit.contain, iconColor: AppColors.textColor), ], ), ), @@ -382,11 +384,11 @@ class _MedicalFilePageState extends State { separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), ).onPress(() { prescriptionVM.setPrescriptionsDetailsLoading(); - // Navigator.of(context).push( - // FadePage( - // page: PrescriptionDetailPage(prescriptionsResponseModel: getPrescriptionRequestModel()), - // ), - // ); + Navigator.of(context).push( + FadePage( + page: PrescriptionDetailPage(prescriptionsResponseModel: prescriptionVM.patientPrescriptionOrders[index]), + ), + ); }), SizedBox(height: 16.h), const Divider(color: AppColors.dividerColor), @@ -397,16 +399,11 @@ class _MedicalFilePageState extends State { child: CustomButton( text: "All Prescriptions".needTranslation, onPressed: () { - // Navigator.of(context) - // .push( - // FadePage( - // page: PrescriptionsListPage(), - // ), - // ) - // .then((val) { - // prescriptionsViewModel.setPrescriptionsDetailsLoading(); - // prescriptionsViewModel.getPrescriptionDetails(getPrescriptionRequestModel()); - // }); + Navigator.of(context).push( + FadePage( + page: PrescriptionsListPage(), + ), + ); }, backgroundColor: AppColors.secondaryLightRedColor, borderColor: AppColors.secondaryLightRedColor, @@ -420,6 +417,23 @@ class _MedicalFilePageState extends State { iconSize: 16.h, ), ), + SizedBox(width: 10.h), + Expanded( + child: CustomButton( + text: "All Medications".needTranslation, + onPressed: () {}, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12.h, + height: 40.h, + icon: AppAssets.all_medications_icon, + iconColor: AppColors.primaryRedColor, + iconSize: 16.h, + ), + ), ], ), ], From b240293064970fc2da3a891bf6ada7e9fe9db6fb Mon Sep 17 00:00:00 2001 From: Haroon Amjad <> Date: Thu, 11 Sep 2025 23:18:53 +0300 Subject: [PATCH 17/20] medical file implementation contd. --- assets/images/svg/allergy_info_icon.svg | 5 + assets/images/svg/vaccine_info_icon.svg | 4 + lib/core/app_assets.dart | 2 + lib/core/dependencies.dart | 21 +- .../{models => }/habib_wallet_repo.dart | 0 .../{models => }/habib_wallet_view_model.dart | 2 +- .../medical_file/medical_file_repo.dart | 59 ++++++ .../medical_file/medical_file_view_model.dart | 46 ++++- .../patient_vaccine_response_model.dart | 160 ++++++++++++++++ .../my_appointments/my_appointments_repo.dart | 47 +++++ .../my_appointments_view_model.dart | 30 +++ lib/main.dart | 7 +- .../appointments/my_doctors_page.dart | 160 ++++++++++++++++ .../habib_wallet/habib_wallet_page.dart | 2 +- lib/presentation/home/landing_page.dart | 3 +- .../home/widgets/habib_wallet_card.dart | 2 +- lib/presentation/lab/lab_orders_page.dart | 3 +- .../medical_file/medical_file_page.dart | 134 ++++++++++++- .../medical_file/vaccine_list_page.dart | 181 ++++++++++++++++++ .../widgets/medical_file_card.dart | 2 +- lib/services/error_handler_service.dart | 2 +- 21 files changed, 859 insertions(+), 13 deletions(-) create mode 100644 assets/images/svg/allergy_info_icon.svg create mode 100644 assets/images/svg/vaccine_info_icon.svg rename lib/features/habib_wallet/{models => }/habib_wallet_repo.dart (100%) rename lib/features/habib_wallet/{models => }/habib_wallet_view_model.dart (93%) create mode 100644 lib/features/medical_file/medical_file_repo.dart create mode 100644 lib/features/medical_file/models/patient_vaccine_response_model.dart create mode 100644 lib/presentation/appointments/my_doctors_page.dart create mode 100644 lib/presentation/medical_file/vaccine_list_page.dart diff --git a/assets/images/svg/allergy_info_icon.svg b/assets/images/svg/allergy_info_icon.svg new file mode 100644 index 0000000..fc962cc --- /dev/null +++ b/assets/images/svg/allergy_info_icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/svg/vaccine_info_icon.svg b/assets/images/svg/vaccine_info_icon.svg new file mode 100644 index 0000000..d8d22b2 --- /dev/null +++ b/assets/images/svg/vaccine_info_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index f0d0254..c6e6a3c 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -101,6 +101,8 @@ class AppAssets { static const String logout = '$svgBasePath/logout.svg'; static const String alarm_clock_icon = '$svgBasePath/alarm_clock_icon.svg'; static const String all_medications_icon = '$svgBasePath/all_medications_icon.svg'; + static const String allergy_info_icon = '$svgBasePath/allergy_info_icon.svg'; + static const String vaccine_info_icon = '$svgBasePath/vaccine_info_icon.svg'; //bottom navigation// static const String homeBottom = '$svgBasePath/home_bottom.svg'; diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index 0edec4e..3518abe 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -7,12 +7,14 @@ import 'package:hmg_patient_app_new/features/authentication/authentication_repo. import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_repo.dart'; import 'package:hmg_patient_app_new/features/common/common_repo.dart'; -import 'package:hmg_patient_app_new/features/habib_wallet/models/habib_wallet_repo.dart'; -import 'package:hmg_patient_app_new/features/habib_wallet/models/habib_wallet_view_model.dart'; +import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_repo.dart'; +import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart'; import 'package:hmg_patient_app_new/features/insurance/insurance_repo.dart'; import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/lab_repo.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; +import 'package:hmg_patient_app_new/features/medical_file/medical_file_repo.dart'; +import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_repo.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/payfort/payfort_repo.dart'; @@ -88,6 +90,7 @@ class AppDependencies { getIt.registerLazySingleton(() => PayfortRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => LocalAuthService(loggerService: getIt(), localAuth: getIt())); getIt.registerLazySingleton(() => HabibWalletRepoImp(loggerService: getIt(), apiClient: getIt())); + getIt.registerLazySingleton(() => MedicalFileRepoImp(loggerService: getIt(), apiClient: getIt())); // ViewModels // Global/shared VMs → LazySingleton @@ -141,6 +144,20 @@ class AppDependencies { ), ); + getIt.registerLazySingleton( + () => HabibWalletViewModel( + habibWalletRepo: getIt(), + errorHandlerService: getIt(), + ), + ); + + getIt.registerLazySingleton( + () => MedicalFileViewModel( + medicalFileRepo: getIt(), + errorHandlerService: getIt(), + ), + ); + getIt.registerLazySingleton( () => AuthenticationViewModel( authenticationRepo: getIt(), cacheService: getIt(), navigationService: getIt(), dialogService: getIt(), appState: getIt(), errorHandlerService: getIt(), localAuthService: getIt()), diff --git a/lib/features/habib_wallet/models/habib_wallet_repo.dart b/lib/features/habib_wallet/habib_wallet_repo.dart similarity index 100% rename from lib/features/habib_wallet/models/habib_wallet_repo.dart rename to lib/features/habib_wallet/habib_wallet_repo.dart diff --git a/lib/features/habib_wallet/models/habib_wallet_view_model.dart b/lib/features/habib_wallet/habib_wallet_view_model.dart similarity index 93% rename from lib/features/habib_wallet/models/habib_wallet_view_model.dart rename to lib/features/habib_wallet/habib_wallet_view_model.dart index 64c6ddf..8e4fc26 100644 --- a/lib/features/habib_wallet/models/habib_wallet_view_model.dart +++ b/lib/features/habib_wallet/habib_wallet_view_model.dart @@ -1,5 +1,5 @@ import 'package:flutter/material.dart'; -import 'package:hmg_patient_app_new/features/habib_wallet/models/habib_wallet_repo.dart'; +import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_repo.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; class HabibWalletViewModel extends ChangeNotifier { diff --git a/lib/features/medical_file/medical_file_repo.dart b/lib/features/medical_file/medical_file_repo.dart new file mode 100644 index 0000000..a635380 --- /dev/null +++ b/lib/features/medical_file/medical_file_repo.dart @@ -0,0 +1,59 @@ +import 'package:dartz/dartz.dart'; +import 'package:hmg_patient_app_new/core/api/api_client.dart'; +import 'package:hmg_patient_app_new/core/api_consts.dart'; +import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; +import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; +import 'package:hmg_patient_app_new/features/medical_file/models/patient_vaccine_response_model.dart'; +import 'package:hmg_patient_app_new/services/logger_service.dart'; + +abstract class MedicalFileRepo { + Future>> getPatientVaccinesList(); +} + +class MedicalFileRepoImp implements MedicalFileRepo { + final ApiClient apiClient; + final LoggerService loggerService; + + MedicalFileRepoImp({required this.loggerService, required this.apiClient}); + + @override + Future>>> getPatientVaccinesList() async { + Map mapDevice = {"To": "0", "From": "0"}; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + GET_VACCINES, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['List_DoneVaccines']; + // if (list == null || list.isEmpty) { + // throw Exception("lab list is empty"); + // } + + final vaccinesList = list.map((item) => PatientVaccineResponseModel.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: vaccinesList, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } +} diff --git a/lib/features/medical_file/medical_file_view_model.dart b/lib/features/medical_file/medical_file_view_model.dart index 8c00fe4..1d6245a 100644 --- a/lib/features/medical_file/medical_file_view_model.dart +++ b/lib/features/medical_file/medical_file_view_model.dart @@ -1,12 +1,56 @@ import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/features/medical_file/medical_file_repo.dart'; +import 'package:hmg_patient_app_new/features/medical_file/models/patient_vaccine_response_model.dart'; +import 'package:hmg_patient_app_new/services/error_handler_service.dart'; class MedicalFileViewModel extends ChangeNotifier { - int selectedTabIndex = 0; + bool isPatientVaccineListLoading = false; + + MedicalFileRepo medicalFileRepo; + ErrorHandlerService errorHandlerService; + + List patientVaccineList = []; + + MedicalFileViewModel({required this.medicalFileRepo, required this.errorHandlerService}); + + initMedicalFileProvider() { + isPatientVaccineListLoading = true; + notifyListeners(); + } + + setIsPatientVaccineListLoading(bool isLoading) { + isPatientVaccineListLoading = isLoading; + notifyListeners(); + } void onTabChanged(int index) { selectedTabIndex = index; notifyListeners(); } + Future getPatientVaccinesList({Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await medicalFileRepo.getPatientVaccinesList(); + + result.fold( + (failure) async => await errorHandlerService.handleError( + failure: failure, + onOkPressed: () { + onError!(failure.message); + }, + ), + (apiResponse) { + if (apiResponse.messageStatus == 2) { + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + patientVaccineList = apiResponse.data!; + isPatientVaccineListLoading = false; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } } diff --git a/lib/features/medical_file/models/patient_vaccine_response_model.dart b/lib/features/medical_file/models/patient_vaccine_response_model.dart new file mode 100644 index 0000000..ff27b06 --- /dev/null +++ b/lib/features/medical_file/models/patient_vaccine_response_model.dart @@ -0,0 +1,160 @@ +class PatientVaccineResponseModel { + String? setupID; + int? projectID; + int? patientID; + int? invoiceNo; + String? procedureID; + String? vaccineName; + Null? vaccineNameN; + String? invoiceDate; + int? doctorID; + int? clinicID; + String? firstName; + String? middleName; + String? lastName; + Null? firstNameN; + Null? middleNameN; + Null? lastNameN; + String? dateofBirth; + int? actualDoctorRate; + String? age; + String? clinicName; + Null? decimalDoctorRate; + Null? doctorImageURL; + String? doctorName; + int? doctorRate; + int? doctorStarsRate; + String? doctorTitle; + int? gender; + Null? genderDescription; + Null? invoiceNoVP; + bool? isActiveDoctorProfile; + bool? isDoctorAllowVedioCall; + bool? isExecludeDoctor; + int? noOfPatientsRate; + String? patientName; + String? projectName; + String? qR; + String? vaccinationDate; + + PatientVaccineResponseModel( + {this.setupID, + this.projectID, + this.patientID, + this.invoiceNo, + this.procedureID, + this.vaccineName, + this.vaccineNameN, + this.invoiceDate, + this.doctorID, + this.clinicID, + this.firstName, + this.middleName, + this.lastName, + this.firstNameN, + this.middleNameN, + this.lastNameN, + this.dateofBirth, + this.actualDoctorRate, + this.age, + this.clinicName, + this.decimalDoctorRate, + this.doctorImageURL, + this.doctorName, + this.doctorRate, + this.doctorStarsRate, + this.doctorTitle, + this.gender, + this.genderDescription, + this.invoiceNoVP, + this.isActiveDoctorProfile, + this.isDoctorAllowVedioCall, + this.isExecludeDoctor, + this.noOfPatientsRate, + this.patientName, + this.projectName, + this.qR, + this.vaccinationDate}); + + PatientVaccineResponseModel.fromJson(Map json) { + setupID = json['SetupID']; + projectID = json['ProjectID']; + patientID = json['PatientID']; + invoiceNo = json['InvoiceNo']; + procedureID = json['ProcedureID']; + vaccineName = json['VaccineName']; + vaccineNameN = json['VaccineNameN']; + invoiceDate = json['InvoiceDate']; + doctorID = json['DoctorID']; + clinicID = json['ClinicID']; + firstName = json['FirstName']; + middleName = json['MiddleName']; + lastName = json['LastName']; + firstNameN = json['FirstNameN']; + middleNameN = json['MiddleNameN']; + lastNameN = json['LastNameN']; + dateofBirth = json['DateofBirth']; + actualDoctorRate = json['ActualDoctorRate']; + age = json['Age']; + clinicName = json['ClinicName']; + decimalDoctorRate = json['DecimalDoctorRate']; + doctorImageURL = json['DoctorImageURL']; + doctorName = json['DoctorName']; + doctorRate = json['DoctorRate']; + doctorStarsRate = json['DoctorStarsRate']; + doctorTitle = json['DoctorTitle']; + gender = json['Gender']; + genderDescription = json['GenderDescription']; + invoiceNoVP = json['InvoiceNo_VP']; + isActiveDoctorProfile = json['IsActiveDoctorProfile']; + isDoctorAllowVedioCall = json['IsDoctorAllowVedioCall']; + isExecludeDoctor = json['IsExecludeDoctor']; + noOfPatientsRate = json['NoOfPatientsRate']; + patientName = json['PatientName']; + projectName = json['ProjectName']; + qR = json['QR']; + vaccinationDate = json['VaccinationDate']; + } + + Map toJson() { + final Map data = new Map(); + data['SetupID'] = this.setupID; + data['ProjectID'] = this.projectID; + data['PatientID'] = this.patientID; + data['InvoiceNo'] = this.invoiceNo; + data['ProcedureID'] = this.procedureID; + data['VaccineName'] = this.vaccineName; + data['VaccineNameN'] = this.vaccineNameN; + data['InvoiceDate'] = this.invoiceDate; + data['DoctorID'] = this.doctorID; + data['ClinicID'] = this.clinicID; + data['FirstName'] = this.firstName; + data['MiddleName'] = this.middleName; + data['LastName'] = this.lastName; + data['FirstNameN'] = this.firstNameN; + data['MiddleNameN'] = this.middleNameN; + data['LastNameN'] = this.lastNameN; + data['DateofBirth'] = this.dateofBirth; + data['ActualDoctorRate'] = this.actualDoctorRate; + data['Age'] = this.age; + data['ClinicName'] = this.clinicName; + data['DecimalDoctorRate'] = this.decimalDoctorRate; + data['DoctorImageURL'] = this.doctorImageURL; + data['DoctorName'] = this.doctorName; + data['DoctorRate'] = this.doctorRate; + data['DoctorStarsRate'] = this.doctorStarsRate; + data['DoctorTitle'] = this.doctorTitle; + data['Gender'] = this.gender; + data['GenderDescription'] = this.genderDescription; + data['InvoiceNo_VP'] = this.invoiceNoVP; + data['IsActiveDoctorProfile'] = this.isActiveDoctorProfile; + data['IsDoctorAllowVedioCall'] = this.isDoctorAllowVedioCall; + data['IsExecludeDoctor'] = this.isExecludeDoctor; + data['NoOfPatientsRate'] = this.noOfPatientsRate; + data['PatientName'] = this.patientName; + data['ProjectName'] = this.projectName; + data['QR'] = this.qR; + data['VaccinationDate'] = this.vaccinationDate; + return data; + } +} diff --git a/lib/features/my_appointments/my_appointments_repo.dart b/lib/features/my_appointments/my_appointments_repo.dart index a5528cf..2035f30 100644 --- a/lib/features/my_appointments/my_appointments_repo.dart +++ b/lib/features/my_appointments/my_appointments_repo.dart @@ -34,6 +34,8 @@ abstract class MyAppointmentsRepo { {required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, required String scannedCode, required int checkInType}); Future>>> getPatientAppointmentsForTimeLine(); + + Future>>> getPatientDoctorsList(); } class MyAppointmentsRepoImp implements MyAppointmentsRepo { @@ -444,4 +446,49 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future>>> getPatientDoctorsList() async { + Map mapDevice = { + "Top": 0, + "beforeDays": 0, + "exludType": 4, + }; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + GET_MY_DOCTOR, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['PatientDoctorAppointmentResultList']; + // if (list == null || list.isEmpty) { + // throw Exception("Appointments list is empty"); + // } + + final appointmentsList = list.map((item) => PatientAppointmentHistoryResponseModel.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: appointmentsList, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } } diff --git a/lib/features/my_appointments/my_appointments_view_model.dart b/lib/features/my_appointments/my_appointments_view_model.dart index d3121af..c6d3499 100644 --- a/lib/features/my_appointments/my_appointments_view_model.dart +++ b/lib/features/my_appointments/my_appointments_view_model.dart @@ -13,6 +13,7 @@ class MyAppointmentsViewModel extends ChangeNotifier { bool isMyAppointmentsLoading = false; bool isAppointmentPatientShareLoading = false; bool isTimeLineAppointmentsLoading = false; + bool isPatientMyDoctorsLoading = false; List patientAppointmentsHistoryList = []; @@ -21,6 +22,8 @@ class MyAppointmentsViewModel extends ChangeNotifier { List patientTimelineAppointmentsList = []; + List patientMyDoctorsList = []; + PatientAppointmentShareResponseModel? patientAppointmentShareResponseModel; MyAppointmentsViewModel({required this.myAppointmentsRepo, required this.errorHandlerService}); @@ -35,9 +38,11 @@ class MyAppointmentsViewModel extends ChangeNotifier { patientUpcomingAppointmentsHistoryList.clear(); patientArrivedAppointmentsHistoryList.clear(); patientTimelineAppointmentsList.clear(); + patientMyDoctorsList.clear(); isMyAppointmentsLoading = true; isAppointmentPatientShareLoading = true; isTimeLineAppointmentsLoading = true; + isPatientMyDoctorsLoading = true; notifyListeners(); } @@ -56,6 +61,11 @@ class MyAppointmentsViewModel extends ChangeNotifier { notifyListeners(); } + setIsPatientMyDoctorsLoading(bool val) { + isPatientMyDoctorsLoading = val; + notifyListeners(); + } + setAppointmentReminder(bool value, PatientAppointmentHistoryResponseModel item) { int index = patientAppointmentsHistoryList.indexOf(item); if (index != -1) { @@ -263,4 +273,24 @@ class MyAppointmentsViewModel extends ChangeNotifier { }, ); } + + Future getPatientMyDoctors({Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await myAppointmentsRepo.getPatientDoctorsList(); + + result.fold( + (failure) async => await errorHandlerService.handleError(failure: failure), + (apiResponse) { + if (apiResponse.messageStatus == 2) { + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + patientMyDoctorsList = apiResponse.data!; + isPatientMyDoctorsLoading = false; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } } diff --git a/lib/main.dart b/lib/main.dart index 2739a6b..8c6e05b 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -9,7 +9,7 @@ import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; -import 'package:hmg_patient_app_new/features/habib_wallet/models/habib_wallet_view_model.dart'; +import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart'; import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; @@ -96,7 +96,10 @@ void main() async { ), ), ChangeNotifierProvider( - create: (_) => MedicalFileViewModel(), + create: (_) => MedicalFileViewModel( + medicalFileRepo: getIt(), + errorHandlerService: getIt(), + ), ), ChangeNotifierProvider( create: (_) => MyAppointmentsViewModel( diff --git a/lib/presentation/appointments/my_doctors_page.dart b/lib/presentation/appointments/my_doctors_page.dart new file mode 100644 index 0000000..89b120c --- /dev/null +++ b/lib/presentation/appointments/my_doctors_page.dart @@ -0,0 +1,160 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:provider/provider.dart'; + +import '../../widgets/chip/app_custom_chip_widget.dart'; + +class MyDoctorsPage extends StatelessWidget { + MyDoctorsPage({super.key}); + + late MyAppointmentsViewModel myAppointmentsViewModel; + + @override + Widget build(BuildContext context) { + myAppointmentsViewModel = Provider.of(context, listen: false); + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: CollapsingListView( + title: LocaleKeys.myDoctor.tr(context: context), + child: SingleChildScrollView( + child: Consumer(builder: (context, myAppointmentsVM, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 16.h), + ListView.separated( + scrollDirection: Axis.vertical, + itemCount: myAppointmentsVM.isPatientMyDoctorsLoading ? 5 : myAppointmentsVM.patientMyDoctorsList.length, + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + padding: EdgeInsets.only(left: 24.h, right: 24.h), + itemBuilder: (context, index) { + return myAppointmentsVM.isPatientMyDoctorsLoading + ? Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: true, + ), + child: Padding( + padding: EdgeInsets.all(14.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Image.network( + "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png", + width: 63.h, + height: 63.h, + fit: BoxFit.fill, + ).circle(100).toShimmer2(isShow: true), + SizedBox(width: 16.h), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Dr John Smith".toText16(isBold: true).toShimmer2(isShow: true), + SizedBox(height: 8.h), + Wrap( + direction: Axis.horizontal, + spacing: 3.h, + runSpacing: 4.h, + children: [ + AppCustomChipWidget(labelText: "").toShimmer2(isShow: true, width: 16.h), + AppCustomChipWidget(labelText: "").toShimmer2(isShow: true, width: 16.h), + ], + ), + ], + ), + ), + ], + ), + ], + ), + ), + ) + : AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 1000), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: true, + ), + child: Padding( + padding: EdgeInsets.all(14.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Image.network( + myAppointmentsVM.patientMyDoctorsList[index].doctorImageURL!, + width: 63.h, + height: 63.h, + fit: BoxFit.fill, + ).circle(100).toShimmer2(isShow: false), + SizedBox(width: 16.h), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + (myAppointmentsVM.patientMyDoctorsList[index].doctorName).toString().toText16(isBold: true).toShimmer2(isShow: false), + SizedBox(height: 8.h), + Wrap( + direction: Axis.horizontal, + spacing: 3.h, + runSpacing: 4.h, + children: [ + AppCustomChipWidget(labelText: myAppointmentsVM.patientMyDoctorsList[index].clinicName).toShimmer2(isShow: false, width: 16.h), + AppCustomChipWidget(labelText: myAppointmentsVM.patientMyDoctorsList[index].projectName).toShimmer2(isShow: false, width: 16.h), + ], + ), + ], + ), + ), + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "".toText16(), + Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, width: 15.h, height: 15.h, fit: BoxFit.contain, iconColor: AppColors.textColor), + ], + ), + ], + ), + ), + ), + ), + ), + ); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + ), + SizedBox(height: 60.h), + ], + ); + }), + ), + ), + ); + } +} diff --git a/lib/presentation/habib_wallet/habib_wallet_page.dart b/lib/presentation/habib_wallet/habib_wallet_page.dart index 632b11d..1d24d05 100644 --- a/lib/presentation/habib_wallet/habib_wallet_page.dart +++ b/lib/presentation/habib_wallet/habib_wallet_page.dart @@ -7,7 +7,7 @@ import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; -import 'package:hmg_patient_app_new/features/habib_wallet/models/habib_wallet_view_model.dart'; +import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/habib_wallet/recharge_wallet_page.dart'; import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.dart'; diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index b63b404..26e5147 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -12,7 +12,7 @@ import 'package:hmg_patient_app_new/extensions/int_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; -import 'package:hmg_patient_app_new/features/habib_wallet/models/habib_wallet_view_model.dart'; +import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; @@ -64,6 +64,7 @@ class _LandingPageState extends State { habibWalletVM.getPatientBalanceAmount(); myAppointmentsViewModel.initAppointmentsViewModel(); myAppointmentsViewModel.getPatientAppointments(true, false); + myAppointmentsViewModel.getPatientMyDoctors(); prescriptionsViewModel.initPrescriptionsViewModel(); } }); diff --git a/lib/presentation/home/widgets/habib_wallet_card.dart b/lib/presentation/home/widgets/habib_wallet_card.dart index 9bb53f5..3509000 100644 --- a/lib/presentation/home/widgets/habib_wallet_card.dart +++ b/lib/presentation/home/widgets/habib_wallet_card.dart @@ -4,7 +4,7 @@ import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; -import 'package:hmg_patient_app_new/features/habib_wallet/models/habib_wallet_view_model.dart'; +import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart'; import 'package:hmg_patient_app_new/presentation/habib_wallet/habib_wallet_page.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; diff --git a/lib/presentation/lab/lab_orders_page.dart b/lib/presentation/lab/lab_orders_page.dart index 2c6a131..520bfec 100644 --- a/lib/presentation/lab/lab_orders_page.dart +++ b/lib/presentation/lab/lab_orders_page.dart @@ -5,6 +5,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; @@ -57,7 +58,7 @@ class _LabOrdersPageState extends State { } }, child: SingleChildScrollView( - padding: EdgeInsets.all(24), + padding: EdgeInsets.all(24.h), physics: NeverScrollableScrollPhysics(), child: Consumer( builder: (context, model, child) { diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index 37fce64..5b40345 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -18,8 +18,10 @@ import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_vie import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/appointments/my_appointments_page.dart'; +import 'package:hmg_patient_app_new/presentation/appointments/my_doctors_page.dart'; import 'package:hmg_patient_app_new/presentation/insurance/widgets/patient_insurance_card.dart'; import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/presentation/medical_file/vaccine_list_page.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/widgets/lab_rad_card.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/widgets/medical_file_card.dart'; import 'package:hmg_patient_app_new/presentation/prescriptions/prescriptions_list_page.dart'; @@ -46,6 +48,7 @@ class _MedicalFilePageState extends State { late InsuranceViewModel insuranceViewModel; late AppState appState; late MyAppointmentsViewModel myAppointmentsViewModel; + late MedicalFileViewModel medicalFileViewModel; int currentIndex = 0; @@ -61,6 +64,7 @@ class _MedicalFilePageState extends State { Widget build(BuildContext context) { insuranceViewModel = Provider.of(context, listen: false); myAppointmentsViewModel = Provider.of(context, listen: false); + medicalFileViewModel = Provider.of(context, listen: false); appState = getIt.get(); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, @@ -321,7 +325,7 @@ class _MedicalFilePageState extends State { SizedBox(height: 16.h), Consumer(builder: (context, prescriptionVM, child) { return prescriptionVM.isPrescriptionsOrdersLoading - ? const MoviesShimmerWidget() + ? const MoviesShimmerWidget().paddingSymmetrical(24.h, 0.h) : Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: Colors.white, @@ -442,6 +446,134 @@ class _MedicalFilePageState extends State { ).paddingSymmetrical(24.h, 0.h); }), SizedBox(height: 24.h), + //My Doctor Section + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + LocaleKeys.myDoctor.tr(context: context).toText18(isBold: true), + Row( + children: [ + LocaleKeys.viewAll.tr().toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), + SizedBox(width: 2.h), + Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h), + ], + ).onPress(() { + myAppointmentsViewModel.setIsPatientMyDoctorsLoading(true); + myAppointmentsViewModel.getPatientMyDoctors(); + Navigator.of(context).push( + FadePage( + page: MyDoctorsPage(), + ), + ); + }), + ], + ).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 16.h), + Consumer(builder: (context, myAppointmentsVM, child) { + return SizedBox( + height: 120.h, + child: ListView.separated( + scrollDirection: Axis.horizontal, + itemCount: myAppointmentsVM.isPatientMyDoctorsLoading ? 5 : myAppointmentsVM.patientMyDoctorsList.length, + shrinkWrap: true, + padding: EdgeInsets.only(left: 24.h, right: 24.h), + itemBuilder: (context, index) { + return myAppointmentsVM.isPatientMyDoctorsLoading + ? SizedBox( + width: 80.h, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Image.network( + "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png", + width: 64.h, + height: 64.h, + fit: BoxFit.fill, + ).circle(100).toShimmer2(isShow: true, radius: 50.h), + SizedBox(height: 8.h), + Expanded( + child: ("Dr. John Smith Smith Smith").toString().toText12(fontWeight: FontWeight.w500, isCenter: true, maxLine: 2).toShimmer2(isShow: true), + ), + ], + ), + ) + : AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 1000), + child: SlideAnimation( + horizontalOffset: 100.0, + child: FadeInAnimation( + child: SizedBox( + width: 80.h, + child: Column( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Image.network( + myAppointmentsVM.patientMyDoctorsList[index].doctorImageURL!, + width: 64.h, + height: 64.h, + fit: BoxFit.fill, + ).circle(100).toShimmer2(isShow: false, radius: 50.h), + SizedBox(height: 8.h), + Expanded( + child: (myAppointmentsVM.patientMyDoctorsList[index].doctorName) + .toString() + .toText12(fontWeight: FontWeight.w500, isCenter: true, maxLine: 2) + .toShimmer2(isShow: false), + ), + ], + ), + ), + ), + ), + ); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(width: 8.h), + ), + ); + }), + SizedBox(height: 24.h), + "Others".needTranslation.toText18(isBold: true).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 16.h), + GridView( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, crossAxisSpacing: 13, mainAxisSpacing: 13), + physics: NeverScrollableScrollPhysics(), + padding: EdgeInsets.zero, + shrinkWrap: true, + children: [ + MedicalFileCard( + label: "Eye Test Results".needTranslation, + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.eye_result_icon, + isLargeText: true, + iconSize: 40.h, + ), + MedicalFileCard( + label: "Allergy Info".needTranslation, + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.allergy_info_icon, + isLargeText: true, + iconSize: 40.h, + ), + MedicalFileCard( + label: "Vaccine Info".needTranslation, + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.vaccine_info_icon, + isLargeText: true, + iconSize: 40.h, + ).onPress(() { + Navigator.of(context).push( + FadePage( + page: VaccineListPage(), + ), + ); + }), + ], + ).paddingSymmetrical(24.h, 0.0), + SizedBox(height: 24.h), ], ); case 1: diff --git a/lib/presentation/medical_file/vaccine_list_page.dart b/lib/presentation/medical_file/vaccine_list_page.dart new file mode 100644 index 0000000..1080902 --- /dev/null +++ b/lib/presentation/medical_file/vaccine_list_page.dart @@ -0,0 +1,181 @@ +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; +import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:provider/provider.dart'; + +import '../../widgets/chip/app_custom_chip_widget.dart'; + +class VaccineListPage extends StatefulWidget { + const VaccineListPage({super.key}); + + @override + State createState() => _VaccineListPageState(); +} + +class _VaccineListPageState extends State { + late MedicalFileViewModel medicalFileViewModel; + + @override + void initState() { + scheduleMicrotask(() { + medicalFileViewModel.setIsPatientVaccineListLoading(true); + medicalFileViewModel.getPatientVaccinesList(onError: (error) { + Navigator.of(context).pop(); + Navigator.of(context).pop(); + }); + }); + super.initState(); + } + + @override + Widget build(BuildContext context) { + medicalFileViewModel = Provider.of(context, listen: false); + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: CollapsingListView( + title: "Vaccine Info", + child: SingleChildScrollView( + child: Consumer(builder: (context, medicalFileVM, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 16.h), + ListView.separated( + scrollDirection: Axis.vertical, + itemCount: medicalFileVM.isPatientVaccineListLoading ? 5 : medicalFileVM.patientVaccineList.length, + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + padding: EdgeInsets.only(left: 24.h, right: 24.h), + itemBuilder: (context, index) { + return medicalFileVM.isPatientVaccineListLoading + ? Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: true, + ), + child: Padding( + padding: EdgeInsets.all(14.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Image.network( + "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png", + width: 63.h, + height: 63.h, + fit: BoxFit.fill, + ).circle(100).toShimmer2(isShow: true), + SizedBox(width: 16.h), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Dr John Smith".toText16(isBold: true).toShimmer2(isShow: true), + SizedBox(height: 8.h), + Wrap( + direction: Axis.horizontal, + spacing: 3.h, + runSpacing: 4.h, + children: [ + AppCustomChipWidget(labelText: "").toShimmer2(isShow: true, width: 16.h), + AppCustomChipWidget(labelText: "").toShimmer2(isShow: true, width: 16.h), + ], + ), + ], + ), + ), + ], + ), + ], + ), + ), + ) + : AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 1000), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: true, + ), + child: Padding( + padding: EdgeInsets.all(14.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Image.network( + // medicalFileVM.patientVaccineList[index].doctorImageURL, + // width: 63.h, + // height: 63.h, + // fit: BoxFit.fill, + // ).circle(100).toShimmer2(isShow: false), + SizedBox(width: 16.h), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + (medicalFileVM.patientVaccineList[index].doctorName).toString().toText16(isBold: true).toShimmer2(isShow: false), + SizedBox(height: 8.h), + Wrap( + direction: Axis.horizontal, + spacing: 3.h, + runSpacing: 4.h, + children: [ + AppCustomChipWidget( + icon: AppAssets.doctor_calendar_icon, + labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(medicalFileVM.patientVaccineList[index].vaccinationDate), false)), + AppCustomChipWidget(labelText: medicalFileVM.patientVaccineList[index].vaccineName).toShimmer2(isShow: false, width: 16.h), + AppCustomChipWidget(labelText: medicalFileVM.patientVaccineList[index].clinicName).toShimmer2(isShow: false, width: 16.h), + AppCustomChipWidget(labelText: medicalFileVM.patientVaccineList[index].projectName).toShimmer2(isShow: false, width: 16.h), + ], + ), + ], + ), + ), + ], + ), + // Row( + // mainAxisAlignment: MainAxisAlignment.spaceBetween, + // children: [ + // "".toText16(), + // Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, width: 15.h, height: 15.h, fit: BoxFit.contain, iconColor: AppColors.textColor), + // ], + // ), + ], + ), + ), + ), + ), + ), + ); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + ), + SizedBox(height: 60.h), + ], + ); + }), + ), + ), + ); + } +} diff --git a/lib/presentation/medical_file/widgets/medical_file_card.dart b/lib/presentation/medical_file/widgets/medical_file_card.dart index 82f38ec..6341225 100644 --- a/lib/presentation/medical_file/widgets/medical_file_card.dart +++ b/lib/presentation/medical_file/widgets/medical_file_card.dart @@ -36,7 +36,7 @@ class MedicalFileCard extends StatelessWidget { children: [ Utils.buildSvgWithAssets(icon: svgIcon, width: iconSize.h, height: iconSize.h, fit: BoxFit.contain), SizedBox(height: 12.h), - isLargeText ? label.toText14(color: textColor, isBold: true) : label.toText11(color: textColor, isBold: true), + isLargeText ? label.toText14(color: textColor, isBold: true, maxlines: 1) : label.toText11(color: textColor, isBold: true, maxLine: 1), ], ), ), diff --git a/lib/services/error_handler_service.dart b/lib/services/error_handler_service.dart index 59a0205..092cf38 100644 --- a/lib/services/error_handler_service.dart +++ b/lib/services/error_handler_service.dart @@ -47,7 +47,7 @@ class ErrorHandlerServiceImp implements ErrorHandlerService { } else { loggerService.errorLogs("Unhandled failure type: $failure"); - await _showDialog(failure, title: "Error"); + await _showDialog(failure, title: "Error", onOkPressed: onOkPressed); } } From de717edfe8004fe5e5c3582b8a17bd731d29d5d8 Mon Sep 17 00:00:00 2001 From: Haroon Amjad <> Date: Fri, 12 Sep 2025 22:15:47 +0300 Subject: [PATCH 18/20] Sickleave implementation done. --- assets/langs/ar-SA.json | 5 +- assets/langs/en-US.json | 5 +- lib/core/api_consts.dart | 2 +- lib/core/utils/utils.dart | 12 + .../medical_file/medical_file_repo.dart | 103 ++++++- .../medical_file/medical_file_view_model.dart | 72 +++++ .../patient_sickleave_response_model.dart | 176 ++++++++++++ lib/generated/locale_keys.g.dart | 14 +- .../authentication/saved_login_screen.dart | 2 +- .../medical_file/medical_file_page.dart | 268 +++++++++++------- .../patient_sickleaves_list_page.dart | 88 ++++++ .../medical_file/vaccine_list_page.dart | 2 +- .../widgets/medical_file_card.dart | 2 +- .../widgets/patient_sick_leave_card.dart | 191 +++++++++++++ pubspec.yaml | 2 + 15 files changed, 822 insertions(+), 122 deletions(-) create mode 100644 lib/features/medical_file/models/patient_sickleave_response_model.dart create mode 100644 lib/presentation/medical_file/patient_sickleaves_list_page.dart create mode 100644 lib/presentation/medical_file/widgets/patient_sick_leave_card.dart diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index 933afa4..bbfa68c 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -811,5 +811,8 @@ "allSet": "جاهز! الآن يمكنك تسجيل الدخول باستخدام Face ID / Biometric أو البصمة", "enableQuickLogin":"تمكين تسجيل الدخول السريع", "enableMsg":"تمكين تسجيل الدخول السريع سيسمح بالتحقق من خلال Face ID / Biometric الخاص بجهازك الحالي", - "notNow": "ليس الآن" + "notNow": "ليس الآن", + "pendingActivation": "في انتظار التنشيط", + "awaitingApproval": "انتظر القبول", + "ready": "جاهز" } \ No newline at end of file diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index 0b29886..e054238 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -807,5 +807,8 @@ "allSet": "All Set! Now you can login with Face ID or Biometric", "enableQuickLogin": "Enable Quick Login", "enableMsg": "Enabling the quick login will verify through your existing device Face ID / Biometric", - "notNow": "Not Now" + "notNow": "Not Now", + "pendingActivation": "Pending Activation", + "awaitingApproval": "Awaiting Approval", + "ready": "Ready" } \ No newline at end of file diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 42f10f4..bad2e4c 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -726,7 +726,7 @@ const DEACTIVATE_ACCOUNT = 'Services/Patients.svc/REST/PatientAppleActivation_In class ApiConsts { static const maxSmallScreen = 660; - static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.uat; + static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.prod; // static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart index a330879..90a810c 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -1,5 +1,7 @@ import 'dart:convert'; import 'dart:developer'; +import 'dart:io'; +import 'dart:typed_data'; import 'package:connectivity_plus/connectivity_plus.dart'; import 'package:crypto/crypto.dart' as crypto; @@ -21,6 +23,7 @@ import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/dialogs/confirm_dialog.dart'; import 'package:hmg_patient_app_new/widgets/loading_dialog.dart'; import 'package:lottie/lottie.dart'; +import 'package:path_provider/path_provider.dart'; import 'package:shared_preferences/shared_preferences.dart'; import 'dart:math' as dartMath; @@ -629,4 +632,13 @@ class Utils { static String getAdvancePaymentTransID(int projectID, int fileNumber) { return '$projectID-$fileNumber-${DateTime.now().millisecondsSinceEpoch}'; } + + static Future createFileFromString(String encodedStr, String ext) async { + Uint8List bytes = base64.decode(encodedStr); + String dir = (await getApplicationDocumentsDirectory()).path; + File file = File("$dir/" + DateTime.now().millisecondsSinceEpoch.toString() + "." + ext); + await file.writeAsBytes(bytes); + return file.path; + } + } diff --git a/lib/features/medical_file/medical_file_repo.dart b/lib/features/medical_file/medical_file_repo.dart index a635380..14be4ae 100644 --- a/lib/features/medical_file/medical_file_repo.dart +++ b/lib/features/medical_file/medical_file_repo.dart @@ -3,11 +3,18 @@ import 'package:hmg_patient_app_new/core/api/api_client.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; +import 'package:hmg_patient_app_new/features/medical_file/models/patient_sickleave_response_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/models/patient_vaccine_response_model.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; +import '../authentication/models/resp_models/authenticated_user_resp_model.dart'; + abstract class MedicalFileRepo { - Future>> getPatientVaccinesList(); + Future>>> getPatientVaccinesList(); + + Future>>> getPatientSickLeavesList(); + + Future>> getPatientSickLeavePDF(PatientSickLeavesResponseModel patientSickLeavesResponseModel, AuthenticatedUser authenticatedUser); } class MedicalFileRepoImp implements MedicalFileRepo { @@ -56,4 +63,98 @@ class MedicalFileRepoImp implements MedicalFileRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future>>> getPatientSickLeavesList() async { + Map mapDevice = {}; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + GET_PATIENT_SICK_LEAVE_STATUS, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['List_SickLeave']; + // if (list == null || list.isEmpty) { + // throw Exception("lab list is empty"); + // } + + final vaccinesList = list.map((item) => PatientSickLeavesResponseModel.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: vaccinesList, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } + + @override + Future> getPatientSickLeavePDF(PatientSickLeavesResponseModel patientSickLeavesResponseModel, AuthenticatedUser authenticatedUser) async { + Map mapDevice = { + "RequestNo": patientSickLeavesResponseModel.requestNo, + "To": authenticatedUser.emailAddress, + "DateofBirth": authenticatedUser.dateofBirth, + "PatientIditificationNum": authenticatedUser.patientIdentificationNo, + "PatientMobileNumber": authenticatedUser.mobileNumber, + "PatientName": "${authenticatedUser.firstName!} ${authenticatedUser.lastName!}", + "ProjectName": patientSickLeavesResponseModel.projectName, + "DoctorName": patientSickLeavesResponseModel.doctorName, + "ProjectID": patientSickLeavesResponseModel.projectID, + "SetupID": patientSickLeavesResponseModel.setupID, + "IsDownload": true, + }; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + SendSickLeaveEmail, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + // final list = response['List_SickLeave']; + // if (list == null || list.isEmpty) { + // throw Exception("lab list is empty"); + // } + + // final vaccinesList = list.map((item) => PatientSickLeavesResponseModel.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: response["Base64Data"], + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } } diff --git a/lib/features/medical_file/medical_file_view_model.dart b/lib/features/medical_file/medical_file_view_model.dart index 1d6245a..7140e0c 100644 --- a/lib/features/medical_file/medical_file_view_model.dart +++ b/lib/features/medical_file/medical_file_view_model.dart @@ -1,16 +1,23 @@ import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/features/authentication/models/resp_models/authenticated_user_resp_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/medical_file_repo.dart'; +import 'package:hmg_patient_app_new/features/medical_file/models/patient_sickleave_response_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/models/patient_vaccine_response_model.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; class MedicalFileViewModel extends ChangeNotifier { int selectedTabIndex = 0; bool isPatientVaccineListLoading = false; + bool isPatientSickLeaveListLoading = false; + bool isPatientSickLeavePDFLoading = false; MedicalFileRepo medicalFileRepo; ErrorHandlerService errorHandlerService; List patientVaccineList = []; + List patientSickLeaveList = []; + + String patientSickLeavePDFBase64 = ""; MedicalFileViewModel({required this.medicalFileRepo, required this.errorHandlerService}); @@ -24,12 +31,26 @@ class MedicalFileViewModel extends ChangeNotifier { notifyListeners(); } + setIsPatientSickLeavePDFLoading(bool isLoading) { + isPatientSickLeavePDFLoading = isLoading; + notifyListeners(); + } + + setIsPatientSickLeaveListLoading(bool val) { + if (val) { + patientSickLeaveList.clear(); + } + isPatientSickLeaveListLoading = val; + notifyListeners(); + } + void onTabChanged(int index) { selectedTabIndex = index; notifyListeners(); } Future getPatientVaccinesList({Function(dynamic)? onSuccess, Function(String)? onError}) async { + patientVaccineList.clear(); final result = await medicalFileRepo.getPatientVaccinesList(); result.fold( @@ -53,4 +74,55 @@ class MedicalFileViewModel extends ChangeNotifier { }, ); } + + Future getPatientSickLeaveList({Function(dynamic)? onSuccess, Function(String)? onError}) async { + patientSickLeaveList.clear(); + final result = await medicalFileRepo.getPatientSickLeavesList(); + + result.fold( + (failure) async => await errorHandlerService.handleError( + failure: failure, + onOkPressed: () { + onError!(failure.message); + }, + ), + (apiResponse) { + if (apiResponse.messageStatus == 2) { + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + patientSickLeaveList = apiResponse.data!; + isPatientSickLeaveListLoading = false; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } + + Future getPatientSickLeavePDF(PatientSickLeavesResponseModel patientSickLeavesResponseModel, AuthenticatedUser authenticatedUser,{Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await medicalFileRepo.getPatientSickLeavePDF(patientSickLeavesResponseModel, authenticatedUser); + + result.fold( + (failure) async => await errorHandlerService.handleError( + failure: failure, + onOkPressed: () { + onError!(failure.message); + }, + ), + (apiResponse) { + if (apiResponse.messageStatus == 2) { + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + patientSickLeavePDFBase64 = apiResponse.data!; + isPatientSickLeaveListLoading = false; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } } diff --git a/lib/features/medical_file/models/patient_sickleave_response_model.dart b/lib/features/medical_file/models/patient_sickleave_response_model.dart new file mode 100644 index 0000000..b0381a8 --- /dev/null +++ b/lib/features/medical_file/models/patient_sickleave_response_model.dart @@ -0,0 +1,176 @@ +class PatientSickLeavesResponseModel { + String? setupID; + int? projectID; + int? patientID; + int? patientType; + int? clinicID; + int? doctorID; + int? requestNo; + String? requestDate; + int? sickLeaveDays; + int? appointmentNo; + int? admissionNo; + dynamic reportDate; + num? actualDoctorRate; + String? appointmentDate; + String? clinicName; + double? decimalDoctorRate; + String? doctorImageURL; + String? doctorName; + num? doctorRate; + num? doctorStarsRate; + String? doctorTitle; + int? employeeID; + String? endDate; + int? gender; + String? genderDescription; + bool? isActiveDoctorProfile; + bool? isDoctorAllowVedioCall; + bool? isExecludeDoctor; + bool? isInOutPatient; + String? isInOutPatientDescription; + String? isInOutPatientDescriptionN; + bool? isLiveCareAppointment; + dynamic medicalDirectorApprovedStatus; + int? noOfPatientsRate; + dynamic patientName; + String? projectName; + String? qR; + List? speciality; + String? startDate; + int? status; + String? strRequestDate; + + PatientSickLeavesResponseModel( + {this.setupID, + this.projectID, + this.patientID, + this.patientType, + this.clinicID, + this.doctorID, + this.requestNo, + this.requestDate, + this.sickLeaveDays, + this.appointmentNo, + this.admissionNo, + this.reportDate, + this.actualDoctorRate, + this.appointmentDate, + this.clinicName, + this.decimalDoctorRate, + this.doctorImageURL, + this.doctorName, + this.doctorRate, + this.doctorStarsRate, + this.doctorTitle, + this.employeeID, + this.endDate, + this.gender, + this.genderDescription, + this.isActiveDoctorProfile, + this.isDoctorAllowVedioCall, + this.isExecludeDoctor, + this.isInOutPatient, + this.isInOutPatientDescription, + this.isInOutPatientDescriptionN, + this.isLiveCareAppointment, + this.medicalDirectorApprovedStatus, + this.noOfPatientsRate, + this.patientName, + this.projectName, + this.qR, + this.speciality, + this.startDate, + this.status, + this.strRequestDate}); + + PatientSickLeavesResponseModel.fromJson(Map json) { + setupID = json['SetupID']; + projectID = json['ProjectID']; + patientID = json['PatientID']; + patientType = json['PatientType']; + clinicID = json['ClinicID']; + doctorID = json['DoctorID']; + requestNo = json['RequestNo']; + requestDate = json['RequestDate']; + sickLeaveDays = json['SickLeaveDays']; + appointmentNo = json['AppointmentNo']; + admissionNo = json['AdmissionNo']; + reportDate = json['ReportDate']; + actualDoctorRate = json['ActualDoctorRate']; + appointmentDate = json['AppointmentDate']; + clinicName = json['ClinicName']; + decimalDoctorRate = json['DecimalDoctorRate']; + doctorImageURL = json['DoctorImageURL']; + doctorName = json['DoctorName']; + doctorRate = json['DoctorRate']; + doctorStarsRate = json['DoctorStarsRate']; + doctorTitle = json['DoctorTitle']; + employeeID = json['EmployeeID']; + endDate = json['EndDate']; + gender = json['Gender']; + genderDescription = json['GenderDescription']; + isActiveDoctorProfile = json['IsActiveDoctorProfile']; + isDoctorAllowVedioCall = json['IsDoctorAllowVedioCall']; + isExecludeDoctor = json['IsExecludeDoctor']; + isInOutPatient = json['IsInOutPatient']; + isInOutPatientDescription = json['IsInOutPatientDescription']; + isInOutPatientDescriptionN = json['IsInOutPatientDescriptionN']; + isLiveCareAppointment = json['IsLiveCareAppointment']; + medicalDirectorApprovedStatus = json['MedicalDirectorApprovedStatus']; + noOfPatientsRate = json['NoOfPatientsRate']; + patientName = json['PatientName']; + projectName = json['ProjectName']; + qR = json['QR']; + speciality = json['Speciality'].cast(); + startDate = json['StartDate']; + status = json['Status']; + strRequestDate = json['StrRequestDate']; + } + + Map toJson() { + final Map data = new Map(); + data['SetupID'] = this.setupID; + data['ProjectID'] = this.projectID; + data['PatientID'] = this.patientID; + data['PatientType'] = this.patientType; + data['ClinicID'] = this.clinicID; + data['DoctorID'] = this.doctorID; + data['RequestNo'] = this.requestNo; + data['RequestDate'] = this.requestDate; + data['SickLeaveDays'] = this.sickLeaveDays; + data['AppointmentNo'] = this.appointmentNo; + data['AdmissionNo'] = this.admissionNo; + data['ReportDate'] = this.reportDate; + data['ActualDoctorRate'] = this.actualDoctorRate; + data['AppointmentDate'] = this.appointmentDate; + data['ClinicName'] = this.clinicName; + data['DecimalDoctorRate'] = this.decimalDoctorRate; + data['DoctorImageURL'] = this.doctorImageURL; + data['DoctorName'] = this.doctorName; + data['DoctorRate'] = this.doctorRate; + data['DoctorStarsRate'] = this.doctorStarsRate; + data['DoctorTitle'] = this.doctorTitle; + data['EmployeeID'] = this.employeeID; + data['EndDate'] = this.endDate; + data['Gender'] = this.gender; + data['GenderDescription'] = this.genderDescription; + data['IsActiveDoctorProfile'] = this.isActiveDoctorProfile; + data['IsDoctorAllowVedioCall'] = this.isDoctorAllowVedioCall; + data['IsExecludeDoctor'] = this.isExecludeDoctor; + data['IsInOutPatient'] = this.isInOutPatient; + data['IsInOutPatientDescription'] = this.isInOutPatientDescription; + data['IsInOutPatientDescriptionN'] = this.isInOutPatientDescriptionN; + data['IsLiveCareAppointment'] = this.isLiveCareAppointment; + data['MedicalDirectorApprovedStatus'] = this.medicalDirectorApprovedStatus; + data['NoOfPatientsRate'] = this.noOfPatientsRate; + data['PatientName'] = this.patientName; + data['ProjectName'] = this.projectName; + data['QR'] = this.qR; + data['Speciality'] = this.speciality; + data['StartDate'] = this.startDate; + data['Status'] = this.status; + data['StrRequestDate'] = this.strRequestDate; + return data; + } +} diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index dc71850..8b7f021 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -780,6 +780,9 @@ abstract class LocaleKeys { static const resultsPending = 'resultsPending'; static const resultsAvailable = 'resultsAvailable'; static const viewReport = 'viewReport'; + static const checkAvailability = 'checkAvailability'; + static const readInstructions = 'readInstructions'; + static const searchLabReport = 'searchLabReport'; static const prescriptionDeliveryError = 'prescriptionDeliveryError'; static const receiveOtpToast = 'receiveOtpToast'; static const enterPhoneNumber = 'enterPhoneNumber'; @@ -802,12 +805,13 @@ abstract class LocaleKeys { static const loginByOTP = 'loginByOTP'; static const guest = 'guest'; static const switchAccount = 'switchAccount'; - static const checkAvailability = 'checkAvailability'; - static const readInstructions = 'readInstructions'; - static const searchLabReport = 'searchLabReport'; - static const lastloginBy = 'lastloginBy'; - static const allSet ='allSet'; + static const lastLoginBy = 'lastLoginBy'; + static const allSet = 'allSet'; static const enableQuickLogin = 'enableQuickLogin'; static const enableMsg = 'enableMsg'; static const notNow = 'notNow'; + static const pendingActivation = 'pendingActivation'; + static const awaitingApproval = 'awaitingApproval'; + static const ready = 'ready'; + } diff --git a/lib/presentation/authentication/saved_login_screen.dart b/lib/presentation/authentication/saved_login_screen.dart index edfc1e7..d711402 100644 --- a/lib/presentation/authentication/saved_login_screen.dart +++ b/lib/presentation/authentication/saved_login_screen.dart @@ -84,7 +84,7 @@ class _SavedLogin extends State { children: [ // Last login info - ("${LocaleKeys.lastloginBy.tr()} ${loginType.displayName}").toText14(isBold: true, color: AppColors.greyTextColor), + ("${LocaleKeys.lastLoginBy.tr()} ${loginType.displayName}").toText14(isBold: true, color: AppColors.greyTextColor), (appState.getSelectDeviceByImeiRespModelElement!.createdOn != null ? DateUtil.getFormattedDate(DateUtil.convertStringToDate(appState.getSelectDeviceByImeiRespModelElement!.createdOn!), "d MMMM, y 'at' HH:mm") : '--') diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index 5b40345..02af055 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -13,17 +13,20 @@ import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; +import 'package:hmg_patient_app_new/features/medical_file/models/patient_sickleave_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/appointments/my_appointments_page.dart'; import 'package:hmg_patient_app_new/presentation/appointments/my_doctors_page.dart'; +import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.dart'; import 'package:hmg_patient_app_new/presentation/insurance/widgets/patient_insurance_card.dart'; import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/vaccine_list_page.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/widgets/lab_rad_card.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/widgets/medical_file_card.dart'; +import 'package:hmg_patient_app_new/presentation/medical_file/widgets/patient_sick_leave_card.dart'; import 'package:hmg_patient_app_new/presentation/prescriptions/prescriptions_list_page.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; @@ -56,6 +59,8 @@ class _MedicalFilePageState extends State { void initState() { scheduleMicrotask(() { insuranceViewModel.initInsuranceProvider(); + medicalFileViewModel.setIsPatientSickLeaveListLoading(true); + medicalFileViewModel.getPatientSickLeaveList(); }); super.initState(); } @@ -326,124 +331,126 @@ class _MedicalFilePageState extends State { Consumer(builder: (context, prescriptionVM, child) { return prescriptionVM.isPrescriptionsOrdersLoading ? const MoviesShimmerWidget().paddingSymmetrical(24.h, 0.h) - : Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: Colors.white, - borderRadius: 20.h, - ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Column( - children: [ - ListView.separated( - itemCount: prescriptionVM.patientPrescriptionOrders.length, - shrinkWrap: true, - padding: const EdgeInsets.only(left: 0, right: 8), - physics: NeverScrollableScrollPhysics(), - itemBuilder: (context, index) { - return AnimationConfiguration.staggeredList( - position: index, - duration: const Duration(milliseconds: 500), - child: SlideAnimation( - verticalOffset: 100.0, - child: FadeInAnimation( - child: Row( - children: [ - Image.network( - prescriptionVM.patientPrescriptionOrders[index].doctorImageURL!, - width: 63.h, - height: 63.h, - fit: BoxFit.fill, - ).circle(100), - SizedBox(width: 16.h), - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - prescriptionVM.patientPrescriptionOrders[index].doctorName!.toText16(isBold: true), - SizedBox(height: 4.h), - Wrap( - direction: Axis.horizontal, - spacing: 3.h, - runSpacing: 4.h, + : prescriptionVM.patientPrescriptionOrders.isNotEmpty + ? Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: Colors.white, + borderRadius: 20.h, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + children: [ + ListView.separated( + itemCount: prescriptionVM.patientPrescriptionOrders.length, + shrinkWrap: true, + padding: const EdgeInsets.only(left: 0, right: 8), + physics: NeverScrollableScrollPhysics(), + itemBuilder: (context, index) { + return AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: Row( + children: [ + Image.network( + prescriptionVM.patientPrescriptionOrders[index].doctorImageURL!, + width: 63.h, + height: 63.h, + fit: BoxFit.fill, + ).circle(100), + SizedBox(width: 16.h), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - AppCustomChipWidget(labelText: prescriptionVM.patientPrescriptionOrders[index].clinicDescription!), - AppCustomChipWidget( - icon: AppAssets.doctor_calendar_icon, - labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(prescriptionVM.patientPrescriptionOrders[index].appointmentDate), false), + prescriptionVM.patientPrescriptionOrders[index].doctorName!.toText16(isBold: true), + SizedBox(height: 4.h), + Wrap( + direction: Axis.horizontal, + spacing: 3.h, + runSpacing: 4.h, + children: [ + AppCustomChipWidget(labelText: prescriptionVM.patientPrescriptionOrders[index].clinicDescription!), + AppCustomChipWidget( + icon: AppAssets.doctor_calendar_icon, + labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(prescriptionVM.patientPrescriptionOrders[index].appointmentDate), false), + ), + ], ), ], ), - ], + ), + SizedBox(width: 40.h), + Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, width: 15.h, height: 15.h, fit: BoxFit.contain, iconColor: AppColors.textColor), + ], + ).onPress(() { + prescriptionVM.setPrescriptionsDetailsLoading(); + Navigator.of(context).push( + FadePage( + page: PrescriptionDetailPage(prescriptionsResponseModel: prescriptionVM.patientPrescriptionOrders[index]), + ), + ); + }), + ), + ), + ); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + ), + SizedBox(height: 16.h), + const Divider(color: AppColors.dividerColor), + SizedBox(height: 16.h), + Row( + children: [ + Expanded( + child: CustomButton( + text: "All Prescriptions".needTranslation, + onPressed: () { + Navigator.of(context).push( + FadePage( + page: PrescriptionsListPage(), ), - ), - SizedBox(width: 40.h), - Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, width: 15.h, height: 15.h, fit: BoxFit.contain, iconColor: AppColors.textColor), - ], + ); + }, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12.h, + height: 40.h, + icon: AppAssets.requests, + iconColor: AppColors.primaryRedColor, + iconSize: 16.h, ), ), - ), - ); - }, - separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), - ).onPress(() { - prescriptionVM.setPrescriptionsDetailsLoading(); - Navigator.of(context).push( - FadePage( - page: PrescriptionDetailPage(prescriptionsResponseModel: prescriptionVM.patientPrescriptionOrders[index]), - ), - ); - }), - SizedBox(height: 16.h), - const Divider(color: AppColors.dividerColor), - SizedBox(height: 16.h), - Row( - children: [ - Expanded( - child: CustomButton( - text: "All Prescriptions".needTranslation, - onPressed: () { - Navigator.of(context).push( - FadePage( - page: PrescriptionsListPage(), - ), - ); - }, - backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, - textColor: AppColors.primaryRedColor, - fontSize: 14, - fontWeight: FontWeight.w500, - borderRadius: 12.h, - height: 40.h, - icon: AppAssets.requests, - iconColor: AppColors.primaryRedColor, - iconSize: 16.h, - ), - ), - SizedBox(width: 10.h), - Expanded( - child: CustomButton( - text: "All Medications".needTranslation, - onPressed: () {}, - backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, - textColor: AppColors.primaryRedColor, - fontSize: 14, - fontWeight: FontWeight.w500, - borderRadius: 12.h, - height: 40.h, - icon: AppAssets.all_medications_icon, - iconColor: AppColors.primaryRedColor, - iconSize: 16.h, - ), + SizedBox(width: 10.h), + Expanded( + child: CustomButton( + text: "All Medications".needTranslation, + onPressed: () {}, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12.h, + height: 40.h, + icon: AppAssets.all_medications_icon, + iconColor: AppColors.primaryRedColor, + iconSize: 16.h, + ), + ), + ], ), ], ), - ], - ), - ), - ).paddingSymmetrical(24.h, 0.h); + ), + ).paddingSymmetrical(24.h, 0.h) + : SizedBox.shrink(); }), SizedBox(height: 24.h), //My Doctor Section @@ -594,7 +601,13 @@ class _MedicalFilePageState extends State { padding: EdgeInsets.only(top: 12), shrinkWrap: true, children: [ - MedicalFileCard(label: "Update Insurance".needTranslation, textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.eye_result_icon), + MedicalFileCard(label: "Update Insurance".needTranslation, textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.eye_result_icon).onPress(() { + Navigator.of(context).push( + FadePage( + page: InsuranceHomePage(), + ), + ); + }), MedicalFileCard(label: "Insurance Approvals".needTranslation, textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.eye_result_icon), MedicalFileCard(label: "My Invoices List".needTranslation, textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.eye_result_icon), MedicalFileCard(label: "Ancillary Orders List".needTranslation, textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.eye_result_icon), @@ -604,7 +617,42 @@ class _MedicalFilePageState extends State { ], ); case 2: - return Container(); + // Requests Tab Data + return Column( + children: [ + Consumer(builder: (context, medicalFileVM, child) { + return medicalFileVM.isPatientSickLeaveListLoading + ? PatientSickLeaveCard( + patientSickLeavesResponseModel: PatientSickLeavesResponseModel(), + isLoading: true, + ).paddingSymmetrical(24.h, 0.0) + : medicalFileVM.patientSickLeaveList.isNotEmpty ? PatientSickLeaveCard( + patientSickLeavesResponseModel: medicalFileVM.patientSickLeaveList.first, + isLoading: false, + ).paddingSymmetrical(24.h, 0.0) : SizedBox.shrink(); + }), + SizedBox(height: 10.h), + // GridView( + // gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, crossAxisSpacing: 13, mainAxisSpacing: 13), + // physics: NeverScrollableScrollPhysics(), + // padding: EdgeInsets.only(top: 12), + // shrinkWrap: true, + // children: [ + // MedicalFileCard(label: "Update Insurance".needTranslation, textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.eye_result_icon).onPress(() { + // Navigator.of(context).push( + // FadePage( + // page: InsuranceHomePage(), + // ), + // ); + // }), + // MedicalFileCard(label: "Insurance Approvals".needTranslation, textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.eye_result_icon), + // MedicalFileCard(label: "My Invoices List".needTranslation, textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.eye_result_icon), + // MedicalFileCard(label: "Ancillary Orders List".needTranslation, textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.eye_result_icon), + // ], + // ).paddingSymmetrical(24.h, 0.0), + // SizedBox(height: 16.h), + ], + ); case 3: return Container(); default: diff --git a/lib/presentation/medical_file/patient_sickleaves_list_page.dart b/lib/presentation/medical_file/patient_sickleaves_list_page.dart new file mode 100644 index 0000000..9950779 --- /dev/null +++ b/lib/presentation/medical_file/patient_sickleaves_list_page.dart @@ -0,0 +1,88 @@ +import 'dart:async'; + +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; +import 'package:hmg_patient_app_new/features/medical_file/models/patient_sickleave_response_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:provider/provider.dart'; + +import 'widgets/patient_sick_leave_card.dart'; + +class PatientSickleavesListPage extends StatefulWidget { + const PatientSickleavesListPage({super.key}); + + @override + State createState() => _PatientSickleavesListPageState(); +} + +class _PatientSickleavesListPageState extends State { + late MedicalFileViewModel medicalFileViewModel; + + @override + void initState() { + scheduleMicrotask(() { + medicalFileViewModel.setIsPatientSickLeaveListLoading(true); + medicalFileViewModel.getPatientSickLeaveList(onError: (error) { + Navigator.of(context).pop(); + Navigator.of(context).pop(); + }); + }); + super.initState(); + } + + @override + Widget build(BuildContext context) { + medicalFileViewModel = Provider.of(context, listen: false); + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: CollapsingListView( + title: "${LocaleKeys.sick.tr(context: context)} ${LocaleKeys.sickSubtitle.tr(context: context)}", + child: SingleChildScrollView( + child: Consumer(builder: (context, medicalFileVM, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ListView.separated( + scrollDirection: Axis.vertical, + itemCount: medicalFileVM.isPatientSickLeaveListLoading ? 3 : medicalFileVM.patientSickLeaveList.length, + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemBuilder: (context, index) { + return medicalFileVM.isPatientSickLeaveListLoading + ? PatientSickLeaveCard( + patientSickLeavesResponseModel: PatientSickLeavesResponseModel(), + isLoading: true, + ).paddingSymmetrical(24.h, 0.0) + : medicalFileVM.patientSickLeaveList.isNotEmpty + ? AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 1000), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: PatientSickLeaveCard( + patientSickLeavesResponseModel: medicalFileVM.patientSickLeaveList.first, + isLoading: false, + ).paddingSymmetrical(24.h, 0.0), + ), + ), + ) + : SizedBox.shrink(); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 8.h), + ), + SizedBox(height: 60.h), + ], + ); + }), + ), + ), + ); + } +} diff --git a/lib/presentation/medical_file/vaccine_list_page.dart b/lib/presentation/medical_file/vaccine_list_page.dart index 1080902..0cf48d6 100644 --- a/lib/presentation/medical_file/vaccine_list_page.dart +++ b/lib/presentation/medical_file/vaccine_list_page.dart @@ -42,7 +42,7 @@ class _VaccineListPageState extends State { return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( - title: "Vaccine Info", + title: "Vaccine Info".needTranslation, child: SingleChildScrollView( child: Consumer(builder: (context, medicalFileVM, child) { return Column( diff --git a/lib/presentation/medical_file/widgets/medical_file_card.dart b/lib/presentation/medical_file/widgets/medical_file_card.dart index 6341225..c4980d9 100644 --- a/lib/presentation/medical_file/widgets/medical_file_card.dart +++ b/lib/presentation/medical_file/widgets/medical_file_card.dart @@ -36,7 +36,7 @@ class MedicalFileCard extends StatelessWidget { children: [ Utils.buildSvgWithAssets(icon: svgIcon, width: iconSize.h, height: iconSize.h, fit: BoxFit.contain), SizedBox(height: 12.h), - isLargeText ? label.toText14(color: textColor, isBold: true, maxlines: 1) : label.toText11(color: textColor, isBold: true, maxLine: 1), + isLargeText ? label.toText14(color: textColor, isBold: true, maxlines: 1) : label.toText11(color: textColor, isBold: true, maxLine: 2), ], ), ), diff --git a/lib/presentation/medical_file/widgets/patient_sick_leave_card.dart b/lib/presentation/medical_file/widgets/patient_sick_leave_card.dart new file mode 100644 index 0000000..438013f --- /dev/null +++ b/lib/presentation/medical_file/widgets/patient_sick_leave_card.dart @@ -0,0 +1,191 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; +import 'package:hmg_patient_app_new/features/medical_file/models/patient_sickleave_response_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/medical_file/patient_sickleaves_list_page.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; +import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; +import 'package:open_filex/open_filex.dart'; +import 'package:provider/provider.dart'; + +class PatientSickLeaveCard extends StatelessWidget { + PatientSickLeaveCard({super.key, required this.patientSickLeavesResponseModel, this.isLoading = false}); + + late MedicalFileViewModel medicalFileViewModel; + PatientSickLeavesResponseModel patientSickLeavesResponseModel; + bool isLoading; + + @override + Widget build(BuildContext context) { + AppState _appState = getIt.get(); + medicalFileViewModel = Provider.of(context, listen: false); + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24, hasShadow: true), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "${LocaleKeys.sick.tr(context: context)} ${LocaleKeys.sickSubtitle.tr(context: context)}".toText16(isBold: true), + AppCustomChipWidget( + labelText: isLoading ? "" : getStatusText(context), + backgroundColor: getStatusColor().withOpacity(0.15), + textColor: getStatusColor(), + ).toShimmer2(isShow: isLoading, width: 100.h), + ], + ), + SizedBox(height: 16.h), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Image.network( + isLoading ? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png" : patientSickLeavesResponseModel.doctorImageURL!, + width: 30.h, + height: 30.h, + fit: BoxFit.fill, + ).circle(100).toShimmer2(isShow: isLoading), + SizedBox(width: 16.h), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + (isLoading ? "" : patientSickLeavesResponseModel.doctorName!).toText16(isBold: true).toShimmer2(isShow: isLoading), + SizedBox(height: 8.h), + Wrap( + direction: Axis.horizontal, + spacing: 3.h, + runSpacing: 4.h, + children: [ + AppCustomChipWidget( + icon: AppAssets.doctor_calendar_icon, + labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientSickLeavesResponseModel.appointmentDate), false), + ).toShimmer2(isShow: isLoading), + AppCustomChipWidget(labelText: isLoading ? "Pending Activation" : patientSickLeavesResponseModel.clinicName!).toShimmer2(isShow: isLoading), + ], + ), + ], + ), + ), + ], + ), + SizedBox(height: 16.h), + Row( + children: [ + isLoading + ? Container().toShimmer2(isShow: true, height: 40.h, width: 100.h, radius: 12.h) + : Expanded( + flex: 6, + child: CustomButton( + text: "Download Report".needTranslation, + onPressed: () async { + LoaderBottomSheet.showLoader(); + await medicalFileViewModel.getPatientSickLeavePDF(patientSickLeavesResponseModel, _appState.getAuthenticatedUser()!).then((val) async { + LoaderBottomSheet.hideLoader(); + if (medicalFileViewModel.patientSickLeavePDFBase64.isNotEmpty) { + String path = await Utils.createFileFromString(medicalFileViewModel.patientSickLeavePDFBase64, "pdf"); + try { + OpenFilex.open(path); + } catch (ex) { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: "Cannot open file".needTranslation), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } + } + }); + }, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + icon: AppAssets.download, + iconColor: AppColors.primaryRedColor, + iconSize: 14.h, + ).toShimmer2(isShow: isLoading), + ), + SizedBox(width: 8.h), + Expanded( + flex: 1, + child: Container( + height: 40.h, + width: 40.h, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.textColor, + borderRadius: 10.h, + ), + child: Padding( + padding: EdgeInsets.all(10.h), + child: Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon, + width: 10.h, + height: 10.h, + fit: BoxFit.contain, + ), + ), + ).toShimmer2(isShow: isLoading).onPress(() { + Navigator.of(context).push( + FadePage( + page: PatientSickleavesListPage(), + ), + ); + }), + ), + ], + ), + ], + ), + ), + ); + } + + String getStatusText(BuildContext context) { + String statusText = ""; + if (patientSickLeavesResponseModel.status == 1) { + statusText = LocaleKeys.pendingActivation.tr(context: context); + } else if (patientSickLeavesResponseModel.status == 2) { + statusText = LocaleKeys.ready.tr(context: context); + } else if (patientSickLeavesResponseModel.status == 3) { + statusText = LocaleKeys.awaitingApproval.tr(context: context); + } else { + statusText = ""; + } + return statusText; + } + + Color getStatusColor() { + Color statusColor = Colors.white; + if (patientSickLeavesResponseModel.status == 1) { + statusColor = Color(0xffCC9B14); + } else if (patientSickLeavesResponseModel.status == 2) { + statusColor = Color(0xff359846); + } else if (patientSickLeavesResponseModel.status == 3) { + statusColor = Color(0xffD02127); + } else { + statusColor = Colors.white; + } + return statusColor; + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 36326f4..147d7ac 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -76,6 +76,8 @@ dependencies: flutter_nfc_kit: ^3.6.0 barcode_scan2: ^4.5.1 keyboard_actions: ^4.2.0 + path_provider: ^2.0.8 + open_filex: ^4.7.0 dev_dependencies: flutter_test: From 14725616c60b7bf3ede96be893f0a0f1be786ff0 Mon Sep 17 00:00:00 2001 From: Haroon Amjad <> Date: Sat, 13 Sep 2025 00:48:22 +0300 Subject: [PATCH 19/20] medical report implementation contd. --- .../medical_file/medical_file_repo.dart | 48 +++++ .../medical_file/medical_file_view_model.dart | 65 +++++- .../patient_medical_response_model.dart | 192 ++++++++++++++++++ .../medical_file/medical_file_page.dart | 112 +++++++--- .../medical_file/medical_reports_page.dart | 91 +++++++++ .../widgets/patient_medical_report_card.dart | 120 +++++++++++ 6 files changed, 599 insertions(+), 29 deletions(-) create mode 100644 lib/features/medical_file/models/patient_medical_response_model.dart create mode 100644 lib/presentation/medical_file/medical_reports_page.dart create mode 100644 lib/presentation/medical_file/widgets/patient_medical_report_card.dart diff --git a/lib/features/medical_file/medical_file_repo.dart b/lib/features/medical_file/medical_file_repo.dart index 14be4ae..6d30adc 100644 --- a/lib/features/medical_file/medical_file_repo.dart +++ b/lib/features/medical_file/medical_file_repo.dart @@ -3,6 +3,7 @@ import 'package:hmg_patient_app_new/core/api/api_client.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; +import 'package:hmg_patient_app_new/features/medical_file/models/patient_medical_response_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/models/patient_sickleave_response_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/models/patient_vaccine_response_model.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; @@ -15,6 +16,8 @@ abstract class MedicalFileRepo { Future>>> getPatientSickLeavesList(); Future>> getPatientSickLeavePDF(PatientSickLeavesResponseModel patientSickLeavesResponseModel, AuthenticatedUser authenticatedUser); + + Future>>> getPatientMedicalReportsList(); } class MedicalFileRepoImp implements MedicalFileRepo { @@ -157,4 +160,49 @@ class MedicalFileRepoImp implements MedicalFileRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future>>> getPatientMedicalReportsList() async { + Map mapDevice = { + "IsReport": true, + "EncounterType": 1, + "RequestType": 1, + }; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + REPORTS, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['GetPatientMedicalStatus']; + // if (list == null || list.isEmpty) { + // throw Exception("lab list is empty"); + // } + + final vaccinesList = list.map((item) => PatientMedicalReportResponseModel.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: vaccinesList, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } } diff --git a/lib/features/medical_file/medical_file_view_model.dart b/lib/features/medical_file/medical_file_view_model.dart index 7140e0c..bcc7ed2 100644 --- a/lib/features/medical_file/medical_file_view_model.dart +++ b/lib/features/medical_file/medical_file_view_model.dart @@ -1,6 +1,7 @@ import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/features/authentication/models/resp_models/authenticated_user_resp_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/medical_file_repo.dart'; +import 'package:hmg_patient_app_new/features/medical_file/models/patient_medical_response_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/models/patient_sickleave_response_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/models/patient_vaccine_response_model.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; @@ -10,6 +11,7 @@ class MedicalFileViewModel extends ChangeNotifier { bool isPatientVaccineListLoading = false; bool isPatientSickLeaveListLoading = false; bool isPatientSickLeavePDFLoading = false; + bool isPatientMedicalReportsListLoading = false; MedicalFileRepo medicalFileRepo; ErrorHandlerService errorHandlerService; @@ -17,12 +19,33 @@ class MedicalFileViewModel extends ChangeNotifier { List patientVaccineList = []; List patientSickLeaveList = []; + List patientMedicalReportList = []; + + List patientMedicalReportRequestedList = []; + List patientMedicalReportReadyList = []; + List patientMedicalReportCancelledList = []; + String patientSickLeavePDFBase64 = ""; + int selectedMedicalReportsTabIndex = 0; + MedicalFileViewModel({required this.medicalFileRepo, required this.errorHandlerService}); initMedicalFileProvider() { isPatientVaccineListLoading = true; + isPatientMedicalReportsListLoading = true; + notifyListeners(); + } + + void onMedicalReportTabChange(int index) { + selectedMedicalReportsTabIndex = index; + if (index == 0) { + patientMedicalReportList = patientMedicalReportRequestedList; + } else if (index == 1) { + patientMedicalReportList = patientMedicalReportReadyList; + } else if (index == 2) { + patientMedicalReportList = patientMedicalReportCancelledList; + } notifyListeners(); } @@ -44,6 +67,14 @@ class MedicalFileViewModel extends ChangeNotifier { notifyListeners(); } + setIsPatientMedicalReportsLoading(bool val) { + if (val) { + patientMedicalReportList.clear(); + } + isPatientMedicalReportsListLoading = val; + notifyListeners(); + } + void onTabChanged(int index) { selectedTabIndex = index; notifyListeners(); @@ -101,7 +132,8 @@ class MedicalFileViewModel extends ChangeNotifier { ); } - Future getPatientSickLeavePDF(PatientSickLeavesResponseModel patientSickLeavesResponseModel, AuthenticatedUser authenticatedUser,{Function(dynamic)? onSuccess, Function(String)? onError}) async { + Future getPatientSickLeavePDF(PatientSickLeavesResponseModel patientSickLeavesResponseModel, AuthenticatedUser authenticatedUser, + {Function(dynamic)? onSuccess, Function(String)? onError}) async { final result = await medicalFileRepo.getPatientSickLeavePDF(patientSickLeavesResponseModel, authenticatedUser); result.fold( @@ -125,4 +157,35 @@ class MedicalFileViewModel extends ChangeNotifier { }, ); } + + Future getPatientMedicalReportList({Function(dynamic)? onSuccess, Function(String)? onError}) async { + patientMedicalReportList.clear(); + final result = await medicalFileRepo.getPatientMedicalReportsList(); + + result.fold( + (failure) async => await errorHandlerService.handleError( + failure: failure, + onOkPressed: () { + onError!(failure.message); + }, + ), + (apiResponse) { + if (apiResponse.messageStatus == 2) { + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + patientMedicalReportList = apiResponse.data!; + if (patientMedicalReportList.isNotEmpty) { + patientMedicalReportRequestedList = patientMedicalReportList.where((element) => element.status == 1).toList(); + patientMedicalReportReadyList = patientMedicalReportList.where((element) => element.status == 2).toList(); + patientMedicalReportCancelledList = patientMedicalReportList.where((element) => element.status == 4).toList(); + } + isPatientMedicalReportsListLoading = false; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } } diff --git a/lib/features/medical_file/models/patient_medical_response_model.dart b/lib/features/medical_file/models/patient_medical_response_model.dart new file mode 100644 index 0000000..52785af --- /dev/null +++ b/lib/features/medical_file/models/patient_medical_response_model.dart @@ -0,0 +1,192 @@ +class PatientMedicalReportResponseModel { + int? status; + String? encounterDate; + int? projectID; + int? invoiceNo; + int? encounterNo; + String? procedureId; + int? requestType; + String? setupId; + int? patientID; + int? doctorID; + int? clinicID; + String? requestDate; + bool? isRead; + dynamic isReadOn; + num? actualDoctorRate; + String? admissionDate; + int? admissionNumber; + String? appointmentDate; + int? appointmentNO; + String? appointmentTime; + String? clinicDescription; + dynamic clinicDescriptionN; + num? decimalDoctorRate; + String? docName; + dynamic docNameN; + String? doctorImageURL; + String? doctorName; + dynamic doctorNameN; + num? doctorRate; + num? doctorStarsRate; + int? invoiceNoVP; + dynamic invoiceType; + bool? isDoctorAllowVedioCall; + bool? isExecludeDoctor; + bool? isInOutPatient; + String? isInOutPatientDescription; + String? isInOutPatientDescriptionN; + int? noOfPatientsRate; + String? projectName; + dynamic projectNameN; + int? sourceID; + dynamic sourceName; + dynamic sourceNameN; + String? statusDesc; + dynamic strAppointmentDate; + + PatientMedicalReportResponseModel( + {this.status, + this.encounterDate, + this.projectID, + this.invoiceNo, + this.encounterNo, + this.procedureId, + this.requestType, + this.setupId, + this.patientID, + this.doctorID, + this.clinicID, + this.requestDate, + this.isRead, + this.isReadOn, + this.actualDoctorRate, + this.admissionDate, + this.admissionNumber, + this.appointmentDate, + this.appointmentNO, + this.appointmentTime, + this.clinicDescription, + this.clinicDescriptionN, + this.decimalDoctorRate, + this.docName, + this.docNameN, + this.doctorImageURL, + this.doctorName, + this.doctorNameN, + this.doctorRate, + this.doctorStarsRate, + this.invoiceNoVP, + this.invoiceType, + this.isDoctorAllowVedioCall, + this.isExecludeDoctor, + this.isInOutPatient, + this.isInOutPatientDescription, + this.isInOutPatientDescriptionN, + this.noOfPatientsRate, + this.projectName, + this.projectNameN, + this.sourceID, + this.sourceName, + this.sourceNameN, + this.statusDesc, + this.strAppointmentDate}); + + PatientMedicalReportResponseModel.fromJson(Map json) { + status = json['Status']; + encounterDate = json['EncounterDate']; + projectID = json['ProjectID']; + invoiceNo = json['InvoiceNo']; + encounterNo = json['EncounterNo']; + procedureId = json['ProcedureId']; + requestType = json['RequestType']; + setupId = json['SetupId']; + patientID = json['PatientID']; + doctorID = json['DoctorID']; + clinicID = json['ClinicID']; + requestDate = json['RequestDate']; + isRead = json['IsRead']; + isReadOn = json['IsReadOn']; + actualDoctorRate = json['ActualDoctorRate']; + admissionDate = json['AdmissionDate']; + admissionNumber = json['AdmissionNumber']; + appointmentDate = json['AppointmentDate']; + appointmentNO = json['AppointmentNO']; + appointmentTime = json['AppointmentTime']; + clinicDescription = json['ClinicDescription']; + clinicDescriptionN = json['ClinicDescriptionN']; + decimalDoctorRate = json['DecimalDoctorRate']; + docName = json['DocName']; + docNameN = json['DocNameN']; + doctorImageURL = json['DoctorImageURL']; + doctorName = json['DoctorName']; + doctorNameN = json['DoctorNameN']; + doctorRate = json['DoctorRate']; + doctorStarsRate = json['DoctorStarsRate']; + invoiceNoVP = json['InvoiceNo_VP']; + invoiceType = json['InvoiceType']; + isDoctorAllowVedioCall = json['IsDoctorAllowVedioCall']; + isExecludeDoctor = json['IsExecludeDoctor']; + isInOutPatient = json['IsInOutPatient']; + isInOutPatientDescription = json['IsInOutPatientDescription']; + isInOutPatientDescriptionN = json['IsInOutPatientDescriptionN']; + noOfPatientsRate = json['NoOfPatientsRate']; + projectName = json['ProjectName']; + projectNameN = json['ProjectNameN']; + sourceID = json['SourceID']; + sourceName = json['SourceName']; + sourceNameN = json['SourceNameN']; + statusDesc = json['StatusDesc']; + strAppointmentDate = json['StrAppointmentDate']; + } + + Map toJson() { + final Map data = new Map(); + data['Status'] = this.status; + data['EncounterDate'] = this.encounterDate; + data['ProjectID'] = this.projectID; + data['InvoiceNo'] = this.invoiceNo; + data['EncounterNo'] = this.encounterNo; + data['ProcedureId'] = this.procedureId; + data['RequestType'] = this.requestType; + data['SetupId'] = this.setupId; + data['PatientID'] = this.patientID; + data['DoctorID'] = this.doctorID; + data['ClinicID'] = this.clinicID; + data['RequestDate'] = this.requestDate; + data['IsRead'] = this.isRead; + data['IsReadOn'] = this.isReadOn; + data['ActualDoctorRate'] = this.actualDoctorRate; + data['AdmissionDate'] = this.admissionDate; + data['AdmissionNumber'] = this.admissionNumber; + data['AppointmentDate'] = this.appointmentDate; + data['AppointmentNO'] = this.appointmentNO; + data['AppointmentTime'] = this.appointmentTime; + data['ClinicDescription'] = this.clinicDescription; + data['ClinicDescriptionN'] = this.clinicDescriptionN; + data['DecimalDoctorRate'] = this.decimalDoctorRate; + data['DocName'] = this.docName; + data['DocNameN'] = this.docNameN; + data['DoctorImageURL'] = this.doctorImageURL; + data['DoctorName'] = this.doctorName; + data['DoctorNameN'] = this.doctorNameN; + data['DoctorRate'] = this.doctorRate; + data['DoctorStarsRate'] = this.doctorStarsRate; + data['InvoiceNo_VP'] = this.invoiceNoVP; + data['InvoiceType'] = this.invoiceType; + data['IsDoctorAllowVedioCall'] = this.isDoctorAllowVedioCall; + data['IsExecludeDoctor'] = this.isExecludeDoctor; + data['IsInOutPatient'] = this.isInOutPatient; + data['IsInOutPatientDescription'] = this.isInOutPatientDescription; + data['IsInOutPatientDescriptionN'] = this.isInOutPatientDescriptionN; + data['NoOfPatientsRate'] = this.noOfPatientsRate; + data['ProjectName'] = this.projectName; + data['ProjectNameN'] = this.projectNameN; + data['SourceID'] = this.sourceID; + data['SourceName'] = this.sourceName; + data['SourceNameN'] = this.sourceNameN; + data['StatusDesc'] = this.statusDesc; + data['StrAppointmentDate'] = this.strAppointmentDate; + return data; + } +} diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index 02af055..22aff4d 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -23,6 +23,7 @@ import 'package:hmg_patient_app_new/presentation/appointments/my_doctors_page.da import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.dart'; import 'package:hmg_patient_app_new/presentation/insurance/widgets/patient_insurance_card.dart'; import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/presentation/medical_file/medical_reports_page.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/vaccine_list_page.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/widgets/lab_rad_card.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/widgets/medical_file_card.dart'; @@ -601,16 +602,41 @@ class _MedicalFilePageState extends State { padding: EdgeInsets.only(top: 12), shrinkWrap: true, children: [ - MedicalFileCard(label: "Update Insurance".needTranslation, textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.eye_result_icon).onPress(() { + MedicalFileCard( + label: "Update Insurance".needTranslation, + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.eye_result_icon, + isLargeText: false, + iconSize: 36.h) + .onPress(() { Navigator.of(context).push( FadePage( page: InsuranceHomePage(), ), ); }), - MedicalFileCard(label: "Insurance Approvals".needTranslation, textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.eye_result_icon), - MedicalFileCard(label: "My Invoices List".needTranslation, textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.eye_result_icon), - MedicalFileCard(label: "Ancillary Orders List".needTranslation, textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.eye_result_icon), + MedicalFileCard( + label: "Insurance Approvals".needTranslation, + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.eye_result_icon, + isLargeText: false, + iconSize: 36.h), + MedicalFileCard( + label: "My Invoices List".needTranslation, + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.eye_result_icon, + isLargeText: false, + iconSize: 36.h), + MedicalFileCard( + label: "Ancillary Orders List".needTranslation, + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.eye_result_icon, + isLargeText: false, + iconSize: 36.h), ], ).paddingSymmetrical(24.h, 0.0), SizedBox(height: 16.h), @@ -626,31 +652,61 @@ class _MedicalFilePageState extends State { patientSickLeavesResponseModel: PatientSickLeavesResponseModel(), isLoading: true, ).paddingSymmetrical(24.h, 0.0) - : medicalFileVM.patientSickLeaveList.isNotEmpty ? PatientSickLeaveCard( - patientSickLeavesResponseModel: medicalFileVM.patientSickLeaveList.first, - isLoading: false, - ).paddingSymmetrical(24.h, 0.0) : SizedBox.shrink(); + : medicalFileVM.patientSickLeaveList.isNotEmpty + ? PatientSickLeaveCard( + patientSickLeavesResponseModel: medicalFileVM.patientSickLeaveList.first, + isLoading: false, + ).paddingSymmetrical(24.h, 0.0) + : SizedBox.shrink(); }), - SizedBox(height: 10.h), - // GridView( - // gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, crossAxisSpacing: 13, mainAxisSpacing: 13), - // physics: NeverScrollableScrollPhysics(), - // padding: EdgeInsets.only(top: 12), - // shrinkWrap: true, - // children: [ - // MedicalFileCard(label: "Update Insurance".needTranslation, textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.eye_result_icon).onPress(() { - // Navigator.of(context).push( - // FadePage( - // page: InsuranceHomePage(), - // ), - // ); - // }), - // MedicalFileCard(label: "Insurance Approvals".needTranslation, textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.eye_result_icon), - // MedicalFileCard(label: "My Invoices List".needTranslation, textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.eye_result_icon), - // MedicalFileCard(label: "Ancillary Orders List".needTranslation, textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.eye_result_icon), - // ], - // ).paddingSymmetrical(24.h, 0.0), - // SizedBox(height: 16.h), + SizedBox(height: 16.h), + GridView( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3, crossAxisSpacing: 13, mainAxisSpacing: 13), + physics: NeverScrollableScrollPhysics(), + padding: EdgeInsets.zero, + shrinkWrap: true, + children: [ + MedicalFileCard( + label: LocaleKeys.monthlyReports.tr(context: context), + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.eye_result_icon, + isLargeText: false, + iconSize: 40.h, + ), + MedicalFileCard( + label: "Medical Reports".needTranslation, + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.allergy_info_icon, + isLargeText: false, + iconSize: 40.h, + ).onPress(() { + medicalFileViewModel.setIsPatientMedicalReportsLoading(true); + medicalFileViewModel.getPatientMedicalReportList(); + Navigator.of(context).push( + FadePage( + page: MedicalReportsPage(), + ), + ); + }), + MedicalFileCard( + label: "Sick Leave Report".needTranslation, + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.vaccine_info_icon, + isLargeText: false, + iconSize: 40.h, + ).onPress(() { + Navigator.of(context).push( + FadePage( + page: VaccineListPage(), + ), + ); + }), + ], + ).paddingSymmetrical(24.h, 0.0), + SizedBox(height: 24.h), ], ); case 3: diff --git a/lib/presentation/medical_file/medical_reports_page.dart b/lib/presentation/medical_file/medical_reports_page.dart new file mode 100644 index 0000000..c30f9af --- /dev/null +++ b/lib/presentation/medical_file/medical_reports_page.dart @@ -0,0 +1,91 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; +import 'package:hmg_patient_app_new/features/medical_file/models/patient_medical_response_model.dart'; +import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/presentation/medical_file/widgets/patient_medical_report_card.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; +import 'package:provider/provider.dart'; + +class MedicalReportsPage extends StatefulWidget { + const MedicalReportsPage({super.key}); + + @override + State createState() => _MedicalReportsPageState(); +} + +class _MedicalReportsPageState extends State { + late MedicalFileViewModel medicalFileViewModel; + + @override + Widget build(BuildContext context) { + medicalFileViewModel = Provider.of(context, listen: false); + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: CollapsingListView( + title: "Medical Reports".needTranslation, + child: SingleChildScrollView( + child: Column( + children: [ + SizedBox(height: 16.h), + CustomTabBar( + activeTextColor: Color(0xffED1C2B), + activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), + tabs: [ + CustomTabBarModel(null, "Requested".needTranslation), + CustomTabBarModel(null, "Ready".needTranslation), + CustomTabBarModel(null, "Cancelled".needTranslation), + ], + onTabChange: (index) { + medicalFileViewModel.onMedicalReportTabChange(index); + }, + ).paddingSymmetrical(24.h, 0.h), + Consumer(builder: (context, medicalFileVM, child) { + return ListView.separated( + padding: EdgeInsets.only(top: 24.h), + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: medicalFileViewModel.isPatientMedicalReportsListLoading ? 3 : medicalFileViewModel.patientMedicalReportList.length, + // medicalFileViewModel.patientMedicalReportList.isNotEmpty + // ? medicalFileViewModel.patientMedicalReportList.length + // : 1, + itemBuilder: (context, index) { + return medicalFileViewModel.isPatientMedicalReportsListLoading + ? PatientMedicalReportCard( + patientMedicalReportResponseModel: PatientMedicalReportResponseModel(), + isLoading: true, + ).paddingSymmetrical(24.h, 0.h) + : AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: AnimatedContainer( + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), + child: PatientMedicalReportCard( + patientMedicalReportResponseModel: medicalFileVM.patientMedicalReportList[index], + isLoading: false, + ), + ).paddingSymmetrical(24.h, 0.h), + ), + ), + ); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + ); + }), + SizedBox(height: 24.h), + ], + ), + ), + ), + ); + } +} diff --git a/lib/presentation/medical_file/widgets/patient_medical_report_card.dart b/lib/presentation/medical_file/widgets/patient_medical_report_card.dart new file mode 100644 index 0000000..3f890e6 --- /dev/null +++ b/lib/presentation/medical_file/widgets/patient_medical_report_card.dart @@ -0,0 +1,120 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/cupertino.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/medical_file/models/patient_medical_response_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; + +class PatientMedicalReportCard extends StatelessWidget { + PatientMedicalReportCard({super.key, required this.patientMedicalReportResponseModel, this.isLoading = false}); + + PatientMedicalReportResponseModel patientMedicalReportResponseModel; + + bool isLoading = true; + + @override + Widget build(BuildContext context) { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: true, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Image.network( + isLoading ? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png" : patientMedicalReportResponseModel.doctorImageURL!, + width: 63.h, + height: 63.h, + fit: BoxFit.fill, + ).circle(100).toShimmer2(isShow: isLoading), + SizedBox(width: 16.h), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + (isLoading ? "" : patientMedicalReportResponseModel.doctorName!).toText16(isBold: true).toShimmer2(isShow: isLoading), + SizedBox(height: 4.h), + Wrap( + direction: Axis.horizontal, + spacing: 3.h, + runSpacing: 4.h, + children: [ + AppCustomChipWidget(labelText: isLoading ? "" : patientMedicalReportResponseModel.clinicDescription!).toShimmer2(isShow: isLoading), + AppCustomChipWidget(labelText: isLoading ? "" : patientMedicalReportResponseModel.projectName!).toShimmer2(isShow: isLoading), + AppCustomChipWidget( + icon: AppAssets.doctor_calendar_icon, + labelText: isLoading + ? "" + : "${DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientMedicalReportResponseModel.requestDate), false)}, ${DateUtil.formatDateToTimeLang(DateUtil.convertStringToDate(patientMedicalReportResponseModel.requestDate), false)}") + .toShimmer2(isShow: isLoading), + AppCustomChipWidget( + icon: AppAssets.rating_icon, iconColor: AppColors.ratingColorYellow, labelText: isLoading ? "" : "Rating: ${patientMedicalReportResponseModel.decimalDoctorRate}") + .toShimmer2(isShow: isLoading), + ], + ), + ], + ), + ), + ], + ), + patientMedicalReportResponseModel.status == 2 + ? Padding( + padding: EdgeInsets.only(top: 16.h), + child: Row( + children: [ + Expanded( + child: CustomButton( + text: "Share", + onPressed: () {}, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12.h, + height: 40.h, + icon: AppAssets.download_1, + iconColor: AppColors.primaryRedColor, + iconSize: 16.h, + ).toShimmer2(isShow: isLoading), + ), + SizedBox(width: 16.h), + Expanded( + child: CustomButton( + text: "Download", + onPressed: () {}, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12.h, + height: 40.h, + icon: AppAssets.download_1, + iconColor: AppColors.primaryRedColor, + iconSize: 16.h, + ).toShimmer2(isShow: isLoading), + ), + ], + ), + ) + : SizedBox.shrink() + ], + ), + ), + ); + } +} From 7b94199d58a46d541708e4dd586cb5bb092f3418 Mon Sep 17 00:00:00 2001 From: Haroon Amjad <> Date: Sun, 14 Sep 2025 01:33:49 +0300 Subject: [PATCH 20/20] Medical report implementation done, Book Appointment implementation contd. --- assets/images/svg/search_by_clinic_icon.svg | 5 + assets/images/svg/search_by_doctor_icon.svg | 5 + assets/images/svg/search_by_region_icon.svg | 5 + lib/core/app_assets.dart | 3 + lib/core/dependencies.dart | 8 + lib/core/utils/utils.dart | 3 +- .../book_appointments_view_model.dart | 17 ++ .../medical_file/medical_file_repo.dart | 62 +++++++ .../medical_file/medical_file_view_model.dart | 29 ++++ lib/main.dart | 7 + .../appointment_payment_page.dart | 2 +- .../book_appointment_page.dart | 159 ++++++++++++++++++ .../book_appointment/select_clinic_page.dart | 29 ++++ lib/presentation/home/navigation_screen.dart | 3 +- .../home/widgets/small_service_card.dart | 18 ++ .../medical_file/medical_file_page.dart | 3 +- .../medical_file/medical_reports_page.dart | 3 + .../widgets/patient_medical_report_card.dart | 45 ++++- 18 files changed, 399 insertions(+), 7 deletions(-) create mode 100644 assets/images/svg/search_by_clinic_icon.svg create mode 100644 assets/images/svg/search_by_doctor_icon.svg create mode 100644 assets/images/svg/search_by_region_icon.svg create mode 100644 lib/presentation/book_appointment/book_appointment_page.dart create mode 100644 lib/presentation/book_appointment/select_clinic_page.dart diff --git a/assets/images/svg/search_by_clinic_icon.svg b/assets/images/svg/search_by_clinic_icon.svg new file mode 100644 index 0000000..ce00f8c --- /dev/null +++ b/assets/images/svg/search_by_clinic_icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/svg/search_by_doctor_icon.svg b/assets/images/svg/search_by_doctor_icon.svg new file mode 100644 index 0000000..2ca5eb9 --- /dev/null +++ b/assets/images/svg/search_by_doctor_icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/svg/search_by_region_icon.svg b/assets/images/svg/search_by_region_icon.svg new file mode 100644 index 0000000..00abe12 --- /dev/null +++ b/assets/images/svg/search_by_region_icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index c6e6a3c..106a5ad 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -103,6 +103,9 @@ class AppAssets { static const String all_medications_icon = '$svgBasePath/all_medications_icon.svg'; static const String allergy_info_icon = '$svgBasePath/allergy_info_icon.svg'; static const String vaccine_info_icon = '$svgBasePath/vaccine_info_icon.svg'; + static const String search_by_clinic_icon = '$svgBasePath/search_by_clinic_icon.svg'; + static const String search_by_doctor_icon = '$svgBasePath/search_by_doctor_icon.svg'; + static const String search_by_region_icon = '$svgBasePath/search_by_region_icon.svg'; //bottom navigation// static const String homeBottom = '$svgBasePath/home_bottom.svg'; diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index 3518abe..f86ec97 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -6,6 +6,7 @@ import 'package:hmg_patient_app_new/core/location_util.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_repo.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_repo.dart'; +import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/common/common_repo.dart'; import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_repo.dart'; import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart'; @@ -158,6 +159,13 @@ class AppDependencies { ), ); + getIt.registerLazySingleton( + () => BookAppointmentsViewModel( + bookAppointmentsRepo: getIt(), + errorHandlerService: getIt(), + ), + ); + getIt.registerLazySingleton( () => AuthenticationViewModel( authenticationRepo: getIt(), cacheService: getIt(), navigationService: getIt(), dialogService: getIt(), appState: getIt(), errorHandlerService: getIt(), localAuthService: getIt()), diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart index 90a810c..e228591 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -342,7 +342,8 @@ class Utils { ).center; } - static bool isVidaPlusProject(AppState appState, int projectID) { + static bool isVidaPlusProject(int projectID) { + AppState appState = getIt.get(); bool isVidaPlus = false; for (var element in appState.vidaPlusProjectList) { if (element.projectID == projectID) { diff --git a/lib/features/book_appointments/book_appointments_view_model.dart b/lib/features/book_appointments/book_appointments_view_model.dart index e69de29..8f096dd 100644 --- a/lib/features/book_appointments/book_appointments_view_model.dart +++ b/lib/features/book_appointments/book_appointments_view_model.dart @@ -0,0 +1,17 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_repo.dart'; +import 'package:hmg_patient_app_new/services/error_handler_service.dart'; + +class BookAppointmentsViewModel extends ChangeNotifier { + int selectedTabIndex = 0; + + BookAppointmentsRepo bookAppointmentsRepo; + ErrorHandlerService errorHandlerService; + + BookAppointmentsViewModel({required this.bookAppointmentsRepo, required this.errorHandlerService}); + + void onTabChanged(int index) { + selectedTabIndex = index; + notifyListeners(); + } +} diff --git a/lib/features/medical_file/medical_file_repo.dart b/lib/features/medical_file/medical_file_repo.dart index 6d30adc..affe564 100644 --- a/lib/features/medical_file/medical_file_repo.dart +++ b/lib/features/medical_file/medical_file_repo.dart @@ -3,6 +3,8 @@ import 'package:hmg_patient_app_new/core/api/api_client.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/features/medical_file/models/patient_medical_response_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/models/patient_sickleave_response_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/models/patient_vaccine_response_model.dart'; @@ -18,6 +20,8 @@ abstract class MedicalFileRepo { Future>> getPatientSickLeavePDF(PatientSickLeavesResponseModel patientSickLeavesResponseModel, AuthenticatedUser authenticatedUser); Future>>> getPatientMedicalReportsList(); + + Future>> getPatientMedicalReportPDF(PatientMedicalReportResponseModel patientMedicalReportResponseModel, AuthenticatedUser authenticatedUser); } class MedicalFileRepoImp implements MedicalFileRepo { @@ -205,4 +209,62 @@ class MedicalFileRepoImp implements MedicalFileRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future> getPatientMedicalReportPDF(PatientMedicalReportResponseModel patientMedicalReportResponseModel, AuthenticatedUser authenticatedUser) async { + Map mapDevice = { + "SetupID": patientMedicalReportResponseModel.setupId, + "PrintDate": patientMedicalReportResponseModel.requestDate!, + "ProcedureID": "05005009", + "Reporttype": "MEDICAL REPORT", + "stamp": patientMedicalReportResponseModel.requestDate!, + "To": authenticatedUser.emailAddress, + "DateofBirth": authenticatedUser.dateofBirth, + "PatientIditificationNum": authenticatedUser.patientIdentificationNo, + "PatientMobileNumber": authenticatedUser.mobileNumber, + "PatientName": "${authenticatedUser.firstName!} ${authenticatedUser.lastName!}", + "ProjectName": patientMedicalReportResponseModel.projectName, + "ClinicName": patientMedicalReportResponseModel.clinicDescription, + "ProjectID": patientMedicalReportResponseModel.projectID, + "InvoiceNo": Utils.isVidaPlusProject(patientMedicalReportResponseModel.projectID!) ? patientMedicalReportResponseModel.invoiceNoVP : patientMedicalReportResponseModel.invoiceNo, + "InvoiceNo_VP": Utils.isVidaPlusProject(patientMedicalReportResponseModel.projectID!) ? patientMedicalReportResponseModel.invoiceNoVP : patientMedicalReportResponseModel.invoiceNo, + "PrintedByName": "${authenticatedUser.firstName!} ${authenticatedUser.lastName!}", + }; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + GET_MEDICAL_REPORT_PDF, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + // final list = response['List_SickLeave']; + // if (list == null || list.isEmpty) { + // throw Exception("lab list is empty"); + // } + + // final vaccinesList = list.map((item) => PatientSickLeavesResponseModel.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: response["MedicalReportBase64"], + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } } diff --git a/lib/features/medical_file/medical_file_view_model.dart b/lib/features/medical_file/medical_file_view_model.dart index bcc7ed2..73167bb 100644 --- a/lib/features/medical_file/medical_file_view_model.dart +++ b/lib/features/medical_file/medical_file_view_model.dart @@ -26,6 +26,7 @@ class MedicalFileViewModel extends ChangeNotifier { List patientMedicalReportCancelledList = []; String patientSickLeavePDFBase64 = ""; + String patientMedicalReportPDFBase64 = ""; int selectedMedicalReportsTabIndex = 0; @@ -62,6 +63,7 @@ class MedicalFileViewModel extends ChangeNotifier { setIsPatientSickLeaveListLoading(bool val) { if (val) { patientSickLeaveList.clear(); + patientSickLeavePDFBase64 = ""; } isPatientSickLeaveListLoading = val; notifyListeners(); @@ -70,6 +72,7 @@ class MedicalFileViewModel extends ChangeNotifier { setIsPatientMedicalReportsLoading(bool val) { if (val) { patientMedicalReportList.clear(); + patientMedicalReportPDFBase64 = ""; } isPatientMedicalReportsListLoading = val; notifyListeners(); @@ -179,6 +182,7 @@ class MedicalFileViewModel extends ChangeNotifier { patientMedicalReportReadyList = patientMedicalReportList.where((element) => element.status == 2).toList(); patientMedicalReportCancelledList = patientMedicalReportList.where((element) => element.status == 4).toList(); } + onMedicalReportTabChange(0); isPatientMedicalReportsListLoading = false; notifyListeners(); if (onSuccess != null) { @@ -188,4 +192,29 @@ class MedicalFileViewModel extends ChangeNotifier { }, ); } + + Future getPatientMedicalReportPDF(PatientMedicalReportResponseModel patientMedicalReportResponseModel, AuthenticatedUser authenticatedUser, + {Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await medicalFileRepo.getPatientMedicalReportPDF(patientMedicalReportResponseModel, authenticatedUser); + + result.fold( + (failure) async => await errorHandlerService.handleError( + failure: failure, + onOkPressed: () { + onError!(failure.message); + }, + ), + (apiResponse) { + if (apiResponse.messageStatus == 2) { + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + patientMedicalReportPDFBase64 = apiResponse.data!; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } } diff --git a/lib/main.dart b/lib/main.dart index 8c6e05b..901c529 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -9,6 +9,7 @@ import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; +import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart'; import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; @@ -119,6 +120,12 @@ void main() async { errorHandlerService: getIt(), ), ), + ChangeNotifierProvider( + create: (_) => BookAppointmentsViewModel( + bookAppointmentsRepo: getIt(), + errorHandlerService: getIt(), + ), + ), ChangeNotifierProvider( create: (_) => AuthenticationViewModel( authenticationRepo: getIt(), diff --git a/lib/presentation/appointments/appointment_payment_page.dart b/lib/presentation/appointments/appointment_payment_page.dart index baa2af3..a617647 100644 --- a/lib/presentation/appointments/appointment_payment_page.dart +++ b/lib/presentation/appointments/appointment_payment_page.dart @@ -370,7 +370,7 @@ class _AppointmentPaymentPageState extends State { onSuccess: (value) async { print(value); await myAppointmentsViewModel.addAdvanceNumberRequest( - advanceNumber: Utils.isVidaPlusProject(appState, widget.patientAppointmentHistoryResponseModel.projectID) + advanceNumber: Utils.isVidaPlusProject(widget.patientAppointmentHistoryResponseModel.projectID) ? value.data['OnlineCheckInAppointments'][0]['AdvanceNumber_VP'].toString() : value.data['OnlineCheckInAppointments'][0]['AdvanceNumber'].toString(), paymentReference: payfortViewModel.payfortCheckPaymentStatusResponseModel!.fortId!, diff --git a/lib/presentation/book_appointment/book_appointment_page.dart b/lib/presentation/book_appointment/book_appointment_page.dart new file mode 100644 index 0000000..0f9a325 --- /dev/null +++ b/lib/presentation/book_appointment/book_appointment_page.dart @@ -0,0 +1,159 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/book_appointment/select_clinic_page.dart'; +import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; +import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; +import 'package:provider/provider.dart'; + +class BookAppointmentPage extends StatefulWidget { + const BookAppointmentPage({super.key}); + + @override + State createState() => _BookAppointmentPageState(); +} + +class _BookAppointmentPageState extends State { + late AppState appState; + + @override + Widget build(BuildContext context) { + appState = getIt.get(); + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: CollapsingListView( + title: LocaleKeys.bookAppo.tr(context: context), + child: SingleChildScrollView( + child: Consumer(builder: (context, bookAppointmentsVM, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 16.h), + CustomTabBar( + activeTextColor: Color(0xffED1C2B), + activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), + tabs: [ + CustomTabBarModel(null, "General".needTranslation), + CustomTabBarModel(null, "LiveCare".needTranslation), + ], + onTabChange: (index) { + bookAppointmentsVM.onTabChanged(index); + }, + ).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 24.h), + getSelectedTabData(bookAppointmentsVM.selectedTabIndex), + ], + ); + }), + ), + ), + ); + } + + Widget getSelectedTabData(int index) { + switch (index) { + case 0: + return Column( + children: [ + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: false, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.search_by_clinic_icon, width: 40.h, height: 40.h), + SizedBox(width: 12.h), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Search By Clinic".needTranslation.toText14(color: AppColors.textColor, weight: FontWeight.w500), + "Tap to select clinic".needTranslation.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), + ], + ), + ], + ), + Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 15.h, height: 15.h), + ], + ).onPress(() { + Navigator.of(context).push( + FadePage( + page: SelectClinicPage(), + ), + ); + }), + SizedBox(height: 16.h), + Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h), + SizedBox(height: 16.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.search_by_doctor_icon, width: 40.h, height: 40.h), + SizedBox(width: 12.h), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Search By Doctor".needTranslation.toText14(color: AppColors.textColor, weight: FontWeight.w500), + "Tap to select".needTranslation.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), + ], + ), + ], + ), + Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 15.h, height: 15.h), + ], + ).onPress(() {}), + SizedBox(height: 16.h), + Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 1.h), + SizedBox(height: 16.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.search_by_region_icon, width: 40.h, height: 40.h), + SizedBox(width: 12.h), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Search By Region".needTranslation.toText14(color: AppColors.textColor, weight: FontWeight.w500), + "Central Region".needTranslation.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), + ], + ), + ], + ), + Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 15.h, height: 15.h), + ], + ).onPress(() {}), + ], + ), + ), + ), + ], + ).paddingSymmetrical(24.h, 0.h); + default: + SizedBox.shrink(); + } + return Container(); + } +} diff --git a/lib/presentation/book_appointment/select_clinic_page.dart b/lib/presentation/book_appointment/select_clinic_page.dart new file mode 100644 index 0000000..496f7d5 --- /dev/null +++ b/lib/presentation/book_appointment/select_clinic_page.dart @@ -0,0 +1,29 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; + +class SelectClinicPage extends StatefulWidget { + const SelectClinicPage({super.key}); + + @override + State createState() => _SelectClinicPageState(); +} + +class _SelectClinicPageState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: CollapsingListView( + title: LocaleKeys.selectClinic.tr(context: context), + child: SingleChildScrollView( + child: Column( + children: [], + ), + ), + ), + ); + } +} diff --git a/lib/presentation/home/navigation_screen.dart b/lib/presentation/home/navigation_screen.dart index 152bbd2..bdce393 100644 --- a/lib/presentation/home/navigation_screen.dart +++ b/lib/presentation/home/navigation_screen.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/presentation/book_appointment/book_appointment_page.dart'; import 'package:hmg_patient_app_new/presentation/home/landing_page.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart'; import 'package:hmg_patient_app_new/widgets/bottom_navigation/bottom_navigation.dart'; @@ -23,7 +24,7 @@ class _LandingNavigationState extends State { children: [ const LandingPage(), MedicalFilePage(), - const LandingPage(), + const BookAppointmentPage(), const LandingPage(), const LandingPage(), ], diff --git a/lib/presentation/home/widgets/small_service_card.dart b/lib/presentation/home/widgets/small_service_card.dart index f988e18..6e1c588 100644 --- a/lib/presentation/home/widgets/small_service_card.dart +++ b/lib/presentation/home/widgets/small_service_card.dart @@ -2,8 +2,10 @@ import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/presentation/appointments/my_doctors_page.dart'; import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_orders_page.dart'; +import 'package:hmg_patient_app_new/presentation/medical_file/patient_sickleaves_list_page.dart'; import 'package:hmg_patient_app_new/presentation/prescriptions/prescriptions_list_page.dart'; import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; @@ -85,6 +87,22 @@ class SmallServiceCard extends StatelessWidget { ), ); break; + + case "my_doctors": + Navigator.of(context).push( + FadePage( + page: MyDoctorsPage(), + ), + ); + break; + + case "sick_leaves": + Navigator.of(context).push( + FadePage( + page: PatientSickleavesListPage(), + ), + ); + break; default: // Handle unknown service break; diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index 22aff4d..48193e8 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -24,6 +24,7 @@ import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.d import 'package:hmg_patient_app_new/presentation/insurance/widgets/patient_insurance_card.dart'; import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/medical_reports_page.dart'; +import 'package:hmg_patient_app_new/presentation/medical_file/patient_sickleaves_list_page.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/vaccine_list_page.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/widgets/lab_rad_card.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/widgets/medical_file_card.dart'; @@ -700,7 +701,7 @@ class _MedicalFilePageState extends State { ).onPress(() { Navigator.of(context).push( FadePage( - page: VaccineListPage(), + page: PatientSickleavesListPage(), ), ); }), diff --git a/lib/presentation/medical_file/medical_reports_page.dart b/lib/presentation/medical_file/medical_reports_page.dart index c30f9af..ea3c34d 100644 --- a/lib/presentation/medical_file/medical_reports_page.dart +++ b/lib/presentation/medical_file/medical_reports_page.dart @@ -57,6 +57,7 @@ class _MedicalReportsPageState extends State { return medicalFileViewModel.isPatientMedicalReportsListLoading ? PatientMedicalReportCard( patientMedicalReportResponseModel: PatientMedicalReportResponseModel(), + medicalFileViewModel: medicalFileVM, isLoading: true, ).paddingSymmetrical(24.h, 0.h) : AnimationConfiguration.staggeredList( @@ -71,6 +72,8 @@ class _MedicalReportsPageState extends State { decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), child: PatientMedicalReportCard( patientMedicalReportResponseModel: medicalFileVM.patientMedicalReportList[index], + medicalFileViewModel: medicalFileVM, + isLoading: false, ), ).paddingSymmetrical(24.h, 0.h), diff --git a/lib/presentation/medical_file/widgets/patient_medical_report_card.dart b/lib/presentation/medical_file/widgets/patient_medical_report_card.dart index 3f890e6..eb1730c 100644 --- a/lib/presentation/medical_file/widgets/patient_medical_report_card.dart +++ b/lib/presentation/medical_file/widgets/patient_medical_report_card.dart @@ -1,25 +1,35 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/cupertino.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/models/patient_medical_response_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; +import 'package:open_filex/open_filex.dart'; +import 'package:share_plus/share_plus.dart'; class PatientMedicalReportCard extends StatelessWidget { - PatientMedicalReportCard({super.key, required this.patientMedicalReportResponseModel, this.isLoading = false}); + PatientMedicalReportCard({super.key, required this.patientMedicalReportResponseModel, required this.medicalFileViewModel, this.isLoading = false}); PatientMedicalReportResponseModel patientMedicalReportResponseModel; + MedicalFileViewModel medicalFileViewModel; bool isLoading = true; @override Widget build(BuildContext context) { + AppState _appState = getIt.get(); return Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.whiteColor, @@ -78,7 +88,9 @@ class PatientMedicalReportCard extends StatelessWidget { Expanded( child: CustomButton( text: "Share", - onPressed: () {}, + onPressed: () { + getMedicalReportPDF(true, context, _appState); + }, backgroundColor: AppColors.secondaryLightRedColor, borderColor: AppColors.secondaryLightRedColor, textColor: AppColors.primaryRedColor, @@ -95,7 +107,9 @@ class PatientMedicalReportCard extends StatelessWidget { Expanded( child: CustomButton( text: "Download", - onPressed: () {}, + onPressed: () async { + getMedicalReportPDF(false, context, _appState); + }, backgroundColor: AppColors.secondaryLightRedColor, borderColor: AppColors.secondaryLightRedColor, textColor: AppColors.primaryRedColor, @@ -117,4 +131,29 @@ class PatientMedicalReportCard extends StatelessWidget { ), ); } + + void getMedicalReportPDF(bool isShare, BuildContext context, AppState _appState) async { + LoaderBottomSheet.showLoader(); + await medicalFileViewModel.getPatientMedicalReportPDF(patientMedicalReportResponseModel, _appState.getAuthenticatedUser()!).then((val) async { + LoaderBottomSheet.hideLoader(); + if (medicalFileViewModel.patientMedicalReportPDFBase64.isNotEmpty) { + String path = await Utils.createFileFromString(medicalFileViewModel.patientMedicalReportPDFBase64, "pdf"); + if (isShare) { + Share.shareXFiles([XFile(path)], text: "Medical Report"); + } else { + try { + OpenFilex.open(path); + } catch (ex) { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: "Cannot open file".needTranslation), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } + } + } + }); + } }