From e37eb78f815e8cf410b265fbbd1b9f0b3861ee04 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Tue, 9 Sep 2025 15:40:14 +0300 Subject: [PATCH 1/3] my appointments changes implementation contd. --- assets/images/svg/lab_result_icon.svg | 7 +- assets/images/svg/prescription_item_icon.svg | 6 + assets/images/svg/radiology_icon.svg | 4 + assets/images/svg/rebook_appointment_icon.svg | 4 + assets/images/svg/report_icon.svg | 3 + lib/core/app_assets.dart | 4 + .../authentication/authentication_repo.dart | 2 +- .../authentication_view_model.dart | 4 +- lib/features/insurance/insurance_repo.dart | 2 +- .../my_appointments/my_appointments_repo.dart | 48 +- .../my_appointments_view_model.dart | 51 +- .../utils/appointment_type.dart | 19 + .../prescriptions/prescriptions_repo.dart | 41 +- .../prescriptions_view_model.dart | 1 - .../appointment_details_page.dart | 501 ++++++++++++++---- .../appointment_payment_page.dart | 32 +- .../appointments/my_appointments_page.dart | 177 +++++-- .../widgets/appointment_card.dart | 142 +++-- .../widgets/appointment_doctor_card.dart | 50 +- .../insurance_update_details_card.dart | 41 +- .../widgets/patient_insurance_card.dart | 44 +- .../medical_file/medical_file_page.dart | 42 +- .../widgets/medical_file_card.dart | 11 +- .../prescription_detail_page.dart | 87 +-- 24 files changed, 883 insertions(+), 440 deletions(-) create mode 100644 assets/images/svg/prescription_item_icon.svg create mode 100644 assets/images/svg/radiology_icon.svg create mode 100644 assets/images/svg/rebook_appointment_icon.svg create mode 100644 assets/images/svg/report_icon.svg diff --git a/assets/images/svg/lab_result_icon.svg b/assets/images/svg/lab_result_icon.svg index 245048d..a8c442d 100644 --- a/assets/images/svg/lab_result_icon.svg +++ b/assets/images/svg/lab_result_icon.svg @@ -1,4 +1,5 @@ - - - + + + + diff --git a/assets/images/svg/prescription_item_icon.svg b/assets/images/svg/prescription_item_icon.svg new file mode 100644 index 0000000..6aba05f --- /dev/null +++ b/assets/images/svg/prescription_item_icon.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/images/svg/radiology_icon.svg b/assets/images/svg/radiology_icon.svg new file mode 100644 index 0000000..e1ae996 --- /dev/null +++ b/assets/images/svg/radiology_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/svg/rebook_appointment_icon.svg b/assets/images/svg/rebook_appointment_icon.svg new file mode 100644 index 0000000..da4533a --- /dev/null +++ b/assets/images/svg/rebook_appointment_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/assets/images/svg/report_icon.svg b/assets/images/svg/report_icon.svg new file mode 100644 index 0000000..97b6db8 --- /dev/null +++ b/assets/images/svg/report_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index eff2ccc..eec83ba 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -85,6 +85,10 @@ class AppAssets { static const String directions_icon = '$svgBasePath/directions_icon.svg'; static const String apple_pay_button = '$svgBasePath/pay_with_apple_pay.svg'; static const String bell = '$svgBasePath/bell.svg'; + static const String rebook_appointment_icon = '$svgBasePath/rebook_appointment_icon.svg'; + static const String report_icon = '$svgBasePath/report_icon.svg'; + static const String radiology_icon = '$svgBasePath/radiology_icon.svg'; + static const String prescription_item_icon = '$svgBasePath/prescription_item_icon.svg'; //bottom navigation// diff --git a/lib/features/authentication/authentication_repo.dart b/lib/features/authentication/authentication_repo.dart index 21061dc..21415df 100644 --- a/lib/features/authentication/authentication_repo.dart +++ b/lib/features/authentication/authentication_repo.dart @@ -38,7 +38,7 @@ class AuthenticationRepoImp implements AuthenticationRepo { @override Future>> selectDeviceByImei({required String firebaseToken}) async { - final mapDevice = {"IMEI": firebaseToken}; + Map mapDevice = {"IMEI": firebaseToken}; try { GenericApiModel? apiResponse; Failure? failure; diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index c451c40..d324a71 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -167,8 +167,8 @@ class AuthenticationViewModel extends ChangeNotifier { Future selectDeviceImei({required Function(dynamic data) onSuccess, Function(String)? onError}) async { // LoadingUtils.showFullScreenLoading(); // String firebaseToken = _appState.deviceToken; - // String firebaseToken = await Utils.getStringFromPrefs(CacheConst.pushToken); - String firebaseToken = "fY1fq_cITMmUCztA3UKKL9:APA91bEb2ZcdCPQPq3QsA0NW6a6btFvN-JjB1Pn3ZCoCzBMmVUhhh1ZQMtRn9tYPQ5G-jHDLiEpVAlBuRCVMkLDxa-zijsqbIui-4A-ynwclDWGFT4bUHTc"; + String firebaseToken = await Utils.getStringFromPrefs(CacheConst.pushToken); + // String firebaseToken = "fY1fq_cITMmUCztA3UKKL9:APA91bEb2ZcdCPQPq3QsA0NW6a6btFvN-JjB1Pn3ZCoCzBMmVUhhh1ZQMtRn9tYPQ5G-jHDLiEpVAlBuRCVMkLDxa-zijsqbIui-4A-ynwclDWGFT4bUHTc"; // == "" // ? "dOGRRszQQMGe_9wA5Hx3kO:APA91bFV5IcIJXvcCXXk0tc2ddtZgWwCPq7sGSuPr-YW7iiJpQZKgFGN9GAzCVOWL8MfheaP1slE8MdxB7lczdPBGdONQ7WbMmhgHcsUCUktq-hsapGXXqc" // : _appState.deviceToken; diff --git a/lib/features/insurance/insurance_repo.dart b/lib/features/insurance/insurance_repo.dart index 68b53e4..874cc28 100644 --- a/lib/features/insurance/insurance_repo.dart +++ b/lib/features/insurance/insurance_repo.dart @@ -106,7 +106,7 @@ class InsuranceRepoImp implements InsuranceRepo { @override Future>> getPatientInsuranceDetailsForUpdate({required String patientId, required String identificationNo}) async { - final mapDevice = {"SetupID": "010266", "ProjectID": 15, "PatientIdentificationID": identificationNo, "IsFamily": false, "ParentID": 0}; + Map mapDevice = {"SetupID": "010266", "ProjectID": 15, "PatientIdentificationID": identificationNo, "IsFamily": false, "ParentID": 0}; try { GenericApiModel? apiResponse; diff --git a/lib/features/my_appointments/my_appointments_repo.dart b/lib/features/my_appointments/my_appointments_repo.dart index 0815b94..8fc477c 100644 --- a/lib/features/my_appointments/my_appointments_repo.dart +++ b/lib/features/my_appointments/my_appointments_repo.dart @@ -8,8 +8,7 @@ import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/ import 'package:hmg_patient_app_new/services/logger_service.dart'; abstract class MyAppointmentsRepo { - Future>>> getPatientAppointments( - {required bool isActiveAppointment, required bool isArrivedAppointments}); + Future>>> getPatientAppointments({required bool isActiveAppointment, required bool isArrivedAppointments}); Future>> getPatientShareAppointment({required int projectID, required int clinicID, required String appointmentNo}); @@ -28,6 +27,8 @@ abstract class MyAppointmentsRepo { Future>> generateAppointmentQR({required int clinicID, required int projectID, required String appointmentNo, required int isFollowUp}); Future>> cancelAppointment({required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel}); + + Future>> confirmAppointment({required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel}); } class MyAppointmentsRepoImp implements MyAppointmentsRepo { @@ -37,8 +38,7 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo { MyAppointmentsRepoImp({required this.loggerService, required this.apiClient}); @override - Future>>> getPatientAppointments( - {required bool isActiveAppointment, required bool isArrivedAppointments}) async { + Future>>> getPatientAppointments({required bool isActiveAppointment, required bool isArrivedAppointments}) async { Map mapDevice = { "IsActiveAppointment": isActiveAppointment, "isDentalAllowedBackend": false, @@ -310,4 +310,44 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future> confirmAppointment({required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel}) async { + Map requestBody = { + "AppointmentNumber": patientAppointmentHistoryResponseModel.appointmentNo, + "IsLiveCareAppointment": patientAppointmentHistoryResponseModel.isLiveCareAppointment, + "ClinicID": patientAppointmentHistoryResponseModel.clinicID, + "ProjectID": patientAppointmentHistoryResponseModel.projectID, + "ConfirmationBy": 102, + }; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + CONFIRM_APPOINTMENT, + body: requestBody, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + 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())); + } + } } diff --git a/lib/features/my_appointments/my_appointments_view_model.dart b/lib/features/my_appointments/my_appointments_view_model.dart index 5c952e9..2815977 100644 --- a/lib/features/my_appointments/my_appointments_view_model.dart +++ b/lib/features/my_appointments/my_appointments_view_model.dart @@ -14,6 +14,10 @@ class MyAppointmentsViewModel extends ChangeNotifier { bool isAppointmentPatientShareLoading = false; List patientAppointmentsHistoryList = []; + + List patientUpcomingAppointmentsHistoryList = []; + List patientArrivedAppointmentsHistoryList = []; + PatientAppointmentShareResponseModel? patientAppointmentShareResponseModel; MyAppointmentsViewModel({required this.myAppointmentsRepo, required this.errorHandlerService}); @@ -25,6 +29,8 @@ class MyAppointmentsViewModel extends ChangeNotifier { initAppointmentsViewModel() { patientAppointmentsHistoryList.clear(); + patientUpcomingAppointmentsHistoryList.clear(); + patientArrivedAppointmentsHistoryList.clear(); isMyAppointmentsLoading = true; isAppointmentPatientShareLoading = true; notifyListeners(); @@ -50,6 +56,7 @@ class MyAppointmentsViewModel extends ChangeNotifier { Future getPatientAppointments(bool isActiveAppointment, bool isArrivedAppointments, {Function(dynamic)? onSuccess, Function(String)? onError}) async { final result = await myAppointmentsRepo.getPatientAppointments(isActiveAppointment: isActiveAppointment, isArrivedAppointments: isArrivedAppointments); + final resultArrived = await myAppointmentsRepo.getPatientAppointments(isActiveAppointment: false, isArrivedAppointments: true); result.fold( (failure) async => await errorHandlerService.handleError(failure: failure), @@ -57,7 +64,23 @@ class MyAppointmentsViewModel extends ChangeNotifier { if (apiResponse.messageStatus == 2) { // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); } else if (apiResponse.messageStatus == 1) { - patientAppointmentsHistoryList = apiResponse.data!; + patientUpcomingAppointmentsHistoryList = apiResponse.data!; + isMyAppointmentsLoading = false; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + + resultArrived.fold( + (failure) async => await errorHandlerService.handleError(failure: failure), + (apiResponse) { + if (apiResponse.messageStatus == 2) { + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + patientArrivedAppointmentsHistoryList = apiResponse.data!; isMyAppointmentsLoading = false; notifyListeners(); if (onSuccess != null) { @@ -66,6 +89,13 @@ class MyAppointmentsViewModel extends ChangeNotifier { } }, ); + + patientAppointmentsHistoryList.addAll(patientUpcomingAppointmentsHistoryList); + patientAppointmentsHistoryList.addAll(patientArrivedAppointmentsHistoryList); + + print('Upcoming Appointments: ${patientUpcomingAppointmentsHistoryList.length}'); + print('Arrived Appointments: ${patientArrivedAppointmentsHistoryList.length}'); + print('All Appointments: ${patientAppointmentsHistoryList.length}'); } Future getPatientShareAppointment(int projectID, int clinicID, String appointmentNo, {Function(dynamic)? onSuccess, Function(String)? onError}) async { @@ -145,6 +175,25 @@ class MyAppointmentsViewModel extends ChangeNotifier { ); } + Future confirmAppointment({required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await myAppointmentsRepo.confirmAppointment(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel); + + result.fold( + (failure) async => await errorHandlerService.handleError(failure: failure), + (apiResponse) { + if (apiResponse.messageStatus == 2) { + onError!(apiResponse.errorMessage!); + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } + Future createAdvancePayment( {required String paymentMethodName, required int projectID, diff --git a/lib/features/my_appointments/utils/appointment_type.dart b/lib/features/my_appointments/utils/appointment_type.dart index cd8bb56..abc23dc 100644 --- a/lib/features/my_appointments/utils/appointment_type.dart +++ b/lib/features/my_appointments/utils/appointment_type.dart @@ -43,6 +43,25 @@ class AppointmentType { } } + static Color getNextActionTextColor(nextAction) { + switch (nextAction) { + case 0: + return AppColors.successColor; + case 10: + return AppColors.successColor; + case 15: + return AppColors.textColor; + case 20: + return AppColors.infoColor; + case 50: + return AppColors.successColor; + case 90: + return AppColors.alertColor; + default: + return AppColors.successColor; + } + } + static Color getNextActionButtonColor(nextAction) { switch (nextAction) { case 0: diff --git a/lib/features/prescriptions/prescriptions_repo.dart b/lib/features/prescriptions/prescriptions_repo.dart index f584f7b..5f2db30 100644 --- a/lib/features/prescriptions/prescriptions_repo.dart +++ b/lib/features/prescriptions/prescriptions_repo.dart @@ -21,23 +21,7 @@ class PrescriptionsRepoImp implements PrescriptionsRepo { @override Future>>> getPatientPrescriptionOrders({required String patientId}) async { - final mapDevice = { - "isDentalAllowedBackend": false, - "VersionID": 50.0, - "Channel": 3, - "LanguageID": 2, - "IPAdress": "10.20.10.20", - "generalid": "Cs2020@2016\$2958", - "Latitude": 0.0, - "Longitude": 0.0, - "DeviceTypeID": 1, - "PatientType": 1, - "PatientTypeID": 1, - "TokenID": "@dm!n", - "PatientID": "1018977", - "PatientOutSA": "0", - "SessionID": "03478TYC02N80874CTYN04883475!?" - }; + Map mapDevice = {}; try { GenericApiModel>? apiResponse; @@ -52,7 +36,7 @@ class PrescriptionsRepoImp implements PrescriptionsRepo { try { final list = response['PatientPrescriptionList']; if (list == null || list.isEmpty) { - throw Exception("lab list is empty"); + // throw Exception("lab list is empty"); } final prescriptionOrders = list.map((item) => PatientPrescriptionsResponseModel.fromJson(item as Map)).toList().cast(); @@ -78,28 +62,13 @@ class PrescriptionsRepoImp implements PrescriptionsRepo { @override Future>>> getPatientPrescriptionDetails({required PatientPrescriptionsResponseModel prescriptionsResponseModel}) async { - final mapDevice = { + Map mapDevice = { "AppointmentNo": prescriptionsResponseModel.appointmentNo.toString(), "SetupID": prescriptionsResponseModel.setupID, "EpisodeID": prescriptionsResponseModel.episodeID.toString(), "ClinicID": prescriptionsResponseModel.clinicID.toString(), "ProjectID": prescriptionsResponseModel.projectID.toString(), - "DischargeNo": prescriptionsResponseModel.dischargeNo.toString(), - "isDentalAllowedBackend": false, - "VersionID": 50.0, - "Channel": 3, - "LanguageID": 2, - "IPAdress": "10.20.10.20", - "generalid": "Cs2020@2016\$2958", - "Latitude": 0.0, - "Longitude": 0.0, - "DeviceTypeID": 1, - "PatientType": 1, - "PatientTypeID": 1, - "TokenID": "@dm!n", - "PatientID": "1018977", - "PatientOutSA": "0", - "SessionID": "03478TYC02N80874CTYN04883475!?" + "DischargeNo": prescriptionsResponseModel.dischargeNo.toString() }; try { @@ -115,7 +84,7 @@ class PrescriptionsRepoImp implements PrescriptionsRepo { try { final list = prescriptionsResponseModel.isInOutPatient! ? response['ListPRM'] : response['INP_GetPrescriptionReport_List']; if (list == null || list.isEmpty) { - throw Exception("prescription list is empty"); + // throw Exception("prescription list is empty"); } final prescriptionOrders = list.map((item) => PrescriptionDetailResponseModel.fromJson(item as Map)).toList().cast(); diff --git a/lib/features/prescriptions/prescriptions_view_model.dart b/lib/features/prescriptions/prescriptions_view_model.dart index ff93f84..d57b17e 100644 --- a/lib/features/prescriptions/prescriptions_view_model.dart +++ b/lib/features/prescriptions/prescriptions_view_model.dart @@ -31,7 +31,6 @@ class PrescriptionsViewModel extends ChangeNotifier { patientPrescriptionOrdersByHospital.clear(); patientPrescriptionOrdersViewList.clear(); isPrescriptionsOrdersLoading = true; - isPrescriptionsDetailsLoading = true; isSortByClinic = true; getPatientPrescriptionOrders(); notifyListeners(); diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart index 4255c71..91a15a4 100644 --- a/lib/presentation/appointments/appointment_details_page.dart +++ b/lib/presentation/appointments/appointment_details_page.dart @@ -1,6 +1,9 @@ +import 'dart:async'; + 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/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; @@ -11,16 +14,23 @@ 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/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/appointments/appointment_payment_page.dart'; import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_doctor_card.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'; 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/shimmer/movies_shimmer_widget.dart'; import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; import 'package:maps_launcher/maps_launcher.dart'; import 'package:provider/provider.dart'; +import '../medical_file/widgets/medical_file_card.dart'; + class AppointmentDetailsPage extends StatefulWidget { AppointmentDetailsPage({super.key, required this.patientAppointmentHistoryResponseModel}); @@ -32,10 +42,23 @@ class AppointmentDetailsPage extends StatefulWidget { class _AppointmentDetailsPageState extends State { late MyAppointmentsViewModel myAppointmentsViewModel; + late PrescriptionsViewModel prescriptionsViewModel; + + @override + void initState() { + scheduleMicrotask(() { + if (AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)) { + prescriptionsViewModel.setPrescriptionsDetailsLoading(); + prescriptionsViewModel.getPrescriptionDetails(getPrescriptionRequestModel()); + } + }); + super.initState(); + } @override Widget build(BuildContext context) { myAppointmentsViewModel = Provider.of(context); + prescriptionsViewModel = Provider.of(context); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, appBar: AppBar( @@ -52,7 +75,23 @@ class _AppointmentDetailsPageState extends State { Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - "Appointment Details".needTranslation.toText24(isBold: true), + "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), @@ -74,8 +113,7 @@ class _AppointmentDetailsPageState extends State { Navigator.of(context).pop(); showCommonBottomSheet(context, child: Utils.getSuccessWidget(loadingText: "Appointment Cancelled Successfully".needTranslation), - callBackFunc: (str) { - }, + callBackFunc: (str) {}, title: "", height: ResponsiveExtension.screenHeight * 0.3, isCloseButtonVisible: false, @@ -89,106 +127,266 @@ class _AppointmentDetailsPageState extends State { onRescheduleTap: () {}, ), SizedBox(height: 16.h), - if (!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( + !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: [ - 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, - ), + Row( + children: [ + "Appointment Status".needTranslation.toText16(isBold: true), + ], ), - 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), - ), + 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), + ), + ), + ], ), ], ), - ], + ), ), - ), - ), - SizedBox(height: 16.h), - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 20.h, - hasShadow: false, - ), - child: Row( - mainAxisSize: MainAxisSize.max, - children: [ - Utils.buildSvgWithAssets(icon: AppAssets.prescription_reminder_icon, width: 35.h, height: 35.h), - SizedBox(width: 8.h), - 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: [ - LocaleKeys.setReminder.tr(context: context).toText13(isBold: true), - "Notify me before the appointment".needTranslation.toText11(color: AppColors.textColorLight, weight: FontWeight.w500), + 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), + ], + ), + const Spacer(), + Switch( + activeColor: AppColors.successColor, + activeTrackColor: AppColors.successColor.withValues(alpha: .15), + value: widget.patientAppointmentHistoryResponseModel.hasReminder!, + onChanged: (newValue) { + setState(() { + myAppointmentsViewModel.setAppointmentReminder(newValue, widget.patientAppointmentHistoryResponseModel); + }); + }, + ), ], - ), - const Spacer(), - Switch( - activeColor: AppColors.successColor, - activeTrackColor: AppColors.successColor.withValues(alpha: .15), - value: widget.patientAppointmentHistoryResponseModel.hasReminder!, - onChanged: (newValue) { - setState(() { - myAppointmentsViewModel.setAppointmentReminder(newValue, widget.patientAppointmentHistoryResponseModel); - }); - }, - ), - ], - ).paddingSymmetrical(16.h, 16.h), + ).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, + ), + ], + ), + ], + ), + ), + ), + ); + }, + 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), - ], - ), ], ).paddingSymmetrical(24.h, 24.h), ), @@ -242,28 +440,39 @@ class _AppointmentDetailsPageState extends State { ) ], ).paddingOnly(left: 16.h, top: 24.h, right: 16.h, bottom: 0.h), - CustomButton( - text: AppointmentType.getNextActionText(widget.patientAppointmentHistoryResponseModel.nextAction), - onPressed: () { - myAppointmentsViewModel.setIsPatientAppointmentShareLoading(true); - Navigator.of(context).push( - FadePage( - page: AppointmentPaymentPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), - ), - ); - }, - backgroundColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction), - borderColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.01), - textColor: AppColors.whiteColor, - fontSize: 16, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 50.h, - icon: AppointmentType.getNextActionIcon(widget.patientAppointmentHistoryResponseModel.nextAction), - iconColor: AppColors.whiteColor, - iconSize: 18.h, - ).paddingSymmetrical(16.h, 24.h), + AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) + ? CustomButton( + text: "Re-book Appointment".needTranslation, + 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.add_icon, + iconColor: AppColors.whiteColor, + iconSize: 18.h, + ).paddingSymmetrical(16.h, 24.h) + : CustomButton( + text: AppointmentType.getNextActionText(widget.patientAppointmentHistoryResponseModel.nextAction), + onPressed: () { + handleAppointmentNextAction(widget.patientAppointmentHistoryResponseModel.nextAction); + }, + backgroundColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction), + borderColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.01), + textColor: widget.patientAppointmentHistoryResponseModel.nextAction == 15 ? AppColors.textColor : AppColors.whiteColor, + fontSize: 16, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 50.h, + icon: AppointmentType.getNextActionIcon(widget.patientAppointmentHistoryResponseModel.nextAction), + iconColor: AppColors.whiteColor, + iconSize: 18.h, + ).paddingSymmetrical(16.h, 24.h), ], ), ), @@ -272,4 +481,64 @@ class _AppointmentDetailsPageState extends State { ), ); } + + Future handleAppointmentNextAction(nextAction) async { + switch (nextAction) { + case 0: + break; + case 10: + showCommonBottomSheet(context, + child: Utils.getLoadingWidget(), callBackFunc: (str) {}, title: "", height: ResponsiveExtension.screenHeight * 0.3, isCloseButtonVisible: false, isDismissible: false, isFullScreen: false); + await myAppointmentsViewModel.confirmAppointment( + patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, + onSuccess: (apiResponse) { + Navigator.of(context).pop(); + showCommonBottomSheet(context, + child: Utils.getSuccessWidget(loadingText: "Appointment Confirmed 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(); + case 15: + break; + case 20: + myAppointmentsViewModel.setIsPatientAppointmentShareLoading(true); + Navigator.of(context).push( + FadePage( + page: AppointmentPaymentPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), + ), + ); + case 50: + // return LocaleKeys.confirmLiveCare.tr(); + case 90: + // return LocaleKeys.checkinOption.tr(); + default: + // return "No Action".needTranslation; + } + } + + PatientPrescriptionsResponseModel getPrescriptionRequestModel() { + return PatientPrescriptionsResponseModel( + appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo, + setupID: widget.patientAppointmentHistoryResponseModel.setupID, + episodeID: widget.patientAppointmentHistoryResponseModel.episodeID, + clinicID: widget.patientAppointmentHistoryResponseModel.clinicID, + projectID: widget.patientAppointmentHistoryResponseModel.projectID, + dischargeNo: 0, + isInOutPatient: widget.patientAppointmentHistoryResponseModel.isInOutPatient, + isHomeMedicineDeliverySupported: false, + doctorImageURL: widget.patientAppointmentHistoryResponseModel.doctorImageURL, + doctorName: "${widget.patientAppointmentHistoryResponseModel.doctorTitle} ${widget.patientAppointmentHistoryResponseModel.doctorNameObj}", + appointmentDate: widget.patientAppointmentHistoryResponseModel.appointmentDate, + clinicDescription: widget.patientAppointmentHistoryResponseModel.clinicName, + decimalDoctorRate: widget.patientAppointmentHistoryResponseModel.decimalDoctorRate, + name: widget.patientAppointmentHistoryResponseModel.projectName, + ); + } } diff --git a/lib/presentation/appointments/appointment_payment_page.dart b/lib/presentation/appointments/appointment_payment_page.dart index 861d5b9..7f2ea9d 100644 --- a/lib/presentation/appointments/appointment_payment_page.dart +++ b/lib/presentation/appointments/appointment_payment_page.dart @@ -19,12 +19,16 @@ import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/ import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/payfort/payfort_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/home/navigation_screen.dart'; +import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.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'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/in_app_browser/InAppBrowser.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 'package:smooth_corner/smooth_corner.dart'; @@ -220,7 +224,13 @@ class _AppointmentPaymentPageState extends State { "Insurance expired or inactive".needTranslation.toText14(color: AppColors.primaryRedColor, weight: FontWeight.w500).paddingSymmetrical(24.h, 0.h), CustomButton( text: LocaleKeys.updateInsurance.tr(context: context), - onPressed: () {}, + onPressed: () { + Navigator.of(context).push( + FadePage( + page: InsuranceHomePage(), + ), + ); + }, backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.secondaryLightRedBorderColor, textColor: AppColors.whiteColor, @@ -366,14 +376,24 @@ class _AppointmentPaymentPageState extends State { clinicID: widget.patientAppointmentHistoryResponseModel.clinicID, projectID: widget.patientAppointmentHistoryResponseModel.projectID, appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), - isFollowUp: myAppointmentsViewModel.patientAppointmentShareResponseModel!.isFollowup!); + 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()), + ); + }); + }); } }); }); - Future.delayed(Duration(milliseconds: 500), () { - Navigator.of(context).pop(); - print(payfortViewModel.payfortCheckPaymentStatusResponseModel!.responseMessage!); - }); } else {} }); // checkPaymentStatus(appo); diff --git a/lib/presentation/appointments/my_appointments_page.dart b/lib/presentation/appointments/my_appointments_page.dart index 0850641..4a3a46e 100644 --- a/lib/presentation/appointments/my_appointments_page.dart +++ b/lib/presentation/appointments/my_appointments_page.dart @@ -64,49 +64,154 @@ class _MyAppointmentsPageState extends State { ], onTabChange: (index) { print(index); + myAppointmentsViewModel.onTabChange(index); }, ).paddingSymmetrical(24.h, 0.h), Consumer(builder: (context, myAppointmentsVM, child) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox(height: 24.h), - // Expandable list - ListView.separated( - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - itemCount: myAppointmentsVM.isMyAppointmentsLoading ? 5 : myAppointmentsVM.patientAppointmentsHistoryList.isNotEmpty ? myAppointmentsVM.patientAppointmentsHistoryList.length : 1, - itemBuilder: (context, index) { - return myAppointmentsVM.isMyAppointmentsLoading - ? const MoviesShimmerWidget().paddingSymmetrical(24.h, 0.h) - : myAppointmentsVM.patientAppointmentsHistoryList.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.patientAppointmentsHistoryList[index], - myAppointmentsViewModel: myAppointmentsViewModel, - ), - ).paddingSymmetrical(24.h, 0.h), - ), - ), - ) : Utils.getNoDataWidget(context); - }, - separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), - ), - ], - ); + return getSelectedTabData(myAppointmentsVM.selectedTabIndex, myAppointmentsVM); }), ], ), ), ); } + + Widget getSelectedTabData(int index, MyAppointmentsViewModel myAppointmentsVM) { + switch (index) { + case 0: + //All Appointments Tab Data + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 24.h), + // Expandable list + ListView.separated( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: myAppointmentsVM.isMyAppointmentsLoading + ? 5 + : myAppointmentsVM.patientAppointmentsHistoryList.isNotEmpty + ? myAppointmentsVM.patientAppointmentsHistoryList.length + : 1, + itemBuilder: (context, index) { + return myAppointmentsVM.isMyAppointmentsLoading + ? const MoviesShimmerWidget().paddingSymmetrical(24.h, 0.h) + : myAppointmentsVM.patientAppointmentsHistoryList.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.patientAppointmentsHistoryList[index], + myAppointmentsViewModel: myAppointmentsViewModel, + ), + ).paddingSymmetrical(24.h, 0.h), + ), + ), + ) + : Utils.getNoDataWidget(context); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + ), + ], + ); + case 1: + //Upcoming Appointments Tab Data + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 24.h), + // Expandable list + ListView.separated( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: myAppointmentsVM.isMyAppointmentsLoading + ? 5 + : myAppointmentsVM.patientUpcomingAppointmentsHistoryList.isNotEmpty + ? myAppointmentsVM.patientUpcomingAppointmentsHistoryList.length + : 1, + itemBuilder: (context, index) { + return myAppointmentsVM.isMyAppointmentsLoading + ? const MoviesShimmerWidget().paddingSymmetrical(24.h, 0.h) + : myAppointmentsVM.patientUpcomingAppointmentsHistoryList.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.patientUpcomingAppointmentsHistoryList[index], + myAppointmentsViewModel: myAppointmentsViewModel, + ), + ).paddingSymmetrical(24.h, 0.h), + ), + ), + ) + : Utils.getNoDataWidget(context); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + ), + ], + ); + case 2: + //Completed Appointments Tab Data + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 24.h), + // Expandable list + ListView.separated( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: myAppointmentsVM.isMyAppointmentsLoading + ? 5 + : myAppointmentsVM.patientArrivedAppointmentsHistoryList.isNotEmpty + ? 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); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + ), + ], + ); + default: + return Container(); + } + } } diff --git a/lib/presentation/appointments/widgets/appointment_card.dart b/lib/presentation/appointments/widgets/appointment_card.dart index 51af492..e9babeb 100644 --- a/lib/presentation/appointments/widgets/appointment_card.dart +++ b/lib/presentation/appointments/widgets/appointment_card.dart @@ -1,3 +1,4 @@ +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'; @@ -10,6 +11,7 @@ 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'; @@ -33,11 +35,16 @@ class _AppointmentCardState extends State { AppState appState = getIt.get(); return InkWell( onTap: () { - Navigator.of(context).push( + Navigator.of(context) + .push( FadePage( page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), ), - ); + ) + .then((val) { + widget.myAppointmentsViewModel.initAppointmentsViewModel(); + widget.myAppointmentsViewModel.getPatientAppointments(true, false); + }); }, child: Padding( padding: EdgeInsets.all(14.h), @@ -94,24 +101,26 @@ class _AppointmentCardState extends State { ), ), // TODO: Implement the logic to enable/disable the switch based on reminder status - Switch( - activeColor: AppColors.successColor, - activeTrackColor: AppColors.successColor.withValues(alpha: .15), - thumbIcon: WidgetStateProperty.resolveWith( - (Set states) { - if (states.contains(WidgetState.selected)) { - return const Icon(Icons.check); // Icon when switch is ON - } - return const Icon(Icons.close); // Icon when switch is OFF - }, - ), - value: widget.patientAppointmentHistoryResponseModel.hasReminder!, - onChanged: (newValue) { - setState(() { - widget.myAppointmentsViewModel.setAppointmentReminder(newValue, widget.patientAppointmentHistoryResponseModel); - }); - }, - ), + AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) + ? SizedBox() + : Switch( + activeColor: AppColors.successColor, + activeTrackColor: AppColors.successColor.withValues(alpha: .15), + thumbIcon: WidgetStateProperty.resolveWith( + (Set states) { + if (states.contains(WidgetState.selected)) { + return const Icon(Icons.check); // Icon when switch is ON + } + return const Icon(Icons.close); // Icon when switch is OFF + }, + ), + value: widget.patientAppointmentHistoryResponseModel.hasReminder!, + onChanged: (newValue) { + setState(() { + widget.myAppointmentsViewModel.setAppointmentReminder(newValue, widget.patientAppointmentHistoryResponseModel); + }); + }, + ), ], ), SizedBox(height: 16.h), @@ -155,30 +164,32 @@ class _AppointmentCardState extends State { children: [ Expanded( flex: 6, - child: CustomButton( - text: AppointmentType.getNextActionText(widget.patientAppointmentHistoryResponseModel.nextAction), - onPressed: () { - Navigator.of(context) - .push(FadePage( - page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), - )) - .then((val) { - widget.myAppointmentsViewModel.initAppointmentsViewModel(); - widget.myAppointmentsViewModel.getPatientAppointments(true, false); - }); - }, - backgroundColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.1), - borderColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.01), - textColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction), - fontSize: 14, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, - icon: AppointmentType.getNextActionIcon(widget.patientAppointmentHistoryResponseModel.nextAction), - iconColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction), - iconSize: 14.h, - ), + child: AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) + ? getArrivedAppointmentButton() + : CustomButton( + text: AppointmentType.getNextActionText(widget.patientAppointmentHistoryResponseModel.nextAction), + onPressed: () { + Navigator.of(context) + .push(FadePage( + page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), + )) + .then((val) { + widget.myAppointmentsViewModel.initAppointmentsViewModel(); + widget.myAppointmentsViewModel.getPatientAppointments(true, false); + }); + }, + backgroundColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.15), + borderColor: AppointmentType.getNextActionButtonColor(widget.patientAppointmentHistoryResponseModel.nextAction).withOpacity(0.01), + textColor: AppointmentType.getNextActionTextColor(widget.patientAppointmentHistoryResponseModel.nextAction), + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + icon: AppointmentType.getNextActionIcon(widget.patientAppointmentHistoryResponseModel.nextAction), + iconColor: AppointmentType.getNextActionTextColor(widget.patientAppointmentHistoryResponseModel.nextAction), + iconSize: 14.h, + ), ), SizedBox(width: 8.h), Expanded( @@ -200,11 +211,16 @@ class _AppointmentCardState extends State { ), ), ).onPress(() { - Navigator.of(context).push( + Navigator.of(context) + .push( FadePage( page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel), ), - ); + ) + .then((val) { + widget.myAppointmentsViewModel.initAppointmentsViewModel(); + widget.myAppointmentsViewModel.getPatientAppointments(true, false); + }); }), ), ], @@ -215,5 +231,39 @@ class _AppointmentCardState extends State { ); } + Widget getArrivedAppointmentButton() { + return DateTime.now().difference(DateUtil.convertStringToDate(widget.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 with same doctor".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, + ); + } + void performAppointmentNextAction(nextAction) {} } diff --git a/lib/presentation/appointments/widgets/appointment_doctor_card.dart b/lib/presentation/appointments/widgets/appointment_doctor_card.dart index 801fdca..e6d7338 100644 --- a/lib/presentation/appointments/widgets/appointment_doctor_card.dart +++ b/lib/presentation/appointments/widgets/appointment_doctor_card.dart @@ -59,10 +59,7 @@ class AppointmentDoctorCard extends StatelessWidget { icon: AppAssets.doctor_calendar_icon, labelText: "${DateUtil.formatDateToDate(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)}, ${DateUtil.formatDateToTimeLang(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate), false)}"), - AppCustomChipWidget( - icon: AppAssets.rating_icon, - iconColor: AppColors.ratingColorYellow, - labelText: "Rating: ${patientAppointmentHistoryResponseModel.decimalDoctorRate}"), + AppCustomChipWidget(icon: AppAssets.rating_icon, iconColor: AppColors.ratingColorYellow, labelText: "Rating: ${patientAppointmentHistoryResponseModel.decimalDoctorRate}"), ], ), ], @@ -82,20 +79,37 @@ class AppointmentDoctorCard extends StatelessWidget { Widget getAppointmentActionButtons(bool isAppointmentArrived) { if (isAppointmentArrived) { - return CustomButton( - text: LocaleKeys.askDoctor.tr(), - 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, - ); + return DateTime.now().difference(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate)).inDays <= 15 + ? CustomButton( + text: LocaleKeys.askDoctor.tr(), + 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.ask_doctor_icon, + iconColor: AppColors.primaryRedColor, + ) + : CustomButton( + text: "Rebook with same doctor".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, + ); } else { return Row( children: [ diff --git a/lib/presentation/insurance/widgets/insurance_update_details_card.dart b/lib/presentation/insurance/widgets/insurance_update_details_card.dart index 51afdc4..b6a38b2 100644 --- a/lib/presentation/insurance/widgets/insurance_update_details_card.dart +++ b/lib/presentation/insurance/widgets/insurance_update_details_card.dart @@ -10,6 +10,7 @@ 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/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/shimmer/movies_shimmer_widget.dart'; import 'package:provider/provider.dart'; @@ -68,42 +69,14 @@ class PatientInsuranceCardUpdateCard extends StatelessWidget { Wrap( direction: Axis.horizontal, spacing: 4.h, - runSpacing: 4.h, + runSpacing: -8.h, children: [ - Row( - children: [ - CustomButton( - icon: AppAssets.doctor_calendar_icon, - iconColor: AppColors.blackColor, - iconSize: 13.h, - text: "${LocaleKeys.expiryOn.tr(context: context)} ${insuranceViewModel.patientInsuranceUpdateResponseModel!.effectiveTo}", - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - ], + AppCustomChipWidget( + icon: AppAssets.doctor_calendar_icon, + labelText: "${LocaleKeys.expiryOn.tr(context: context)} ${insuranceViewModel.patientInsuranceUpdateResponseModel!.effectiveTo}", ), - Row( - children: [ - CustomButton( - text: "Member ID: ${insuranceViewModel.patientInsuranceUpdateResponseModel!.memberID!}", - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - ], + AppCustomChipWidget( + labelText: "Member ID: ${insuranceViewModel.patientInsuranceUpdateResponseModel!.memberID!}", ), ], ), diff --git a/lib/presentation/insurance/widgets/patient_insurance_card.dart b/lib/presentation/insurance/widgets/patient_insurance_card.dart index 899016e..720c452 100644 --- a/lib/presentation/insurance/widgets/patient_insurance_card.dart +++ b/lib/presentation/insurance/widgets/patient_insurance_card.dart @@ -13,6 +13,7 @@ import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/insurance/widgets/insurance_update_details_card.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:provider/provider.dart'; @@ -73,44 +74,13 @@ class PatientInsuranceCard extends StatelessWidget { SizedBox(height: 8.h), Wrap( direction: Axis.horizontal, - spacing: 6.h, - runSpacing: 6.h, + spacing: 4.h, + runSpacing: -8.h, children: [ - Row( - children: [ - CustomButton( - icon: AppAssets.doctor_calendar_icon, - iconColor: AppColors.blackColor, - iconSize: 13.h, - text: "${LocaleKeys.expiryDate.tr(context: context)} ${DateUtil.formatDateToDate(DateUtil.convertStringToDate(insuranceCardDetailsModel.cardValidTo), false)}", - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - ], - ), - Row( - children: [ - CustomButton( - text: "Patient Card ID: ${insuranceCardDetailsModel.patientCardID}", - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - ], - ), + AppCustomChipWidget( + icon: AppAssets.doctor_calendar_icon, + labelText: "${LocaleKeys.expiryDate.tr(context: context)} ${DateUtil.formatDateToDate(DateUtil.convertStringToDate(insuranceCardDetailsModel.cardValidTo), false)}"), + AppCustomChipWidget(labelText: "Patient Card ID: ${insuranceCardDetailsModel.patientCardID}"), ], ), SizedBox(height: 10.h), diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index d6cbd14..47951c4 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -167,22 +167,22 @@ class _MedicalFilePageState extends State { 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(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), @@ -255,7 +255,7 @@ class _MedicalFilePageState extends State { ? const MoviesShimmerWidget().paddingSymmetrical(24.h, 0.0) : PatientInsuranceCard( insuranceCardDetailsModel: insuranceVM.patientInsuranceList.first, - isInsuranceExpired: DateTime.now().isBefore(DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo))); + isInsuranceExpired: DateTime.now().isAfter(DateUtil.convertStringToDate(insuranceVM.patientInsuranceList.first.cardValidTo))); }), SizedBox(height: 10.h), GridView( @@ -264,10 +264,10 @@ class _MedicalFilePageState extends State { padding: EdgeInsets.only(top: 12), shrinkWrap: true, children: [ - MedicalFileCard(label: "Update Insurance", textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.eye_result_icon), - MedicalFileCard(label: "Insurance Approvals", textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.eye_result_icon), - MedicalFileCard(label: "My Invoices List", textColor: AppColors.blackColor, backgroundColor: AppColors.whiteColor, svgIcon: AppAssets.eye_result_icon), - MedicalFileCard(label: "Ancillary Orders List", 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), + 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), diff --git a/lib/presentation/medical_file/widgets/medical_file_card.dart b/lib/presentation/medical_file/widgets/medical_file_card.dart index 92bb4ee..82f38ec 100644 --- a/lib/presentation/medical_file/widgets/medical_file_card.dart +++ b/lib/presentation/medical_file/widgets/medical_file_card.dart @@ -6,18 +6,19 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; class MedicalFileCard extends StatelessWidget { final String label; - - // final Color svgColor; final Color textColor; final Color backgroundColor; final String svgIcon; + final num iconSize; + bool isLargeText; MedicalFileCard({ required this.label, - // required this.svgColor, required this.textColor, required this.backgroundColor, this.svgIcon = "", + this.iconSize = 30, + this.isLargeText = false }); @override @@ -33,9 +34,9 @@ class MedicalFileCard extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, mainAxisAlignment: MainAxisAlignment.center, children: [ - Utils.buildSvgWithAssets(icon: svgIcon, width: 30.h, height: 30.h, fit: BoxFit.contain), + Utils.buildSvgWithAssets(icon: svgIcon, width: iconSize.h, height: iconSize.h, fit: BoxFit.contain), SizedBox(height: 12.h), - label.toText11(color: textColor, isBold: true), + isLargeText ? label.toText14(color: textColor, isBold: true) : label.toText11(color: textColor, isBold: true), ], ), ), diff --git a/lib/presentation/prescriptions/prescription_detail_page.dart b/lib/presentation/prescriptions/prescription_detail_page.dart index 1f8e0a2..3bd9273 100644 --- a/lib/presentation/prescriptions/prescription_detail_page.dart +++ b/lib/presentation/prescriptions/prescription_detail_page.dart @@ -14,6 +14,7 @@ import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_mo 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/shimmer/movies_shimmer_widget.dart'; import 'package:provider/provider.dart'; @@ -34,6 +35,7 @@ class _PrescriptionDetailPageState extends State { @override void initState() { scheduleMicrotask(() { + prescriptionsViewModel.setPrescriptionsDetailsLoading(); prescriptionsViewModel.getPrescriptionDetails(widget.prescriptionsResponseModel); }); super.initState(); @@ -85,82 +87,23 @@ class _PrescriptionDetailPageState extends State { SizedBox(height: 16.h), Wrap( direction: Axis.horizontal, - spacing: 6.h, - runSpacing: 6.h, + spacing: 4.h, + runSpacing: -8.h, children: [ - Row( - mainAxisSize: MainAxisSize.min, - children: [ - CustomButton( - icon: AppAssets.doctor_calendar_icon, - iconColor: AppColors.textColor, - iconSize: 13.h, - text: DateUtil.formatDateToDate(DateUtil.convertStringToDate(widget.prescriptionsResponseModel.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: 30.h, - ), - ], + AppCustomChipWidget( + icon: AppAssets.doctor_calendar_icon, + labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(widget.prescriptionsResponseModel.appointmentDate), false), ), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - CustomButton( - text: widget.prescriptionsResponseModel.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: 30.h, - ), - ], + AppCustomChipWidget( + labelText: widget.prescriptionsResponseModel.clinicDescription!, ), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - CustomButton( - icon: AppAssets.rating_icon, - iconColor: AppColors.ratingColorYellow, - iconSize: 13.h, - text: "Rating: ${widget.prescriptionsResponseModel.decimalDoctorRate}", - 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, - ), - ], + AppCustomChipWidget( + icon: AppAssets.rating_icon, + iconColor: AppColors.ratingColorYellow, + labelText: "Rating: ${widget.prescriptionsResponseModel.decimalDoctorRate}".needTranslation, ), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - CustomButton( - text: widget.prescriptionsResponseModel.name!, - 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, - ), - ], + AppCustomChipWidget( + labelText: widget.prescriptionsResponseModel.name!, ), ], ), From b10523dac35ef32a2c8c75815b476d103c30000a Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Tue, 9 Sep 2025 17:41:05 +0300 Subject: [PATCH 2/3] Online CheckIn implementation contd. --- assets/animations/lottie/ErrorAnimation.json | 1 + lib/core/api/api_client.dart | 292 +++++++++--------- lib/core/app_assets.dart | 1 + lib/core/utils/utils.dart | 13 + .../my_appointments/my_appointments_repo.dart | 45 +++ .../my_appointments_view_model.dart | 24 ++ .../appointment_details_page.dart | 1 + .../appointment_checkin_bottom_sheet.dart | 108 ++++++- .../widgets/appointment_queueing_screen.dart | 0 lib/widgets/nfc/nfc_reader_sheet.dart | 212 +++++++++++++ pubspec.yaml | 2 + 11 files changed, 546 insertions(+), 153 deletions(-) create mode 100644 assets/animations/lottie/ErrorAnimation.json create mode 100644 lib/presentation/appointments/widgets/appointment_queueing_screen.dart create mode 100644 lib/widgets/nfc/nfc_reader_sheet.dart diff --git a/assets/animations/lottie/ErrorAnimation.json b/assets/animations/lottie/ErrorAnimation.json new file mode 100644 index 0000000..a9fc775 --- /dev/null +++ b/assets/animations/lottie/ErrorAnimation.json @@ -0,0 +1 @@ +{"nm":"Bouncy Fail","ddd":0,"h":512,"w":512,"meta":{"g":"@lottiefiles/toolkit-js 0.33.2"},"layers":[{"ty":4,"nm":"X line 2","sr":1,"st":0,"op":60,"ip":26,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[41,-3,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[256,256,0],"ix":2},"r":{"a":0,"k":90,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Shape 1","ix":1,"cix":2,"np":3,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":false,"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[112,-74],[-30,68]]},"ix":2}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":2,"lj":2,"ml":1,"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"c":{"a":0,"k":[1,1,1],"ix":3}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.8863,0.1176,0.1882],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[0,0],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"tm","bm":0,"hd":false,"mn":"ADBE Vector Filter - Trim","nm":"Trim Paths 1","ix":2,"e":{"a":0,"k":100,"ix":2},"o":{"a":0,"k":0,"ix":3},"s":{"a":1,"k":[{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[100],"t":26},{"s":[0],"t":40}],"ix":1},"m":1}],"ind":1},{"ty":4,"nm":"X line 1","sr":1,"st":0,"op":60,"ip":26,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[41,-3,0],"ix":1},"s":{"a":0,"k":[100,100,100],"ix":6},"sk":{"a":0,"k":0},"p":{"a":0,"k":[256,256,0],"ix":2},"r":{"a":0,"k":0,"ix":10},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11}},"ef":[],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Shape 1","ix":1,"cix":2,"np":3,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":false,"i":[[0,0],[0,0]],"o":[[0,0],[0,0]],"v":[[112,-74],[-30,68]]},"ix":2}},{"ty":"st","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Stroke","nm":"Stroke 1","lc":2,"lj":2,"ml":1,"o":{"a":0,"k":100,"ix":4},"w":{"a":0,"k":40,"ix":5},"c":{"a":0,"k":[1,1,1],"ix":3}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.8863,0.1176,0.1882],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[0,0],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]},{"ty":"tm","bm":0,"hd":false,"mn":"ADBE Vector Filter - Trim","nm":"Trim Paths 1","ix":2,"e":{"a":1,"k":[{"o":{"x":0.333,"y":0},"i":{"x":0.667,"y":1},"s":[0],"t":26},{"s":[100],"t":40}],"ix":2},"o":{"a":0,"k":0,"ix":3},"s":{"a":0,"k":0,"ix":1},"m":1}],"ind":2},{"ty":4,"nm":"Circle 2","sr":1,"st":0,"op":360,"ip":10,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[140.061,140.061,0],"ix":1},"s":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.667,"y":1},"s":[0,0,100],"t":10},{"s":[140,140,100],"t":20}],"ix":6,"x":"var $bm_rt;\nvar scaleInertialBounce, scaleBounceBack, n, n, t, t, v, amp, freq, decay, v, amp, freq, decay, e, g, nMax, e, g, nMax, n, n, t, v, vl, vu, vu, tCur, segDur, tNext, nb, delta;\nscaleInertialBounce = effect('Bounce & Drop - ukramedia.com')(16);\nscaleBounceBack = effect('Bounce & Drop - ukramedia.com')(17);\ntry {\n if (scaleInertialBounce == 1) {\n $bm_rt = n = 0;\n if (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time) {\n n--;\n }\n }\n if (n == 0) {\n $bm_rt = t = 0;\n } else {\n $bm_rt = t = $bm_sub(time, key(n).time);\n }\n if (effect('Bounce & Drop - ukramedia.com')(58) == 1) {\n if (n > 0 && t < 1) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = effect('Bounce & Drop - ukramedia.com')(59);\n freq = effect('Bounce & Drop - ukramedia.com')(60);\n decay = effect('Bounce & Drop - ukramedia.com')(61);\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n } else {\n $bm_rt = value;\n }\n } else {\n if (n > 0 && t < 1) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = effect('Bounce & Drop - ukramedia.com')(19);\n freq = effect('Bounce & Drop - ukramedia.com')(20);\n decay = effect('Bounce & Drop - ukramedia.com')(21);\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n } else {\n $bm_rt = value;\n }\n }\n } else if (scaleBounceBack == 1) {\n if (effect('Bounce & Drop - ukramedia.com')(64) == 1) {\n e = effect('Bounce & Drop - ukramedia.com')(65);\n g = effect('Bounce & Drop - ukramedia.com')(66);\n nMax = effect('Bounce & Drop - ukramedia.com')(67);\n } else {\n e = effect('Bounce & Drop - ukramedia.com')(24);\n g = effect('Bounce & Drop - ukramedia.com')(25);\n nMax = effect('Bounce & Drop - ukramedia.com')(26);\n }\n $bm_rt = n = 0;\n if (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time)\n n--;\n }\n if (n > 0) {\n t = $bm_sub(time, key(n).time);\n v = $bm_mul($bm_neg(velocityAtTime($bm_sub(key(n).time, 0.001))), e);\n vl = length(v);\n if ($bm_isInstanceOfArray(value)) {\n vu = vl > 0 ? normalize(v) : [\n 0,\n 0,\n 0\n ];\n } else {\n vu = v < 0 ? -1 : 1;\n }\n tCur = 0;\n segDur = $bm_div($bm_mul(2, vl), g);\n tNext = segDur;\n nb = 1;\n while (tNext < t && nb <= nMax) {\n vl *= e;\n segDur *= e;\n tCur = tNext;\n tNext = $bm_sum(tNext, segDur);\n nb++;\n }\n if (nb <= nMax) {\n delta = $bm_sub(t, tCur);\n $bm_rt = $bm_sum(value, $bm_mul($bm_mul(vu, delta), $bm_sub(vl, $bm_div($bm_mul(g, delta), 2))));\n } else {\n $bm_rt = value;\n }\n } else\n $bm_rt = value;\n } else {\n $bm_rt = value;\n }\n} catch (err) {\n $bm_rt = value;\n}"},"sk":{"a":0,"k":0},"p":{"a":0,"k":[256,256,0],"ix":2,"x":"var $bm_rt;\nvar positionInertialBounce, positionBounceBack, n, n, t, t, v, amp, freq, decay, v, amp, freq, decay, e, g, nMax, e, g, nMax, n, n, t, v, vl, vu, vu, tCur, segDur, tNext, nb, delta;\npositionInertialBounce = effect('Bounce & Drop - ukramedia.com')(2);\npositionBounceBack = effect('Bounce & Drop - ukramedia.com')(3);\ntry {\n if (positionInertialBounce == 1) {\n $bm_rt = n = 0;\n if (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time) {\n n--;\n }\n }\n if (n == 0) {\n $bm_rt = t = 0;\n } else {\n $bm_rt = t = $bm_sub(time, key(n).time);\n }\n if (effect('Bounce & Drop - ukramedia.com')(58) == 1) {\n if (n > 0 && t < 1) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = effect('Bounce & Drop - ukramedia.com')(59);\n freq = effect('Bounce & Drop - ukramedia.com')(60);\n decay = effect('Bounce & Drop - ukramedia.com')(61);\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n } else {\n $bm_rt = value;\n }\n } else {\n if (n > 0 && t < 1) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = effect('Bounce & Drop - ukramedia.com')(5);\n freq = effect('Bounce & Drop - ukramedia.com')(6);\n decay = effect('Bounce & Drop - ukramedia.com')(7);\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n } else {\n $bm_rt = value;\n }\n }\n } else if (positionBounceBack == 1) {\n if (effect('Bounce & Drop - ukramedia.com')(64) == 1) {\n e = effect('Bounce & Drop - ukramedia.com')(65);\n g = effect('Bounce & Drop - ukramedia.com')(66);\n nMax = effect('Bounce & Drop - ukramedia.com')(67);\n } else {\n e = effect('Bounce & Drop - ukramedia.com')(10);\n g = effect('Bounce & Drop - ukramedia.com')(11);\n nMax = effect('Bounce & Drop - ukramedia.com')(12);\n }\n $bm_rt = n = 0;\n if (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time)\n n--;\n }\n if (n > 0) {\n t = $bm_sub(time, key(n).time);\n v = $bm_mul($bm_neg(velocityAtTime($bm_sub(key(n).time, 0.001))), e);\n vl = length(v);\n if ($bm_isInstanceOfArray(value)) {\n vu = vl > 0 ? normalize(v) : [\n 0,\n 0,\n 0\n ];\n } else {\n vu = v < 0 ? -1 : 1;\n }\n tCur = 0;\n segDur = $bm_div($bm_mul(2, vl), g);\n tNext = segDur;\n nb = 1;\n while (tNext < t && nb <= nMax) {\n vl *= e;\n segDur *= e;\n tCur = tNext;\n tNext = $bm_sum(tNext, segDur);\n nb++;\n }\n if (nb <= nMax) {\n delta = $bm_sub(t, tCur);\n $bm_rt = $bm_sum(value, $bm_mul($bm_mul(vu, delta), $bm_sub(vl, $bm_div($bm_mul(g, delta), 2))));\n } else {\n $bm_rt = value;\n }\n } else\n $bm_rt = value;\n } else {\n $bm_rt = value;\n }\n} catch (err) {\n $bm_rt = value;\n}"},"r":{"a":0,"k":0,"ix":10,"x":"var $bm_rt;\nvar rotationInertialBounce, rotationBounceBack, n, n, t, t, v, amp, freq, decay, v, amp, freq, decay, e, g, nMax, e, g, nMax, n, n, t, v, vl, vu, vu, tCur, segDur, tNext, nb, delta;\nrotationInertialBounce = effect('Bounce & Drop - ukramedia.com')(30);\nrotationBounceBack = effect('Bounce & Drop - ukramedia.com')(31);\ntry {\n if (rotationInertialBounce == 1) {\n $bm_rt = n = 0;\n if (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time) {\n n--;\n }\n }\n if (n == 0) {\n $bm_rt = t = 0;\n } else {\n $bm_rt = t = $bm_sub(time, key(n).time);\n }\n if (effect('Bounce & Drop - ukramedia.com')(58) == 1) {\n if (n > 0 && t < 1) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = effect('Bounce & Drop - ukramedia.com')(59);\n freq = effect('Bounce & Drop - ukramedia.com')(60);\n decay = effect('Bounce & Drop - ukramedia.com')(61);\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n } else {\n $bm_rt = value;\n }\n } else {\n if (n > 0 && t < 1) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = effect('Bounce & Drop - ukramedia.com')(33);\n freq = effect('Bounce & Drop - ukramedia.com')(34);\n decay = effect('Bounce & Drop - ukramedia.com')(35);\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n } else {\n $bm_rt = value;\n }\n }\n } else if (rotationBounceBack == 1) {\n if (effect('Bounce & Drop - ukramedia.com')(64) == 1) {\n e = effect('Bounce & Drop - ukramedia.com')(65);\n g = effect('Bounce & Drop - ukramedia.com')(66);\n nMax = effect('Bounce & Drop - ukramedia.com')(67);\n } else {\n e = effect('Bounce & Drop - ukramedia.com')(38);\n g = effect('Bounce & Drop - ukramedia.com')(39);\n nMax = effect('Bounce & Drop - ukramedia.com')(40);\n }\n $bm_rt = n = 0;\n if (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time)\n n--;\n }\n if (n > 0) {\n t = $bm_sub(time, key(n).time);\n v = $bm_mul($bm_neg(velocityAtTime($bm_sub(key(n).time, 0.001))), e);\n vl = length(v);\n if ($bm_isInstanceOfArray(value)) {\n vu = vl > 0 ? normalize(v) : [\n 0,\n 0,\n 0\n ];\n } else {\n vu = v < 0 ? -1 : 1;\n }\n tCur = 0;\n segDur = $bm_div($bm_mul(2, vl), g);\n tNext = segDur;\n nb = 1;\n while (tNext < t && nb <= nMax) {\n vl *= e;\n segDur *= e;\n tCur = tNext;\n tNext = $bm_sum(tNext, segDur);\n nb++;\n }\n if (nb <= nMax) {\n delta = $bm_sub(t, tCur);\n $bm_rt = $bm_sum(value, $bm_mul($bm_mul(vu, delta), $bm_sub(vl, $bm_div($bm_mul(g, delta), 2))));\n } else {\n $bm_rt = value;\n }\n } else\n $bm_rt = value;\n } else {\n $bm_rt = value;\n }\n} catch (err) {\n $bm_rt = value;\n}"},"sa":{"a":0,"k":0},"o":{"a":0,"k":100,"ix":11,"x":"var $bm_rt;\nvar opacityInertialBounce, opacityBounceBack, n, n, t, t, v, amp, freq, decay, v, amp, freq, decay, e, g, nMax, e, g, nMax, n, n, t, v, vl, vu, vu, tCur, segDur, tNext, nb, delta;\nopacityInertialBounce = effect('Bounce & Drop - ukramedia.com')(44);\nopacityBounceBack = effect('Bounce & Drop - ukramedia.com')(45);\ntry {\n if (opacityInertialBounce == 1) {\n $bm_rt = n = 0;\n if (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time) {\n n--;\n }\n }\n if (n == 0) {\n $bm_rt = t = 0;\n } else {\n $bm_rt = t = $bm_sub(time, key(n).time);\n }\n if (effect('Bounce & Drop - ukramedia.com')(58) == 1) {\n if (n > 0 && t < 1) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = effect('Bounce & Drop - ukramedia.com')(59);\n freq = effect('Bounce & Drop - ukramedia.com')(60);\n decay = effect('Bounce & Drop - ukramedia.com')(61);\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n } else {\n $bm_rt = value;\n }\n } else {\n if (n > 0 && t < 1) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = effect('Bounce & Drop - ukramedia.com')(47);\n freq = effect('Bounce & Drop - ukramedia.com')(48);\n decay = effect('Bounce & Drop - ukramedia.com')(49);\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n } else {\n $bm_rt = value;\n }\n }\n } else if (opacityBounceBack == 1) {\n if (effect('Bounce & Drop - ukramedia.com')(64) == 1) {\n e = effect('Bounce & Drop - ukramedia.com')(65);\n g = effect('Bounce & Drop - ukramedia.com')(66);\n nMax = effect('Bounce & Drop - ukramedia.com')(67);\n } else {\n e = effect('Bounce & Drop - ukramedia.com')(52);\n g = effect('Bounce & Drop - ukramedia.com')(53);\n nMax = effect('Bounce & Drop - ukramedia.com')(54);\n }\n $bm_rt = n = 0;\n if (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time)\n n--;\n }\n if (n > 0) {\n t = $bm_sub(time, key(n).time);\n v = $bm_mul($bm_neg(velocityAtTime($bm_sub(key(n).time, 0.001))), e);\n vl = length(v);\n if ($bm_isInstanceOfArray(value)) {\n vu = vl > 0 ? normalize(v) : [\n 0,\n 0,\n 0\n ];\n } else {\n vu = v < 0 ? -1 : 1;\n }\n tCur = 0;\n segDur = $bm_div($bm_mul(2, vl), g);\n tNext = segDur;\n nb = 1;\n while (tNext < t && nb <= nMax) {\n vl *= e;\n segDur *= e;\n tCur = tNext;\n tNext = $bm_sum(tNext, segDur);\n nb++;\n }\n if (nb <= nMax) {\n delta = $bm_sub(t, tCur);\n $bm_rt = $bm_sum(value, $bm_mul($bm_mul(vu, delta), $bm_sub(vl, $bm_div($bm_mul(g, delta), 2))));\n } else {\n $bm_rt = value;\n }\n } else\n $bm_rt = value;\n } else {\n $bm_rt = value;\n }\n} catch (err) {\n $bm_rt = value;\n}"}},"ef":[{"ty":5,"mn":"Pseudo/animationControl","nm":"Bounce & Drop - ukramedia.com","ix":1,"en":1,"ef":[{"ty":6,"mn":"Pseudo/animationControl-0001","nm":"Position","ix":1,"v":0},{"ty":7,"mn":"Pseudo/animationControl-0002","nm":"Enable Inertial Bounce","ix":2,"v":{"a":0,"k":0,"ix":2}},{"ty":7,"mn":"Pseudo/animationControl-0003","nm":"Enable Bounce Back","ix":3,"v":{"a":0,"k":0,"ix":3}},{"ty":6,"mn":"Pseudo/animationControl-0004","nm":"Inertial Bounce Options","ix":4,"v":0},{"ty":0,"mn":"Pseudo/animationControl-0005","nm":"Amplitude","ix":5,"v":{"a":0,"k":0.05,"ix":5}},{"ty":0,"mn":"Pseudo/animationControl-0006","nm":"Frequency","ix":6,"v":{"a":0,"k":4,"ix":6}},{"ty":0,"mn":"Pseudo/animationControl-0007","nm":"Decay","ix":7,"v":{"a":0,"k":8,"ix":7}},{"ty":6,"mn":"Pseudo/animationControl-0008","nm":"","ix":8,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0009","nm":"Bounce Back Options","ix":9,"v":0},{"ty":0,"mn":"Pseudo/animationControl-0010","nm":"Elasticity","ix":10,"v":{"a":0,"k":0.7,"ix":10}},{"ty":0,"mn":"Pseudo/animationControl-0011","nm":"Gravity","ix":11,"v":{"a":0,"k":5000,"ix":11}},{"ty":0,"mn":"Pseudo/animationControl-0012","nm":"nMax","ix":12,"v":{"a":0,"k":9,"ix":12}},{"ty":6,"mn":"Pseudo/animationControl-0013","nm":"","ix":13,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0014","nm":"","ix":14,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0015","nm":"Scale","ix":15,"v":0},{"ty":7,"mn":"Pseudo/animationControl-0016","nm":"Enable Inertial Bounce","ix":16,"v":{"a":0,"k":1,"ix":16}},{"ty":7,"mn":"Pseudo/animationControl-0017","nm":"Enable Bounce Back","ix":17,"v":{"a":0,"k":0,"ix":17}},{"ty":6,"mn":"Pseudo/animationControl-0018","nm":"Inertial Bounce Options","ix":18,"v":0},{"ty":0,"mn":"Pseudo/animationControl-0019","nm":"Amplitude","ix":19,"v":{"a":0,"k":0.8,"ix":19}},{"ty":0,"mn":"Pseudo/animationControl-0020","nm":"Frequency","ix":20,"v":{"a":0,"k":4,"ix":20}},{"ty":0,"mn":"Pseudo/animationControl-0021","nm":"Decay","ix":21,"v":{"a":0,"k":8,"ix":21}},{"ty":6,"mn":"Pseudo/animationControl-0022","nm":"","ix":22,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0023","nm":"Bounce Back Options","ix":23,"v":0},{"ty":0,"mn":"Pseudo/animationControl-0024","nm":"Elasticity","ix":24,"v":{"a":0,"k":0.7,"ix":24}},{"ty":0,"mn":"Pseudo/animationControl-0025","nm":"Gravity","ix":25,"v":{"a":0,"k":5000,"ix":25}},{"ty":0,"mn":"Pseudo/animationControl-0026","nm":"nMax","ix":26,"v":{"a":0,"k":9,"ix":26}},{"ty":6,"mn":"Pseudo/animationControl-0027","nm":"","ix":27,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0028","nm":"","ix":28,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0029","nm":"Rotation","ix":29,"v":0},{"ty":7,"mn":"Pseudo/animationControl-0030","nm":"Enable Inertial Bounce","ix":30,"v":{"a":0,"k":0,"ix":30}},{"ty":7,"mn":"Pseudo/animationControl-0031","nm":"Enable Bounce Back","ix":31,"v":{"a":0,"k":0,"ix":31}},{"ty":6,"mn":"Pseudo/animationControl-0032","nm":"Inertial Bounce Options","ix":32,"v":0},{"ty":0,"mn":"Pseudo/animationControl-0033","nm":"Amplitude","ix":33,"v":{"a":0,"k":0.05,"ix":33}},{"ty":0,"mn":"Pseudo/animationControl-0034","nm":"Frequency","ix":34,"v":{"a":0,"k":4,"ix":34}},{"ty":0,"mn":"Pseudo/animationControl-0035","nm":"Decay","ix":35,"v":{"a":0,"k":8,"ix":35}},{"ty":6,"mn":"Pseudo/animationControl-0036","nm":"","ix":36,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0037","nm":"Bounce Back Options","ix":37,"v":0},{"ty":0,"mn":"Pseudo/animationControl-0038","nm":"Elasticity","ix":38,"v":{"a":0,"k":0.7,"ix":38}},{"ty":0,"mn":"Pseudo/animationControl-0039","nm":"Gravity","ix":39,"v":{"a":0,"k":5000,"ix":39}},{"ty":0,"mn":"Pseudo/animationControl-0040","nm":"nMax","ix":40,"v":{"a":0,"k":9,"ix":40}},{"ty":6,"mn":"Pseudo/animationControl-0041","nm":"","ix":41,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0042","nm":"","ix":42,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0043","nm":"Opacity","ix":43,"v":0},{"ty":7,"mn":"Pseudo/animationControl-0044","nm":"Enable Inertial Bounce","ix":44,"v":{"a":0,"k":0,"ix":44}},{"ty":7,"mn":"Pseudo/animationControl-0045","nm":"Enable Bounce Back","ix":45,"v":{"a":0,"k":0,"ix":45}},{"ty":6,"mn":"Pseudo/animationControl-0046","nm":"Inertial Bounce Options","ix":46,"v":0},{"ty":0,"mn":"Pseudo/animationControl-0047","nm":"Amplitude","ix":47,"v":{"a":0,"k":0.05,"ix":47}},{"ty":0,"mn":"Pseudo/animationControl-0048","nm":"Frequency","ix":48,"v":{"a":0,"k":4,"ix":48}},{"ty":0,"mn":"Pseudo/animationControl-0049","nm":"Decay","ix":49,"v":{"a":0,"k":8,"ix":49}},{"ty":6,"mn":"Pseudo/animationControl-0050","nm":"","ix":50,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0051","nm":"Bounce Back Options","ix":51,"v":0},{"ty":0,"mn":"Pseudo/animationControl-0052","nm":"Elasticity","ix":52,"v":{"a":0,"k":0.7,"ix":52}},{"ty":0,"mn":"Pseudo/animationControl-0053","nm":"Gravity","ix":53,"v":{"a":0,"k":5000,"ix":53}},{"ty":0,"mn":"Pseudo/animationControl-0054","nm":"nMax","ix":54,"v":{"a":0,"k":9,"ix":54}},{"ty":6,"mn":"Pseudo/animationControl-0055","nm":"","ix":55,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0056","nm":"","ix":56,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0057","nm":"Global Inertial Bounce Options","ix":57,"v":0},{"ty":7,"mn":"Pseudo/animationControl-0058","nm":"Enable Global Inertial Bounce","ix":58,"v":{"a":0,"k":0,"ix":58}},{"ty":0,"mn":"Pseudo/animationControl-0059","nm":"Amplitude","ix":59,"v":{"a":0,"k":0.05,"ix":59}},{"ty":0,"mn":"Pseudo/animationControl-0060","nm":"Frequency","ix":60,"v":{"a":0,"k":4,"ix":60}},{"ty":0,"mn":"Pseudo/animationControl-0061","nm":"Decay","ix":61,"v":{"a":0,"k":8,"ix":61}},{"ty":6,"mn":"Pseudo/animationControl-0062","nm":"","ix":62,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0063","nm":"Global Bounce Back Options","ix":63,"v":0},{"ty":7,"mn":"Pseudo/animationControl-0064","nm":"Enable Global Bounce Back","ix":64,"v":{"a":0,"k":0,"ix":64}},{"ty":0,"mn":"Pseudo/animationControl-0065","nm":"Elasticity","ix":65,"v":{"a":0,"k":0.7,"ix":65}},{"ty":0,"mn":"Pseudo/animationControl-0066","nm":"Gravity","ix":66,"v":{"a":0,"k":5000,"ix":66}},{"ty":0,"mn":"Pseudo/animationControl-0067","nm":"nMax","ix":67,"v":{"a":0,"k":9,"ix":67}},{"ty":6,"mn":"Pseudo/animationControl-0068","nm":"","ix":68,"v":0}]}],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 1","ix":1,"cix":2,"np":2,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,-77.215],[77.216,0],[0,77.215],[-77.215,0]],"o":[[0,77.215],[-77.215,0],[0,-77.215],[77.216,0]],"v":[[139.811,0],[0,139.811],[-139.811,0],[0,-139.811]]},"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.8863,0.1176,0.1882],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[140.061,140.061],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":3},{"ty":4,"nm":"Circle 1","sr":1,"st":0,"op":360,"ip":0,"hd":false,"ddd":0,"bm":0,"hasMask":false,"ao":0,"ks":{"a":{"a":0,"k":[140.061,140.061,0],"ix":1},"s":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.667,"y":1},"s":[0,0,100],"t":0},{"o":{"x":0.167,"y":0.167},"i":{"x":0.667,"y":1},"s":[132,132,100],"t":10},{"o":{"x":0.167,"y":0.167},"i":{"x":0.667,"y":1},"s":[130,130,100],"t":35},{"s":[175,175,100],"t":55}],"ix":6,"x":"var $bm_rt;\nvar scaleInertialBounce, scaleBounceBack, n, n, t, t, v, amp, freq, decay, v, amp, freq, decay, e, g, nMax, e, g, nMax, n, n, t, v, vl, vu, vu, tCur, segDur, tNext, nb, delta;\nscaleInertialBounce = effect('Bounce & Drop - ukramedia.com')(16);\nscaleBounceBack = effect('Bounce & Drop - ukramedia.com')(17);\ntry {\n if (scaleInertialBounce == 1) {\n $bm_rt = n = 0;\n if (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time) {\n n--;\n }\n }\n if (n == 0) {\n $bm_rt = t = 0;\n } else {\n $bm_rt = t = $bm_sub(time, key(n).time);\n }\n if (effect('Bounce & Drop - ukramedia.com')(58) == 1) {\n if (n > 0 && t < 1) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = effect('Bounce & Drop - ukramedia.com')(59);\n freq = effect('Bounce & Drop - ukramedia.com')(60);\n decay = effect('Bounce & Drop - ukramedia.com')(61);\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n } else {\n $bm_rt = value;\n }\n } else {\n if (n > 0 && t < 1) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = effect('Bounce & Drop - ukramedia.com')(19);\n freq = effect('Bounce & Drop - ukramedia.com')(20);\n decay = effect('Bounce & Drop - ukramedia.com')(21);\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n } else {\n $bm_rt = value;\n }\n }\n } else if (scaleBounceBack == 1) {\n if (effect('Bounce & Drop - ukramedia.com')(64) == 1) {\n e = effect('Bounce & Drop - ukramedia.com')(65);\n g = effect('Bounce & Drop - ukramedia.com')(66);\n nMax = effect('Bounce & Drop - ukramedia.com')(67);\n } else {\n e = effect('Bounce & Drop - ukramedia.com')(24);\n g = effect('Bounce & Drop - ukramedia.com')(25);\n nMax = effect('Bounce & Drop - ukramedia.com')(26);\n }\n $bm_rt = n = 0;\n if (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time)\n n--;\n }\n if (n > 0) {\n t = $bm_sub(time, key(n).time);\n v = $bm_mul($bm_neg(velocityAtTime($bm_sub(key(n).time, 0.001))), e);\n vl = length(v);\n if ($bm_isInstanceOfArray(value)) {\n vu = vl > 0 ? normalize(v) : [\n 0,\n 0,\n 0\n ];\n } else {\n vu = v < 0 ? -1 : 1;\n }\n tCur = 0;\n segDur = $bm_div($bm_mul(2, vl), g);\n tNext = segDur;\n nb = 1;\n while (tNext < t && nb <= nMax) {\n vl *= e;\n segDur *= e;\n tCur = tNext;\n tNext = $bm_sum(tNext, segDur);\n nb++;\n }\n if (nb <= nMax) {\n delta = $bm_sub(t, tCur);\n $bm_rt = $bm_sum(value, $bm_mul($bm_mul(vu, delta), $bm_sub(vl, $bm_div($bm_mul(g, delta), 2))));\n } else {\n $bm_rt = value;\n }\n } else\n $bm_rt = value;\n } else {\n $bm_rt = value;\n }\n} catch (err) {\n $bm_rt = value;\n}"},"sk":{"a":0,"k":0},"p":{"a":0,"k":[256,256,0],"ix":2,"x":"var $bm_rt;\nvar positionInertialBounce, positionBounceBack, n, n, t, t, v, amp, freq, decay, v, amp, freq, decay, e, g, nMax, e, g, nMax, n, n, t, v, vl, vu, vu, tCur, segDur, tNext, nb, delta;\npositionInertialBounce = effect('Bounce & Drop - ukramedia.com')(2);\npositionBounceBack = effect('Bounce & Drop - ukramedia.com')(3);\ntry {\n if (positionInertialBounce == 1) {\n $bm_rt = n = 0;\n if (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time) {\n n--;\n }\n }\n if (n == 0) {\n $bm_rt = t = 0;\n } else {\n $bm_rt = t = $bm_sub(time, key(n).time);\n }\n if (effect('Bounce & Drop - ukramedia.com')(58) == 1) {\n if (n > 0 && t < 1) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = effect('Bounce & Drop - ukramedia.com')(59);\n freq = effect('Bounce & Drop - ukramedia.com')(60);\n decay = effect('Bounce & Drop - ukramedia.com')(61);\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n } else {\n $bm_rt = value;\n }\n } else {\n if (n > 0 && t < 1) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = effect('Bounce & Drop - ukramedia.com')(5);\n freq = effect('Bounce & Drop - ukramedia.com')(6);\n decay = effect('Bounce & Drop - ukramedia.com')(7);\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n } else {\n $bm_rt = value;\n }\n }\n } else if (positionBounceBack == 1) {\n if (effect('Bounce & Drop - ukramedia.com')(64) == 1) {\n e = effect('Bounce & Drop - ukramedia.com')(65);\n g = effect('Bounce & Drop - ukramedia.com')(66);\n nMax = effect('Bounce & Drop - ukramedia.com')(67);\n } else {\n e = effect('Bounce & Drop - ukramedia.com')(10);\n g = effect('Bounce & Drop - ukramedia.com')(11);\n nMax = effect('Bounce & Drop - ukramedia.com')(12);\n }\n $bm_rt = n = 0;\n if (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time)\n n--;\n }\n if (n > 0) {\n t = $bm_sub(time, key(n).time);\n v = $bm_mul($bm_neg(velocityAtTime($bm_sub(key(n).time, 0.001))), e);\n vl = length(v);\n if ($bm_isInstanceOfArray(value)) {\n vu = vl > 0 ? normalize(v) : [\n 0,\n 0,\n 0\n ];\n } else {\n vu = v < 0 ? -1 : 1;\n }\n tCur = 0;\n segDur = $bm_div($bm_mul(2, vl), g);\n tNext = segDur;\n nb = 1;\n while (tNext < t && nb <= nMax) {\n vl *= e;\n segDur *= e;\n tCur = tNext;\n tNext = $bm_sum(tNext, segDur);\n nb++;\n }\n if (nb <= nMax) {\n delta = $bm_sub(t, tCur);\n $bm_rt = $bm_sum(value, $bm_mul($bm_mul(vu, delta), $bm_sub(vl, $bm_div($bm_mul(g, delta), 2))));\n } else {\n $bm_rt = value;\n }\n } else\n $bm_rt = value;\n } else {\n $bm_rt = value;\n }\n} catch (err) {\n $bm_rt = value;\n}"},"r":{"a":0,"k":0,"ix":10,"x":"var $bm_rt;\nvar rotationInertialBounce, rotationBounceBack, n, n, t, t, v, amp, freq, decay, v, amp, freq, decay, e, g, nMax, e, g, nMax, n, n, t, v, vl, vu, vu, tCur, segDur, tNext, nb, delta;\nrotationInertialBounce = effect('Bounce & Drop - ukramedia.com')(30);\nrotationBounceBack = effect('Bounce & Drop - ukramedia.com')(31);\ntry {\n if (rotationInertialBounce == 1) {\n $bm_rt = n = 0;\n if (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time) {\n n--;\n }\n }\n if (n == 0) {\n $bm_rt = t = 0;\n } else {\n $bm_rt = t = $bm_sub(time, key(n).time);\n }\n if (effect('Bounce & Drop - ukramedia.com')(58) == 1) {\n if (n > 0 && t < 1) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = effect('Bounce & Drop - ukramedia.com')(59);\n freq = effect('Bounce & Drop - ukramedia.com')(60);\n decay = effect('Bounce & Drop - ukramedia.com')(61);\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n } else {\n $bm_rt = value;\n }\n } else {\n if (n > 0 && t < 1) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = effect('Bounce & Drop - ukramedia.com')(33);\n freq = effect('Bounce & Drop - ukramedia.com')(34);\n decay = effect('Bounce & Drop - ukramedia.com')(35);\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n } else {\n $bm_rt = value;\n }\n }\n } else if (rotationBounceBack == 1) {\n if (effect('Bounce & Drop - ukramedia.com')(64) == 1) {\n e = effect('Bounce & Drop - ukramedia.com')(65);\n g = effect('Bounce & Drop - ukramedia.com')(66);\n nMax = effect('Bounce & Drop - ukramedia.com')(67);\n } else {\n e = effect('Bounce & Drop - ukramedia.com')(38);\n g = effect('Bounce & Drop - ukramedia.com')(39);\n nMax = effect('Bounce & Drop - ukramedia.com')(40);\n }\n $bm_rt = n = 0;\n if (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time)\n n--;\n }\n if (n > 0) {\n t = $bm_sub(time, key(n).time);\n v = $bm_mul($bm_neg(velocityAtTime($bm_sub(key(n).time, 0.001))), e);\n vl = length(v);\n if ($bm_isInstanceOfArray(value)) {\n vu = vl > 0 ? normalize(v) : [\n 0,\n 0,\n 0\n ];\n } else {\n vu = v < 0 ? -1 : 1;\n }\n tCur = 0;\n segDur = $bm_div($bm_mul(2, vl), g);\n tNext = segDur;\n nb = 1;\n while (tNext < t && nb <= nMax) {\n vl *= e;\n segDur *= e;\n tCur = tNext;\n tNext = $bm_sum(tNext, segDur);\n nb++;\n }\n if (nb <= nMax) {\n delta = $bm_sub(t, tCur);\n $bm_rt = $bm_sum(value, $bm_mul($bm_mul(vu, delta), $bm_sub(vl, $bm_div($bm_mul(g, delta), 2))));\n } else {\n $bm_rt = value;\n }\n } else\n $bm_rt = value;\n } else {\n $bm_rt = value;\n }\n} catch (err) {\n $bm_rt = value;\n}"},"sa":{"a":0,"k":0},"o":{"a":1,"k":[{"o":{"x":0.167,"y":0.167},"i":{"x":0.667,"y":1},"s":[50],"t":41.25},{"s":[0],"t":55}],"ix":11,"x":"var $bm_rt;\nvar opacityInertialBounce, opacityBounceBack, n, n, t, t, v, amp, freq, decay, v, amp, freq, decay, e, g, nMax, e, g, nMax, n, n, t, v, vl, vu, vu, tCur, segDur, tNext, nb, delta;\nopacityInertialBounce = effect('Bounce & Drop - ukramedia.com')(44);\nopacityBounceBack = effect('Bounce & Drop - ukramedia.com')(45);\ntry {\n if (opacityInertialBounce == 1) {\n $bm_rt = n = 0;\n if (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time) {\n n--;\n }\n }\n if (n == 0) {\n $bm_rt = t = 0;\n } else {\n $bm_rt = t = $bm_sub(time, key(n).time);\n }\n if (effect('Bounce & Drop - ukramedia.com')(58) == 1) {\n if (n > 0 && t < 1) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = effect('Bounce & Drop - ukramedia.com')(59);\n freq = effect('Bounce & Drop - ukramedia.com')(60);\n decay = effect('Bounce & Drop - ukramedia.com')(61);\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n } else {\n $bm_rt = value;\n }\n } else {\n if (n > 0 && t < 1) {\n v = velocityAtTime($bm_sub(key(n).time, $bm_div(thisComp.frameDuration, 10)));\n amp = effect('Bounce & Drop - ukramedia.com')(47);\n freq = effect('Bounce & Drop - ukramedia.com')(48);\n decay = effect('Bounce & Drop - ukramedia.com')(49);\n $bm_rt = $bm_sum(value, $bm_div($bm_mul($bm_mul(v, amp), Math.sin($bm_mul($bm_mul($bm_mul(freq, t), 2), Math.PI))), Math.exp($bm_mul(decay, t))));\n } else {\n $bm_rt = value;\n }\n }\n } else if (opacityBounceBack == 1) {\n if (effect('Bounce & Drop - ukramedia.com')(64) == 1) {\n e = effect('Bounce & Drop - ukramedia.com')(65);\n g = effect('Bounce & Drop - ukramedia.com')(66);\n nMax = effect('Bounce & Drop - ukramedia.com')(67);\n } else {\n e = effect('Bounce & Drop - ukramedia.com')(52);\n g = effect('Bounce & Drop - ukramedia.com')(53);\n nMax = effect('Bounce & Drop - ukramedia.com')(54);\n }\n $bm_rt = n = 0;\n if (numKeys > 0) {\n $bm_rt = n = nearestKey(time).index;\n if (key(n).time > time)\n n--;\n }\n if (n > 0) {\n t = $bm_sub(time, key(n).time);\n v = $bm_mul($bm_neg(velocityAtTime($bm_sub(key(n).time, 0.001))), e);\n vl = length(v);\n if ($bm_isInstanceOfArray(value)) {\n vu = vl > 0 ? normalize(v) : [\n 0,\n 0,\n 0\n ];\n } else {\n vu = v < 0 ? -1 : 1;\n }\n tCur = 0;\n segDur = $bm_div($bm_mul(2, vl), g);\n tNext = segDur;\n nb = 1;\n while (tNext < t && nb <= nMax) {\n vl *= e;\n segDur *= e;\n tCur = tNext;\n tNext = $bm_sum(tNext, segDur);\n nb++;\n }\n if (nb <= nMax) {\n delta = $bm_sub(t, tCur);\n $bm_rt = $bm_sum(value, $bm_mul($bm_mul(vu, delta), $bm_sub(vl, $bm_div($bm_mul(g, delta), 2))));\n } else {\n $bm_rt = value;\n }\n } else\n $bm_rt = value;\n } else {\n $bm_rt = value;\n }\n} catch (err) {\n $bm_rt = value;\n}"}},"ef":[{"ty":5,"mn":"Pseudo/animationControl","nm":"Bounce & Drop - ukramedia.com","ix":1,"en":1,"ef":[{"ty":6,"mn":"Pseudo/animationControl-0001","nm":"Position","ix":1,"v":0},{"ty":7,"mn":"Pseudo/animationControl-0002","nm":"Enable Inertial Bounce","ix":2,"v":{"a":0,"k":0,"ix":2}},{"ty":7,"mn":"Pseudo/animationControl-0003","nm":"Enable Bounce Back","ix":3,"v":{"a":0,"k":0,"ix":3}},{"ty":6,"mn":"Pseudo/animationControl-0004","nm":"Inertial Bounce Options","ix":4,"v":0},{"ty":0,"mn":"Pseudo/animationControl-0005","nm":"Amplitude","ix":5,"v":{"a":0,"k":0.05,"ix":5}},{"ty":0,"mn":"Pseudo/animationControl-0006","nm":"Frequency","ix":6,"v":{"a":0,"k":4,"ix":6}},{"ty":0,"mn":"Pseudo/animationControl-0007","nm":"Decay","ix":7,"v":{"a":0,"k":8,"ix":7}},{"ty":6,"mn":"Pseudo/animationControl-0008","nm":"","ix":8,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0009","nm":"Bounce Back Options","ix":9,"v":0},{"ty":0,"mn":"Pseudo/animationControl-0010","nm":"Elasticity","ix":10,"v":{"a":0,"k":0.7,"ix":10}},{"ty":0,"mn":"Pseudo/animationControl-0011","nm":"Gravity","ix":11,"v":{"a":0,"k":5000,"ix":11}},{"ty":0,"mn":"Pseudo/animationControl-0012","nm":"nMax","ix":12,"v":{"a":0,"k":9,"ix":12}},{"ty":6,"mn":"Pseudo/animationControl-0013","nm":"","ix":13,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0014","nm":"","ix":14,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0015","nm":"Scale","ix":15,"v":0},{"ty":7,"mn":"Pseudo/animationControl-0016","nm":"Enable Inertial Bounce","ix":16,"v":{"a":0,"k":1,"ix":16}},{"ty":7,"mn":"Pseudo/animationControl-0017","nm":"Enable Bounce Back","ix":17,"v":{"a":0,"k":0,"ix":17}},{"ty":6,"mn":"Pseudo/animationControl-0018","nm":"Inertial Bounce Options","ix":18,"v":0},{"ty":0,"mn":"Pseudo/animationControl-0019","nm":"Amplitude","ix":19,"v":{"a":0,"k":0.8,"ix":19}},{"ty":0,"mn":"Pseudo/animationControl-0020","nm":"Frequency","ix":20,"v":{"a":0,"k":4,"ix":20}},{"ty":0,"mn":"Pseudo/animationControl-0021","nm":"Decay","ix":21,"v":{"a":0,"k":8,"ix":21}},{"ty":6,"mn":"Pseudo/animationControl-0022","nm":"","ix":22,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0023","nm":"Bounce Back Options","ix":23,"v":0},{"ty":0,"mn":"Pseudo/animationControl-0024","nm":"Elasticity","ix":24,"v":{"a":0,"k":0.7,"ix":24}},{"ty":0,"mn":"Pseudo/animationControl-0025","nm":"Gravity","ix":25,"v":{"a":0,"k":5000,"ix":25}},{"ty":0,"mn":"Pseudo/animationControl-0026","nm":"nMax","ix":26,"v":{"a":0,"k":9,"ix":26}},{"ty":6,"mn":"Pseudo/animationControl-0027","nm":"","ix":27,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0028","nm":"","ix":28,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0029","nm":"Rotation","ix":29,"v":0},{"ty":7,"mn":"Pseudo/animationControl-0030","nm":"Enable Inertial Bounce","ix":30,"v":{"a":0,"k":0,"ix":30}},{"ty":7,"mn":"Pseudo/animationControl-0031","nm":"Enable Bounce Back","ix":31,"v":{"a":0,"k":0,"ix":31}},{"ty":6,"mn":"Pseudo/animationControl-0032","nm":"Inertial Bounce Options","ix":32,"v":0},{"ty":0,"mn":"Pseudo/animationControl-0033","nm":"Amplitude","ix":33,"v":{"a":0,"k":0.05,"ix":33}},{"ty":0,"mn":"Pseudo/animationControl-0034","nm":"Frequency","ix":34,"v":{"a":0,"k":4,"ix":34}},{"ty":0,"mn":"Pseudo/animationControl-0035","nm":"Decay","ix":35,"v":{"a":0,"k":8,"ix":35}},{"ty":6,"mn":"Pseudo/animationControl-0036","nm":"","ix":36,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0037","nm":"Bounce Back Options","ix":37,"v":0},{"ty":0,"mn":"Pseudo/animationControl-0038","nm":"Elasticity","ix":38,"v":{"a":0,"k":0.7,"ix":38}},{"ty":0,"mn":"Pseudo/animationControl-0039","nm":"Gravity","ix":39,"v":{"a":0,"k":5000,"ix":39}},{"ty":0,"mn":"Pseudo/animationControl-0040","nm":"nMax","ix":40,"v":{"a":0,"k":9,"ix":40}},{"ty":6,"mn":"Pseudo/animationControl-0041","nm":"","ix":41,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0042","nm":"","ix":42,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0043","nm":"Opacity","ix":43,"v":0},{"ty":7,"mn":"Pseudo/animationControl-0044","nm":"Enable Inertial Bounce","ix":44,"v":{"a":0,"k":0,"ix":44}},{"ty":7,"mn":"Pseudo/animationControl-0045","nm":"Enable Bounce Back","ix":45,"v":{"a":0,"k":0,"ix":45}},{"ty":6,"mn":"Pseudo/animationControl-0046","nm":"Inertial Bounce Options","ix":46,"v":0},{"ty":0,"mn":"Pseudo/animationControl-0047","nm":"Amplitude","ix":47,"v":{"a":0,"k":0.05,"ix":47}},{"ty":0,"mn":"Pseudo/animationControl-0048","nm":"Frequency","ix":48,"v":{"a":0,"k":4,"ix":48}},{"ty":0,"mn":"Pseudo/animationControl-0049","nm":"Decay","ix":49,"v":{"a":0,"k":8,"ix":49}},{"ty":6,"mn":"Pseudo/animationControl-0050","nm":"","ix":50,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0051","nm":"Bounce Back Options","ix":51,"v":0},{"ty":0,"mn":"Pseudo/animationControl-0052","nm":"Elasticity","ix":52,"v":{"a":0,"k":0.7,"ix":52}},{"ty":0,"mn":"Pseudo/animationControl-0053","nm":"Gravity","ix":53,"v":{"a":0,"k":5000,"ix":53}},{"ty":0,"mn":"Pseudo/animationControl-0054","nm":"nMax","ix":54,"v":{"a":0,"k":9,"ix":54}},{"ty":6,"mn":"Pseudo/animationControl-0055","nm":"","ix":55,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0056","nm":"","ix":56,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0057","nm":"Global Inertial Bounce Options","ix":57,"v":0},{"ty":7,"mn":"Pseudo/animationControl-0058","nm":"Enable Global Inertial Bounce","ix":58,"v":{"a":0,"k":0,"ix":58}},{"ty":0,"mn":"Pseudo/animationControl-0059","nm":"Amplitude","ix":59,"v":{"a":0,"k":0.05,"ix":59}},{"ty":0,"mn":"Pseudo/animationControl-0060","nm":"Frequency","ix":60,"v":{"a":0,"k":4,"ix":60}},{"ty":0,"mn":"Pseudo/animationControl-0061","nm":"Decay","ix":61,"v":{"a":0,"k":8,"ix":61}},{"ty":6,"mn":"Pseudo/animationControl-0062","nm":"","ix":62,"v":0},{"ty":6,"mn":"Pseudo/animationControl-0063","nm":"Global Bounce Back Options","ix":63,"v":0},{"ty":7,"mn":"Pseudo/animationControl-0064","nm":"Enable Global Bounce Back","ix":64,"v":{"a":0,"k":0,"ix":64}},{"ty":0,"mn":"Pseudo/animationControl-0065","nm":"Elasticity","ix":65,"v":{"a":0,"k":0.7,"ix":65}},{"ty":0,"mn":"Pseudo/animationControl-0066","nm":"Gravity","ix":66,"v":{"a":0,"k":5000,"ix":66}},{"ty":0,"mn":"Pseudo/animationControl-0067","nm":"nMax","ix":67,"v":{"a":0,"k":9,"ix":67}},{"ty":6,"mn":"Pseudo/animationControl-0068","nm":"","ix":68,"v":0}]}],"shapes":[{"ty":"gr","bm":0,"hd":false,"mn":"ADBE Vector Group","nm":"Group 1","ix":1,"cix":2,"np":2,"it":[{"ty":"sh","bm":0,"hd":false,"mn":"ADBE Vector Shape - Group","nm":"Path 1","ix":1,"d":1,"ks":{"a":0,"k":{"c":true,"i":[[0,-77.215],[77.216,0],[0,77.215],[-77.215,0]],"o":[[0,77.215],[-77.215,0],[0,-77.215],[77.216,0]],"v":[[139.811,0],[0,139.811],[-139.811,0],[0,-139.811]]},"ix":2}},{"ty":"fl","bm":0,"hd":false,"mn":"ADBE Vector Graphic - Fill","nm":"Fill 1","c":{"a":0,"k":[0.8863,0.1176,0.1882],"ix":4},"r":1,"o":{"a":0,"k":100,"ix":5}},{"ty":"tr","a":{"a":0,"k":[0,0],"ix":1},"s":{"a":0,"k":[100,100],"ix":3},"sk":{"a":0,"k":0,"ix":4},"p":{"a":0,"k":[140.061,140.061],"ix":2},"r":{"a":0,"k":0,"ix":6},"sa":{"a":0,"k":0,"ix":5},"o":{"a":0,"k":100,"ix":7}}]}],"ind":4}],"v":"5.7.11","fr":30,"op":60,"ip":0,"assets":[]} \ No newline at end of file diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index 7555400..2fd8082 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -17,6 +17,7 @@ import '../exceptions/api_failure.dart'; abstract class ApiClient { static final NavigationService _navigationService = getIt.get(); + Future post( String endPoint, { required Map body, @@ -93,7 +94,7 @@ class ApiClientImp implements ApiClient { required Map body, required Function(dynamic response, int statusCode, {int? messageStatus, String? errorMessage}) onSuccess, required Function(String error, int statusCode, {int? messageStatus, Failure? failureType}) onFailure, - bool isAllowAny = true, + bool isAllowAny = false, bool isExternal = false, bool isRCService = false, bool bypassConnectionCheck = false, @@ -109,187 +110,186 @@ class ApiClientImp implements ApiClient { } } // try { - var user = _appState.getAuthenticatedUser(); - Map headers = {'Content-Type': 'application/json', 'Accept': 'application/json'}; - if (!isExternal) { - String? token = _appState.appAuthToken; + var user = _appState.getAuthenticatedUser(); + Map headers = {'Content-Type': 'application/json', 'Accept': 'application/json'}; + if (!isExternal) { + String? token = _appState.appAuthToken; - if (body.containsKey('SetupID')) { - body['SetupID'] = body.containsKey('SetupID') ? body['SetupID'] ?? body[''] : SETUP_ID; - } else {} + if (body.containsKey('SetupID')) { + body['SetupID'] = body.containsKey('SetupID') ? body['SetupID'] ?? body[''] : SETUP_ID; + } else {} - if (body.containsKey('isDentalAllowedBackend')) { - body['isDentalAllowedBackend'] = body.containsKey('isDentalAllowedBackend') ? body['isDentalAllowedBackend'] ?? IS_DENTAL_ALLOWED_BACKEND : IS_DENTAL_ALLOWED_BACKEND; - } + if (body.containsKey('isDentalAllowedBackend')) { + body['isDentalAllowedBackend'] = body.containsKey('isDentalAllowedBackend') ? body['isDentalAllowedBackend'] ?? IS_DENTAL_ALLOWED_BACKEND : IS_DENTAL_ALLOWED_BACKEND; + } - if (!body.containsKey('IsPublicRequest')) { - // if (!body.containsKey('PatientType')) { - if (user != null && user.patientType != null) { - body['PatientType'] = user.patientType; - } else { - body['PatientType'] = PATIENT_TYPE.toString(); - } + if (!body.containsKey('IsPublicRequest')) { + // if (!body.containsKey('PatientType')) { + if (user != null && user.patientType != null) { + body['PatientType'] = user.patientType; + } else { + body['PatientType'] = PATIENT_TYPE.toString(); + } - if (user != null && user.patientType != null) { - body['PatientTypeID'] = user.patientType; - } else { - body['PatientType'] = PATIENT_TYPE_ID.toString(); - } + if (user != null && user.patientType != null) { + body['PatientTypeID'] = user.patientType; + } else { + body['PatientType'] = PATIENT_TYPE_ID.toString(); + } - // TODO : These should be from the appState - if (user != null) { - body['TokenID'] = body['TokenID'] ?? token; - body['PatientID'] = body['PatientID'] ?? user.patientId; + // TODO : These should be from the appState + if (user != null) { + body['TokenID'] = body['TokenID'] ?? token; + body['PatientID'] = body['PatientID'] ?? user.patientId; - body['PatientOutSA'] = body.containsKey('PatientOutSA') ? body['PatientOutSA'] ?? user.outSa : user.outSa; - body['SessionID'] = body['TokenID'] == null ? ApiConsts.sessionID : getSessionId(body['TokenID'] ?? ""); //getSe - } - // else { - // body['SessionID'] = body['TokenID'] == null ? ApiConsts.sessionID : getSessionId(body['TokenID'] ?? ""); //getSe - // - // } + body['PatientOutSA'] = body.containsKey('PatientOutSA') ? body['PatientOutSA'] ?? user.outSa : user.outSa; + body['SessionID'] = body['TokenID'] == null ? ApiConsts.sessionID : getSessionId(body['TokenID'] ?? ""); //getSe } + // else { + // body['SessionID'] = body['TokenID'] == null ? ApiConsts.sessionID : getSessionId(body['TokenID'] ?? ""); //getSe + // + // } } + } - // request.versionID = VERSION_ID; - // request.channel = CHANNEL; - // request.iPAdress = IP_ADDRESS; - // request.generalid = GENERAL_ID; - // request.languageID = (languageID == 'ar' ? 1 : 2); - // request.patientOutSA = (request.zipCode == '966' || request.zipCode == '+966') ? 0 : 1; - - // body['VersionID'] = ApiConsts.appVersionID.toString(); - if (!isExternal) { - body['VersionID'] = "50.0"; - body['Channel'] = ApiConsts.appChannelId.toString(); - body['IPAdress'] = ApiConsts.appIpAddress; - body['generalid'] = ApiConsts.appGeneralId; - - body['LanguageID'] = _appState.getLanguageID().toString(); - body['Latitude'] = _appState.userLat.toString(); - body['Longitude'] = _appState.userLong.toString(); - body['DeviceTypeID'] = _appState.deviceTypeID; - if (_appState.appAuthToken.isNotEmpty) { - body[_appState.isAuthenticated ? 'TokenID' : 'LogInTokenID'] = _appState.appAuthToken; - } - - // body['TokenID'] = "@dm!n"; - // body['PatientID'] = "4767477"; + // request.versionID = VERSION_ID; + // request.channel = CHANNEL; + // request.iPAdress = IP_ADDRESS; + // request.generalid = GENERAL_ID; + // request.languageID = (languageID == 'ar' ? 1 : 2); + // request.patientOutSA = (request.zipCode == '966' || request.zipCode == '+966') ? 0 : 1; + + // body['VersionID'] = ApiConsts.appVersionID.toString(); + if (!isExternal) { + body['VersionID'] = "50.0"; + body['Channel'] = ApiConsts.appChannelId.toString(); + body['IPAdress'] = ApiConsts.appIpAddress; + body['generalid'] = ApiConsts.appGeneralId; + + body['LanguageID'] = _appState.getLanguageID().toString(); + body['Latitude'] = _appState.userLat.toString(); + body['Longitude'] = _appState.userLong.toString(); + body['DeviceTypeID'] = _appState.deviceTypeID; + if (_appState.appAuthToken.isNotEmpty) { + body[_appState.isAuthenticated ? 'TokenID' : 'LogInTokenID'] = _appState.appAuthToken; } - body.removeWhere((key, value) => value == null); - log("body: ${json.encode(body)}"); - log("uri: ${Uri.parse(url.trim())}"); + // body['TokenID'] = "@dm!n"; + // body['PatientID'] = "4767477"; + } - final bool networkStatus = await Utils.checkConnection(bypassConnectionCheck: bypassConnectionCheck); + body.removeWhere((key, value) => value == null); + log("body: ${json.encode(body)}"); + log("uri: ${Uri.parse(url.trim())}"); - if (!networkStatus) { - onFailure( - 'Please Check The Internet Connection 1', - -1, - failureType: ConnectivityFailure("Please Check The Internet Connection 1"), - ); - _analytics.errorTracking.log("internet_connectivity", error: "no internet available"); - return; - } + final bool networkStatus = await Utils.checkConnection(bypassConnectionCheck: bypassConnectionCheck); - final response = await http.post(Uri.parse(url.trim()), body: json.encode(body), headers: headers); - final int statusCode = response.statusCode; - log("response.body: ${response.body}"); - if (statusCode < 200 || statusCode >= 400) { - var parsed = json.decode(utf8.decode(response.bodyBytes)); - onFailure('Error While Fetching data', statusCode, failureType: StatusCodeFailure("Error While Fetching data")); - logApiEndpointError(endPoint, 'Error While Fetching data', statusCode); + if (!networkStatus) { + onFailure( + 'Please Check The Internet Connection 1', + -1, + failureType: ConnectivityFailure("Please Check The Internet Connection 1"), + ); + _analytics.errorTracking.log("internet_connectivity", error: "no internet available"); + return; + } + + final response = await http.post(Uri.parse(url.trim()), body: json.encode(body), headers: headers); + final int statusCode = response.statusCode; + log("response.body: ${response.body}"); + if (statusCode < 200 || statusCode >= 400) { + var parsed = json.decode(utf8.decode(response.bodyBytes)); + onFailure('Error While Fetching data', statusCode, failureType: StatusCodeFailure("Error While Fetching data")); + logApiEndpointError(endPoint, 'Error While Fetching data', statusCode); + } else { + var parsed = json.decode(utf8.decode(response.bodyBytes)); + if (isAllowAny) { + onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage']); } else { - var parsed = json.decode(utf8.decode(response.bodyBytes)); - if (isAllowAny) { - onSuccess(parsed, statusCode, messageStatus: 1, errorMessage: ""); + if (parsed['Response_Message'] != null) { + onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); } else { - if (parsed['Response_Message'] != null) { + if (parsed['ErrorType'] == 4) { + //TODO : handle app update + logApiEndpointError(endPoint, parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode); + } + if (parsed['ErrorType'] == 2) { + // todo: handle Logout + logApiEndpointError(endPoint, "session logged out", statusCode); + } + if (isAllowAny) { onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); - } else { - if (parsed['ErrorType'] == 4) { - //TODO : handle app update + } else if (parsed['IsAuthenticated'] == null) { + if (parsed['isSMSSent'] == true) { + onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); + } else if (parsed['MessageStatus'] == 1) { + onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); + } else if (parsed['Result'] == 'OK') { + onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); + } else { + onFailure( + parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], + statusCode, + failureType: MessageStatusFailure(parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']), + ); logApiEndpointError(endPoint, parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode); } - if (parsed['ErrorType'] == 2) { - // todo: handle Logout - logApiEndpointError(endPoint, "session logged out", statusCode); - } - if (isAllowAny) { - onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); - } else if (parsed['IsAuthenticated'] == null) { - if (parsed['isSMSSent'] == true) { - onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); - } else if (parsed['MessageStatus'] == 1) { - onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); - } else if (parsed['Result'] == 'OK') { - onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); - } else { - onFailure( - parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], - statusCode, - failureType: MessageStatusFailure(parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']), - ); - logApiEndpointError(endPoint, parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode); - } - } else if (parsed['MessageStatus'] == 1 || parsed['SMSLoginRequired'] == true) { + } else if (parsed['MessageStatus'] == 1 || parsed['SMSLoginRequired'] == true) { + onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); + } else if (parsed['MessageStatus'] == 2 && parsed['IsAuthenticated']) { + if (parsed['SameClinicApptList'] != null) { onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); - } else if (parsed['MessageStatus'] == 2 && parsed['IsAuthenticated']) { - if (parsed['SameClinicApptList'] != null) { - onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); - } else { - if (parsed['message'] == null && parsed['ErrorEndUserMessage'] == null) { - if (parsed['ErrorSearchMsg'] == null) { - onFailure( - "Server Error found with no available message", - statusCode, - failureType: ServerFailure("Error While Fetching data"), - ); - logApiEndpointError(endPoint, "Server Error found with no available message", statusCode); - } else { - onFailure( - parsed['ErrorSearchMsg'], - statusCode, - failureType: ServerFailure("Error While Fetching data"), - ); - logApiEndpointError(endPoint, parsed['ErrorSearchMsg'], statusCode); - } - } else { - onFailure( - parsed['message'] ?? parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], - statusCode, - failureType: UserIntimationFailure(parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']), - ); - logApiEndpointError(endPoint, parsed['message'] ?? parsed['message'], statusCode); - } - } - } else if (!parsed['IsAuthenticated']) { - } else { - if (parsed['SameClinicApptList'] != null) { - onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); - } else { - if (parsed['message'] != null) { + if (parsed['message'] == null && parsed['ErrorEndUserMessage'] == null) { + if (parsed['ErrorSearchMsg'] == null) { onFailure( - parsed['message'] ?? parsed['message'], + "Server Error found with no available message", statusCode, failureType: ServerFailure("Error While Fetching data"), ); - logApiEndpointError(endPoint, parsed['message'] ?? parsed['message'], statusCode); + logApiEndpointError(endPoint, "Server Error found with no available message", statusCode); } else { onFailure( - parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], + parsed['ErrorSearchMsg'], statusCode, failureType: ServerFailure("Error While Fetching data"), ); - logApiEndpointError(endPoint, parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode); + logApiEndpointError(endPoint, parsed['ErrorSearchMsg'], statusCode); } + } else { + onFailure( + parsed['message'] ?? parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], + statusCode, + failureType: UserIntimationFailure(parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']), + ); + logApiEndpointError(endPoint, parsed['message'] ?? parsed['message'], statusCode); + } + } + } else if (!parsed['IsAuthenticated']) { + } else { + if (parsed['SameClinicApptList'] != null) { + onSuccess(parsed, statusCode, messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); + } else { + if (parsed['message'] != null) { + onFailure( + parsed['message'] ?? parsed['message'], + statusCode, + failureType: ServerFailure("Error While Fetching data"), + ); + logApiEndpointError(endPoint, parsed['message'] ?? parsed['message'], statusCode); + } else { + onFailure( + parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], + statusCode, + failureType: ServerFailure("Error While Fetching data"), + ); + logApiEndpointError(endPoint, parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage'], statusCode); } } } } } + } // } catch (e, stackTrace) { // _loggerService.errorLogs(stackTrace.toString()); // if (e.toString().contains("ClientException")) { diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index 35371cc..a2cbc49 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -121,4 +121,5 @@ class AppAnimations { static const String register = '$lottieBasePath/register.json'; static const String checkmark = '$lottieBasePath/checkmark.json'; static const String loadingAnimation = '$lottieBasePath/Loader.json'; + static const String errorAnimation = '$lottieBasePath/ErrorAnimation.json'; } diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart index 75c0cf9..216da22 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -323,6 +323,19 @@ class Utils { ).center; } + static Widget getErrorWidget({String? loadingText}) { + return Column( + mainAxisAlignment: MainAxisAlignment.center, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + Lottie.asset(AppAnimations.errorAnimation, repeat: true, reverse: false, frameRate: FrameRate(60), width: 100.h, height: 100.h, fit: BoxFit.fill), + SizedBox(height: 8.h), + (loadingText ?? LocaleKeys.loadingText.tr()).toText16(color: AppColors.blackColor), + SizedBox(height: 8.h), + ], + ).center; + } + static bool isVidaPlusProject(AppState appState, int projectID) { bool isVidaPlus = false; for (var element in appState.vidaPlusProjectList) { diff --git a/lib/features/my_appointments/my_appointments_repo.dart b/lib/features/my_appointments/my_appointments_repo.dart index 8fc477c..804f9f9 100644 --- a/lib/features/my_appointments/my_appointments_repo.dart +++ b/lib/features/my_appointments/my_appointments_repo.dart @@ -29,6 +29,9 @@ abstract class MyAppointmentsRepo { Future>> cancelAppointment({required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel}); Future>> confirmAppointment({required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel}); + + Future>> sendCheckInNfcRequest( + {required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, required String scannedCode, required int checkInType}); } class MyAppointmentsRepoImp implements MyAppointmentsRepo { @@ -350,4 +353,46 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future> sendCheckInNfcRequest( + {required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, required String scannedCode, required int checkInType}) async { + Map requestBody = { + "AppointmentNo": patientAppointmentHistoryResponseModel.appointmentNo, + "NFC_Code": scannedCode, + "ProjectID": patientAppointmentHistoryResponseModel.projectID, + "ClinicID": patientAppointmentHistoryResponseModel.clinicID, + "CheckinBy": checkInType, + }; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + SEND_CHECK_IN_NFC_REQUEST, + body: requestBody, + 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()); + } + }, + isAllowAny: true, + ); + 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 2815977..4a921da 100644 --- a/lib/features/my_appointments/my_appointments_view_model.dart +++ b/lib/features/my_appointments/my_appointments_view_model.dart @@ -229,4 +229,28 @@ class MyAppointmentsViewModel extends ChangeNotifier { }, ); } + + Future sendCheckInNfcRequest( + {required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, + required String scannedCode, + required int checkInType, + Function(dynamic)? onSuccess, + Function(String)? onError}) async { + final result = await myAppointmentsRepo.sendCheckInNfcRequest(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel, scannedCode: scannedCode, checkInType: checkInType); + + result.fold( + (failure) async => await errorHandlerService.handleError(failure: failure), + (apiResponse) { + if (apiResponse.messageStatus == 2) { + onError!(apiResponse.errorMessage!); + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } } diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart index d6ff672..7188620 100644 --- a/lib/presentation/appointments/appointment_details_page.dart +++ b/lib/presentation/appointments/appointment_details_page.dart @@ -522,6 +522,7 @@ class _AppointmentDetailsPageState extends State { title: LocaleKeys.onlineCheckIn.tr(), child: AppointmentCheckinBottomSheet( patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, + myAppointmentsViewModel: myAppointmentsViewModel, ), callBackFunc: () {}, isFullScreen: false); diff --git a/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart b/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart index f3f05b3..f8e0f59 100644 --- a/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart +++ b/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart @@ -1,44 +1,138 @@ +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_nfc_kit/flutter_nfc_kit.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/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/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/theme/colors.dart'; +import 'package:barcode_scan2/barcode_scan2.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/nfc/nfc_reader_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; class AppointmentCheckinBottomSheet extends StatelessWidget { - AppointmentCheckinBottomSheet({super.key, required this.patientAppointmentHistoryResponseModel}); + AppointmentCheckinBottomSheet({super.key, required this.patientAppointmentHistoryResponseModel, required this.myAppointmentsViewModel}); PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel; + MyAppointmentsViewModel myAppointmentsViewModel; + + bool _supportsNFC = false; @override Widget build(BuildContext context) { + FlutterNfcKit.nfcAvailability.then((value) { + _supportsNFC = (value == NFCAvailability.available); + }); return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - checkInOptionCard(AppAssets.checkin_location_icon, "Live Location".needTranslation, "".needTranslation), + checkInOptionCard( + AppAssets.checkin_location_icon, + "Live Location".needTranslation, + "Verify your location to be at hospital to check in".needTranslation, + ).onPress(() { + Navigator.of(context).pop(); + }), SizedBox(height: 16.h), - checkInOptionCard(AppAssets.checkin_nfc_icon, "NFC (Near Field Communication)".needTranslation, "".needTranslation), + checkInOptionCard( + AppAssets.checkin_nfc_icon, + "NFC (Near Field Communication)".needTranslation, + "Scan your phone via NFC board to check in".needTranslation, + ).onPress(() { + Future.delayed(const Duration(milliseconds: 500), () { + showNfcReader(context, onNcfScan: (String nfcId) { + Future.delayed(const Duration(milliseconds: 100), () { + sendCheckInRequest(nfcId, context); + }); + }, onCancel: () {}); + }); + }), SizedBox(height: 16.h), - checkInOptionCard(AppAssets.checkin_qr_icon, "QR Code".needTranslation, "".needTranslation), + checkInOptionCard( + AppAssets.checkin_qr_icon, + "QR Code".needTranslation, + "Scan QR code with your camera to check in".needTranslation, + ).onPress(() async { + String onlineCheckInQRCode = (await BarcodeScanner.scan().then((value) => value.rawContent)); + if (onlineCheckInQRCode != "") { + sendCheckInRequest(onlineCheckInQRCode, context); + } else {} + }), ], ); } Widget checkInOptionCard(String icon, String title, String subTitle) { return Container( - height: 120.h, decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: false, ), child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Utils.buildSvgWithAssets(icon: icon), + Utils.buildSvgWithAssets(icon: icon, width: 40.h, height: 40.h, fit: BoxFit.fill), + SizedBox(height: 16.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + title.toText16(isBold: true, color: AppColors.textColor), + subTitle.toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), + ], + ), + Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon, + iconColor: AppColors.blackColor, + width: 18.h, + height: 13.h, + fit: BoxFit.contain, + ), + ], + ), ], - ), + ).paddingAll(16.h), + ); + } + + void sendCheckInRequest(String scannedCode, BuildContext context) async { + showCommonBottomSheet(context, + child: Utils.getLoadingWidget(), callBackFunc: (str) {}, title: "", height: ResponsiveExtension.screenHeight * 0.3, isCloseButtonVisible: false, isDismissible: false, isFullScreen: false); + await myAppointmentsViewModel.sendCheckInNfcRequest( + patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel, + scannedCode: scannedCode, + checkInType: 2, + onSuccess: (apiResponse) { + Navigator.of(context).pop(); + showCommonBottomSheetWithoutHeight(context, title: "Success".needTranslation, child: Utils.getSuccessWidget(loadingText: LocaleKeys.success.tr()), callBackFunc: () { + Navigator.of(context).pop(); + Navigator.pushAndRemoveUntil( + context, + FadePage( + page: LandingNavigation(), + ), + (r) => false); + Navigator.of(context).push( + FadePage(page: MyAppointmentsPage()), + ); + }, isFullScreen: false); + }, + onError: (error) { + Navigator.of(context).pop(); + showCommonBottomSheetWithoutHeight(context, title: "Error".needTranslation, child: Utils.getErrorWidget(loadingText: error), callBackFunc: () { + Navigator.of(context).pop(); + }, isFullScreen: false); + }, ); } } diff --git a/lib/presentation/appointments/widgets/appointment_queueing_screen.dart b/lib/presentation/appointments/widgets/appointment_queueing_screen.dart new file mode 100644 index 0000000..e69de29 diff --git a/lib/widgets/nfc/nfc_reader_sheet.dart b/lib/widgets/nfc/nfc_reader_sheet.dart new file mode 100644 index 0000000..b76b36f --- /dev/null +++ b/lib/widgets/nfc/nfc_reader_sheet.dart @@ -0,0 +1,212 @@ +import 'dart:io'; + +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_nfc_kit/flutter_nfc_kit.dart'; +import 'package:flutter_svg/flutter_svg.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/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'; + +void showNfcReader(BuildContext context, {required Function onNcfScan, required VoidCallback onCancel}) { + showModalBottomSheet( + context: context, + enableDrag: false, + isDismissible: true, + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.only(topLeft: Radius.circular(12), topRight: Radius.circular(12)), + ), + backgroundColor: Colors.white, + builder: (context) { + return NfcLayout( + onNcfScan: onNcfScan, + onCancel: onCancel, + ); + }); +} + +class NfcLayout extends StatefulWidget { + Function? onNcfScan; + VoidCallback? onCancel; + + NfcLayout({this.onNcfScan, this.onCancel}); + + @override + _NfcLayoutState createState() => _NfcLayoutState(); +} + +class _NfcLayoutState extends State { + bool _reading = false; + Widget? mainWidget; + late String nfcId; + + @override + void initState() { + super.initState(); + readNFC(); + } + + void readNFC() async { + FlutterNfcKit.finish(); + FlutterNfcKit.poll(timeout: Duration(seconds: 10), androidPlatformSound: true, androidCheckNDEF: false, iosMultipleTagMessage: "Multiple tags found!").then((value) async { + setState(() { + _reading = true; + mainWidget = doneNfc(); + }); + Future.delayed(const Duration(milliseconds: 500), () async { + await FlutterNfcKit.finish(); + widget.onNcfScan!(nfcId); + Navigator.pop(context); + }); + nfcId = value.id; + }).catchError((err) { + print(err); + Navigator.of(context).pop(); + }); + } + + @override + Widget build(BuildContext context) { + // return SizedBox(); + (mainWidget == null && !_reading) ? mainWidget = scanNfc() : mainWidget = doneNfc(); + return Platform.isAndroid ? AnimatedSwitcher(duration: Duration(milliseconds: 500), child: mainWidget) : SizedBox.shrink(); + } + + Widget scanNfc() { + return Container( + key: ValueKey(1), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + height: 30, + ), + Text( + "Ready To Scan", + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 24, + ), + ), + SizedBox( + height: 30, + ), + SvgPicture.asset( + "assets/images/nfc/contactless.svg", + height: MediaQuery.of(context).size.width / 3, + ), + SizedBox( + height: 30, + ), + Text( + "Approach an NFC Tag", + style: TextStyle( + fontSize: 18, + ), + ), + SizedBox( + height: 30, + ), + ButtonTheme( + minWidth: MediaQuery.of(context).size.width / 1.2, + height: 45.0, + buttonColor: Colors.grey[300], + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6), + ), + child: CustomButton( + text: LocaleKeys.cancel.tr(), + onPressed: () { + widget.onCancel!(); + Navigator.pop(context); + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12.h, + height: 40.h, + icon: AppAssets.cancel, + iconColor: AppColors.whiteColor, + iconSize: 16.h, + ), + ), + SizedBox( + height: 30, + ), + ], + ), + ); + } + + Widget doneNfc() { + return Container( + key: ValueKey(2), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + SizedBox( + height: 30, + ), + Text( + "Successfully Scanned", + style: TextStyle( + fontWeight: FontWeight.bold, + fontSize: 24, + ), + ), + SizedBox( + height: 30, + ), + Image.asset( + "assets/images/nfc/ic_done.png", + height: MediaQuery.of(context).size.width / 3, + ), + SizedBox( + height: 30, + ), + Text( + "Approach an NFC Tag", + style: TextStyle( + fontSize: 18, + ), + ), + SizedBox( + height: 30, + ), + ButtonTheme( + minWidth: MediaQuery.of(context).size.width / 1.2, + height: 45.0, + buttonColor: Colors.grey[300], + shape: RoundedRectangleBorder( + borderRadius: BorderRadius.circular(6), + ), + child: CustomButton( + text: LocaleKeys.done.tr(), + onPressed: () { + widget.onCancel!(); + Navigator.pop(context); + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12.h, + height: 40.h, + icon: AppAssets.cancel, + iconColor: AppColors.whiteColor, + iconSize: 16.h, + ), + ), + SizedBox( + height: 30, + ), + ], + ), + ); + } +} diff --git a/pubspec.yaml b/pubspec.yaml index 7536aaa..afbac11 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -73,6 +73,8 @@ dependencies: maps_launcher: ^3.0.0+1 amazon_payfort: ^1.1.4 network_info_plus: ^6.1.4 + flutter_nfc_kit: ^3.6.0 + barcode_scan2: ^4.5.1 dev_dependencies: flutter_test: From 6b2ec5aa2cfd61d7b331171b6a341df650f3fd20 Mon Sep 17 00:00:00 2001 From: Haroon Amjad <> Date: Tue, 9 Sep 2025 21:42:41 +0300 Subject: [PATCH 3/3] updates, fixes & service privileges --- lib/core/api_consts.dart | 1 + lib/core/app_state.dart | 23 ++- .../privilege/HMCProjectListModel.dart | 15 ++ .../privilege/PrivilegeModel.dart | 24 +++ .../privilege/ProjectDetailListModel.dart | 32 ++++ .../VidaPlusProjectListModel.dart | 0 lib/core/utils/request_utils.dart | 28 +-- lib/core/utils/utils.dart | 31 ++++ .../authentication/authentication_repo.dart | 39 +++- .../authentication_view_model.dart | 166 +++++++++++++----- .../appointment_checkin_bottom_sheet.dart | 28 ++- .../authentication/quick_login.dart | 2 +- .../authentication/saved_login_screen.dart | 13 +- lib/presentation/home/landing_page.dart | 10 +- lib/splashPage.dart | 24 ++- 15 files changed, 343 insertions(+), 93 deletions(-) create mode 100644 lib/core/common_models/privilege/HMCProjectListModel.dart create mode 100644 lib/core/common_models/privilege/PrivilegeModel.dart create mode 100644 lib/core/common_models/privilege/ProjectDetailListModel.dart rename lib/core/common_models/{ => privilege}/VidaPlusProjectListModel.dart (100%) diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index a034570..daa094e 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -803,6 +803,7 @@ class ApiConsts { static final String insertPatientDeviceIMEIData = 'Services/Patients.svc/REST/Patient_INSERTDeviceIMEI'; static final String insertPatientMobileData = 'Services/MobileNotifications.svc/REST/Insert_PatientMobileDeviceInfo'; + static final String getPrivileges = 'Services/Patients.svc/REST/Service_Privilege'; // 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 4ab98f6..3c4555e 100644 --- a/lib/core/app_state.dart +++ b/lib/core/app_state.dart @@ -1,7 +1,10 @@ import 'dart:io'; import 'package:easy_localization/easy_localization.dart'; -import 'package:hmg_patient_app_new/core/common_models/VidaPlusProjectListModel.dart'; +import 'package:hmg_patient_app_new/core/common_models/privilege/HMCProjectListModel.dart'; +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/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'; @@ -37,7 +40,7 @@ class AppState { if (isFamily) { _authenticatedChildUser = authenticatedUser; } else { - setIsAuthenticated = true; + setIsAuthenticated = true; _authenticatedRootUser = authenticatedUser; } } @@ -93,11 +96,26 @@ class AppState { set setDeviceTypeID(v) => deviceTypeID = v; List vidaPlusProjectList = []; + List privilegeModelList = []; + List hMCProjectListModel = []; + List projectDetailListModel = []; setVidaPlusProjectList(List vidaPlusProjectListModelInput) { vidaPlusProjectList = vidaPlusProjectListModelInput; } + setPrivilegeModelList(List privilegeModelListInput) { + privilegeModelList = privilegeModelListInput; + } + + setHMCProjectList(List hMCProjectListModelInput) { + hMCProjectListModel = hMCProjectListModelInput; + } + + setProjectsDetailList(List projectDetailListModelInput) { + projectDetailListModel = projectDetailListModelInput; + } + CheckUserStatusResponseNHIC? _nHICUserData; CheckUserStatusResponseNHIC get getNHICUserData => _nHICUserData!; @@ -106,7 +124,6 @@ class AppState { _nHICUserData = value; } - RegistrationDataModelPayload? _userRegistrationPayload; RegistrationDataModelPayload get getUserRegistrationPayload => _userRegistrationPayload ?? RegistrationDataModelPayload(); diff --git a/lib/core/common_models/privilege/HMCProjectListModel.dart b/lib/core/common_models/privilege/HMCProjectListModel.dart new file mode 100644 index 0000000..6af7bfb --- /dev/null +++ b/lib/core/common_models/privilege/HMCProjectListModel.dart @@ -0,0 +1,15 @@ +class HMCProjectListModel { + int? projectID; + + HMCProjectListModel({this.projectID}); + + HMCProjectListModel.fromJson(Map json) { + projectID = json['ProjectID']; + } + + Map toJson() { + final Map data = new Map(); + data['ProjectID'] = this.projectID; + return data; + } +} diff --git a/lib/core/common_models/privilege/PrivilegeModel.dart b/lib/core/common_models/privilege/PrivilegeModel.dart new file mode 100644 index 0000000..587490b --- /dev/null +++ b/lib/core/common_models/privilege/PrivilegeModel.dart @@ -0,0 +1,24 @@ +class PrivilegeModel { + int? iD; + String? serviceName; + bool? privilege; + dynamic region; + + PrivilegeModel({this.iD, this.serviceName, this.privilege, this.region}); + + PrivilegeModel.fromJson(Map json) { + iD = json['ID']; + serviceName = json['ServiceName']; + privilege = json['Previlege']; + region = json['Region']; + } + + Map toJson() { + final Map data = new Map(); + data['ID'] = this.iD; + data['ServiceName'] = this.serviceName; + data['Previlege'] = this.privilege; + data['Region'] = this.region; + return data; + } +} diff --git a/lib/core/common_models/privilege/ProjectDetailListModel.dart b/lib/core/common_models/privilege/ProjectDetailListModel.dart new file mode 100644 index 0000000..782c376 --- /dev/null +++ b/lib/core/common_models/privilege/ProjectDetailListModel.dart @@ -0,0 +1,32 @@ +class ProjectDetailListModel { + int? projectID; + String? latitude; + String? longitude; + int? geofenceRadius; + String? checkInQrCode; + + ProjectDetailListModel( + {this.projectID, + this.latitude, + this.longitude, + this.geofenceRadius, + this.checkInQrCode}); + + ProjectDetailListModel.fromJson(Map json) { + projectID = json['ProjectID']; + latitude = json['Latitude']; + longitude = json['Longitude']; + geofenceRadius = json['GeofenceRadius']; + checkInQrCode = json['CheckInQrCode']; + } + + Map toJson() { + final Map data = new Map(); + data['ProjectID'] = this.projectID; + data['Latitude'] = this.latitude; + data['Longitude'] = this.longitude; + data['GeofenceRadius'] = this.geofenceRadius; + data['CheckInQrCode'] = this.checkInQrCode; + return data; + } +} diff --git a/lib/core/common_models/VidaPlusProjectListModel.dart b/lib/core/common_models/privilege/VidaPlusProjectListModel.dart similarity index 100% rename from lib/core/common_models/VidaPlusProjectListModel.dart rename to lib/core/common_models/privilege/VidaPlusProjectListModel.dart diff --git a/lib/core/utils/request_utils.dart b/lib/core/utils/request_utils.dart index 1bf1e06..f54432c 100644 --- a/lib/core/utils/request_utils.dart +++ b/lib/core/utils/request_utils.dart @@ -20,6 +20,9 @@ class RequestUtils { bool fileNo = false; if (nationId.isNotEmpty) { fileNo = nationId.length < 10; + if (fileNo) { + patientId = int.tryParse(nationId); + } } var request = SendActivationRequest(); if (phoneNumber.isNotEmpty) { @@ -51,18 +54,17 @@ class RequestUtils { return request; } - static dynamic getCommonRequestWelcome({ - required String phoneNumber, - required OTPTypeEnum otpTypeEnum, - required String? deviceToken, - required bool patientOutSA, - required String? loginTokenID, - required var registeredData, - int? patientId, - required String nationIdText, - required String countryCode, - required int loginType - }) { + static dynamic getCommonRequestWelcome( + {required String phoneNumber, + required OTPTypeEnum otpTypeEnum, + required String? deviceToken, + required bool patientOutSA, + required String? loginTokenID, + required var registeredData, + int? patientId, + required String nationIdText, + required String countryCode, + required int loginType}) { bool fileNo = false; if (nationIdText.isNotEmpty) { fileNo = nationIdText.length < 10; @@ -86,7 +88,7 @@ class RequestUtils { } else { if (fileNo) { request.patientID = patientId ?? int.parse(nationIdText); - request.patientIdentificationID = request.nationalID = "" as int?; + request.patientIdentificationID = request.nationalID = 0; request.searchType = 2; } else { request.patientID = 0; diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart index 216da22..ebb7037 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -10,6 +10,7 @@ import 'package:fluttertoast/fluttertoast.dart'; import 'package:google_api_availability/google_api_availability.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/common_models/privilege/ProjectDetailListModel.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/extensions/string_extensions.dart'; @@ -22,6 +23,8 @@ import 'package:hmg_patient_app_new/widgets/loading_dialog.dart'; import 'package:lottie/lottie.dart'; import 'package:shared_preferences/shared_preferences.dart'; +import 'dart:math' as dartMath; + class Utils { static AppState appState = getIt.get(); static NavigationService navigationService = getIt.get(); @@ -346,6 +349,34 @@ class Utils { return isVidaPlus; } + static ProjectDetailListModel getProjectDetailObj(AppState appState, int projectID) { + ProjectDetailListModel projectDetailListModel = ProjectDetailListModel(); + for (var element in appState.projectDetailListModel) { + if (element.projectID == projectID) { + projectDetailListModel = element; + } + } + return projectDetailListModel; + } + + static double distance(double lat1, double lon1, double lat2, double lon2) { + const r = 6372.8; // Earth radius in kilometers + + final dLat = _toRadians(lat2 - lat1); + final dLon = _toRadians(lon2 - lon1); + final lat1Radians = _toRadians(lat1); + final lat2Radians = _toRadians(lat2); + + final a = _haversin(dLat) + dartMath.cos(lat1Radians) * dartMath.cos(lat2Radians) * _haversin(dLon); + final c = 2 * dartMath.asin(dartMath.sqrt(a)); + + return r * c; + } + + static double _toRadians(double degrees) => degrees * dartMath.pi / 180; + + static num _haversin(double radians) => dartMath.pow(dartMath.sin(radians / 2), 2); + static getPhoneNumberWithoutZero(String number) { String newNumber = ""; if (number.startsWith('0')) { diff --git a/lib/features/authentication/authentication_repo.dart b/lib/features/authentication/authentication_repo.dart index e64e4ba..d81094c 100644 --- a/lib/features/authentication/authentication_repo.dart +++ b/lib/features/authentication/authentication_repo.dart @@ -4,12 +4,15 @@ 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/common_models/privilege/PrivilegeModel.dart'; import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; import 'package:hmg_patient_app_new/features/authentication/models/request_models/check_activation_code_register_request_model.dart'; import 'package:hmg_patient_app_new/features/authentication/models/resp_models/select_device_by_imei.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; abstract class AuthenticationRepo { + Future>> getServicePrivilege(); + Future>> selectDeviceByImei({required String firebaseToken}); Future>> checkPatientAuthentication({required dynamic checkPatientAuthenticationReq}); @@ -32,7 +35,6 @@ abstract class AuthenticationRepo { Future>> insertPatientIMEIData({required dynamic patientIMEIDataRequest}); Future>> insertPatientDeviceData({required dynamic patientDeviceDataRequest}); - } class AuthenticationRepoImp implements AuthenticationRepo { @@ -390,7 +392,6 @@ class AuthenticationRepoImp implements AuthenticationRepo { @override Future> insertPatientDeviceData({required patientDeviceDataRequest}) { - try { GenericApiModel? apiResponse; Failure? failure; @@ -421,4 +422,38 @@ class AuthenticationRepoImp implements AuthenticationRepo { return Future.value(Left(UnknownFailure(e.toString()))); } } + + @override + Future>> getServicePrivilege() { + Map mapDevice = {}; + try { + GenericApiModel? apiResponse; + Failure? failure; + return apiClient.post( + ApiConsts.getPrivileges, + body: mapDevice, + 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()); + } + }, + ).then((_) { + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + }); + } catch (e) { + return Future.value(Left(UnknownFailure(e.toString()))); + } + } } diff --git a/lib/features/authentication/authentication_view_model.dart b/lib/features/authentication/authentication_view_model.dart index 0797c6d..0add5a0 100644 --- a/lib/features/authentication/authentication_view_model.dart +++ b/lib/features/authentication/authentication_view_model.dart @@ -6,6 +6,10 @@ import 'package:hijri_gregorian_calendar/hijri_gregorian_calendar.dart'; 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/common_models/nationality_country_model.dart'; +import 'package:hmg_patient_app_new/core/common_models/privilege/HMCProjectListModel.dart'; +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/core/enums.dart'; import 'package:hmg_patient_app_new/core/utils/loading_utils.dart'; import 'package:hmg_patient_app_new/core/utils/request_utils.dart'; @@ -53,7 +57,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; @@ -366,18 +374,18 @@ class AuthenticationViewModel extends ChangeNotifier { required Function(String? message) onWrongActivationCode, }) async { final request = RequestUtils.getCommonRequestWelcome( - phoneNumber: phoneNumberController.text, - otpTypeEnum: otpTypeEnum, - deviceToken: _appState.deviceToken, - patientOutSA: true, - loginTokenID: _appState.appAuthToken, - registeredData: null, - nationIdText: nationalIdController.text, - countryCode: selectedCountrySignup.countryCode, - loginType: loginTypeEnum.toInt - ).toJson(); - - bool isForRegister = (_appState.getUserRegistrationPayload.healthId != null || _appState.getUserRegistrationPayload.patientOutSa == true); + phoneNumber: phoneNumberController.text, + otpTypeEnum: otpTypeEnum, + deviceToken: _appState.deviceToken, + patientOutSA: true, + loginTokenID: _appState.appAuthToken, + registeredData: null, + nationIdText: nationalIdController.text, + countryCode: selectedCountrySignup.countryCode, + loginType: loginTypeEnum.toInt) + .toJson(); + + bool isForRegister = (_appState.getUserRegistrationPayload.healthId != null || _appState.getUserRegistrationPayload.patientOutSa == true); if (isForRegister) { if (_appState.getUserRegistrationPayload.patientOutSa == true) request['DOB'] = _appState.getUserRegistrationPayload.dob; request['HealthId'] = _appState.getUserRegistrationPayload.healthId; @@ -514,12 +522,12 @@ class AuthenticationViewModel extends ChangeNotifier { _localAuthService.authenticate().then((value) async { if (value) { // we have to handle this if verification true; - if(!_appState.isAuthenticated) { + if (!_appState.isAuthenticated) { loginTypeEnum = (_appState.deviceTypeID == 1 ? LoginTypeEnum.face : LoginTypeEnum.fingerprint); print(loginTypeEnum); - checkActivationCode(otpTypeEnum:OTPTypeEnum.faceIDFingerprint , activationCode: null, onWrongActivationCode: (String? message) {}); + checkActivationCode(otpTypeEnum: OTPTypeEnum.faceIDFingerprint, activationCode: null, onWrongActivationCode: (String? message) {}); insertPatientIMEIData((_appState.deviceTypeID == 1 ? LoginTypeEnum.face.toInt : LoginTypeEnum.fingerprint.toInt)); - }else { + } else { // authenticated = true; insertPatientIMEIData((_appState.deviceTypeID == 1 ? LoginTypeEnum.face.toInt : LoginTypeEnum.fingerprint.toInt)); } @@ -535,20 +543,19 @@ class AuthenticationViewModel extends ChangeNotifier { checkLastLoginStatus(Function() onSuccess) async { Future.delayed(Duration(seconds: 1), () { - if (_appState.getSelectDeviceByImeiRespModelElement != null && - (_appState.getSelectDeviceByImeiRespModelElement!.logInType == 1 || _appState.getSelectDeviceByImeiRespModelElement!.logInType == 4)) { - phoneNumberController.text = (_appState.getAuthenticatedUser()!.mobileNumber!.startsWith("0") - ? _appState.getAuthenticatedUser()!.mobileNumber!.replaceFirst("0", "") - : _appState.getAuthenticatedUser()!.mobileNumber)!; + if (_appState.getSelectDeviceByImeiRespModelElement != null && + (_appState.getSelectDeviceByImeiRespModelElement!.logInType == 1 || _appState.getSelectDeviceByImeiRespModelElement!.logInType == 4)) { + phoneNumberController.text = + (_appState.getAuthenticatedUser()!.mobileNumber!.startsWith("0") ? _appState.getAuthenticatedUser()!.mobileNumber!.replaceFirst("0", "") : _appState.getAuthenticatedUser()!.mobileNumber)!; nationalIdController.text = _appState.getAuthenticatedUser()!.nationalityId!; onSuccess(); - } else if((loginTypeEnum == LoginTypeEnum.sms || loginTypeEnum == LoginTypeEnum.whatsapp && _appState.getSelectDeviceByImeiRespModelElement == null) && _appState.getAuthenticatedUser() != null){ - phoneNumberController.text = (_appState.getAuthenticatedUser()!.mobileNumber!.startsWith("0") - ? _appState.getAuthenticatedUser()!.mobileNumber!.replaceFirst("0", "") - : _appState.getAuthenticatedUser()!.mobileNumber)!; - nationalIdController.text = _appState.getAuthenticatedUser()!.nationalityId!; - onSuccess(); - } + } else if ((loginTypeEnum == LoginTypeEnum.sms || loginTypeEnum == LoginTypeEnum.whatsapp && _appState.getSelectDeviceByImeiRespModelElement == null) && + _appState.getAuthenticatedUser() != null) { + phoneNumberController.text = + (_appState.getAuthenticatedUser()!.mobileNumber!.startsWith("0") ? _appState.getAuthenticatedUser()!.mobileNumber!.replaceFirst("0", "") : _appState.getAuthenticatedUser()!.mobileNumber)!; + nationalIdController.text = _appState.getAuthenticatedUser()!.nationalityId!; + onSuccess(); + } }); } @@ -583,9 +590,7 @@ class AuthenticationViewModel extends ChangeNotifier { }); } - - Future onRegistrationComplete() async{ - + Future onRegistrationComplete() async { // if (emailAddress.text.isEmpty) { // Utils.showErrorToast(TranslationBase.of(context).enterEmailAddress); // return; @@ -596,7 +601,6 @@ class AuthenticationViewModel extends ChangeNotifier { //authVM!.clearDefaultInputValues(); } - Future checkUserStatusForRegistration({required dynamic response, required dynamic request}) async { if (response is Map) { _appState.setAppAuthToken = response["LogInTokenID"]; @@ -703,22 +707,52 @@ class AuthenticationViewModel extends ChangeNotifier { _appState.setUserRegistrationPayload = RegistrationDataModelPayload.fromJson(request); } - Future insertPatientIMEIData(int loginType) async{ - - final resultEither = await _authenticationRepo.insertPatientIMEIData(patientIMEIDataRequest: PatientInsertDeviceImei(imei: _appState.deviceToken, deviceTypeId: _appState.getDeviceTypeID(), patientId: _appState.getAuthenticatedUser()!.patientId!, patientIdentificationNo:_appState.getAuthenticatedUser()!.nationalityId!, firstName: _appState.getAuthenticatedUser()!.firstName!, lastName: _appState.getAuthenticatedUser()!.lastName!, patientTypeId: _appState.getAuthenticatedUser()!.patientType, mobileNo:_appState.getAuthenticatedUser()!.mobileNumber!, logInTypeId: loginType, patientOutSa:_appState.getAuthenticatedUser()!.outSa!, outSa: _appState.getAuthenticatedUser()!.outSa == 1 ? true :false, biometricEnabled: loginType == 1 || loginType ==2 ? false :true, firstNameN:_appState.getAuthenticatedUser()!.firstNameN , lastNameN:_appState.getAuthenticatedUser()!.lastNameN ).toJson()); - resultEither.fold((failure) async => await _errorHandlerService.handleError(failure: failure), (apiResponse) async { - if (apiResponse.messageStatus == 1) { - log("Insert IMEI Success"); - insertPatientDeviceData( loginType); - } else { - log("Insert IMEI Failed"); - } - }); - - } - Future insertPatientDeviceData(int loginType) async{ + Future insertPatientIMEIData(int loginType) async { + final resultEither = await _authenticationRepo.insertPatientIMEIData( + patientIMEIDataRequest: PatientInsertDeviceImei( + imei: _appState.deviceToken, + deviceTypeId: _appState.getDeviceTypeID(), + patientId: _appState.getAuthenticatedUser()!.patientId!, + patientIdentificationNo: _appState.getAuthenticatedUser()!.patientIdentificationNo!, + firstName: _appState.getAuthenticatedUser()!.firstName!, + lastName: _appState.getAuthenticatedUser()!.lastName!, + patientTypeId: _appState.getAuthenticatedUser()!.patientType, + mobileNo: _appState.getAuthenticatedUser()!.mobileNumber!, + logInTypeId: loginType, + patientOutSa: _appState.getAuthenticatedUser()!.outSa!, + outSa: _appState.getAuthenticatedUser()!.outSa == 1 ? true : false, + biometricEnabled: loginType == 1 || loginType == 2 ? false : true, + firstNameN: _appState.getAuthenticatedUser()!.firstNameN, + lastNameN: _appState.getAuthenticatedUser()!.lastNameN, + ).toJson()); + resultEither.fold((failure) async => await _errorHandlerService.handleError(failure: failure), (apiResponse) async { + if (apiResponse.messageStatus == 1) { + log("Insert IMEI Success"); + insertPatientDeviceData(loginType); + } else { + log("Insert IMEI Failed"); + } + }); + } - final resultEither = await _authenticationRepo.insertPatientDeviceData(patientDeviceDataRequest: InsertPatientMobileDeviceInfo(deviceToken: _appState.deviceToken, deviceTypeId: _appState.getDeviceTypeID(), patientId: _appState.getAuthenticatedUser()!.patientId!, patientTypeId: _appState.getAuthenticatedUser()!.patientType, patientOutSa:_appState.getAuthenticatedUser()!.outSa!, loginType: loginType, languageId: _appState.getLanguageID(), latitude: _appState.userLat, longitude:_appState.userLong, voipToken: "", deviceType: _appState.deviceTypeID, patientMobileNumber:_appState.getAuthenticatedUser()!.mobileNumber, nationalId: _appState.getAuthenticatedUser()!.patientIdentificationNo, gender: _appState.getAuthenticatedUser()!.gender ).toJson()); + Future insertPatientDeviceData(int loginType) async { + final resultEither = await _authenticationRepo.insertPatientDeviceData( + patientDeviceDataRequest: InsertPatientMobileDeviceInfo( + deviceToken: _appState.deviceToken, + deviceTypeId: _appState.getDeviceTypeID(), + patientId: _appState.getAuthenticatedUser()!.patientId!, + patientTypeId: _appState.getAuthenticatedUser()!.patientType, + patientOutSa: _appState.getAuthenticatedUser()!.outSa!, + loginType: loginType, + languageId: _appState.getLanguageID(), + latitude: _appState.userLat, + longitude: _appState.userLong, + voipToken: "", + deviceType: _appState.deviceTypeID, + patientMobileNumber: _appState.getAuthenticatedUser()!.mobileNumber, + nationalId: _appState.getAuthenticatedUser()!.patientIdentificationNo, + gender: _appState.getAuthenticatedUser()!.gender) + .toJson()); resultEither.fold((failure) async => await _errorHandlerService.handleError(failure: failure), (apiResponse) async { if (apiResponse.messageStatus == 1) { log("Insert Device Data Success"); @@ -726,7 +760,6 @@ class AuthenticationViewModel extends ChangeNotifier { log("Insert IMEI Failed"); } }); - } @override @@ -736,4 +769,41 @@ class AuthenticationViewModel extends ChangeNotifier { myFocusNode.dispose(); super.dispose(); } + + Future getServicePrivilege() async { + final resultEither = await _authenticationRepo.getServicePrivilege(); + List privilegeModelList = []; + List vidaPlusProjectListModel = []; + List hMCProjectListModel = []; + List projectDetailListModel = []; + + resultEither.fold( + (failure) async => await _errorHandlerService.handleError(failure: failure), + (apiResponse) async { + if (apiResponse.messageStatus == 2) { + await _dialogService.showErrorBottomSheet(message: apiResponse.errorMessage ?? "ErrorEmpty"); + } else { + apiResponse.data["ServicePrivilegeList"].forEach((v) { + privilegeModelList.add(PrivilegeModel.fromJson(v)); + }); + _appState.setPrivilegeModelList(privilegeModelList); + + apiResponse.data["ProjectListVidaPlus"].forEach((v) { + vidaPlusProjectListModel.add(VidaPlusProjectListModel.fromJson(v)); + }); + _appState.setVidaPlusProjectList(vidaPlusProjectListModel); + + apiResponse.data["HMCProjectList"].forEach((v) { + hMCProjectListModel.add(HMCProjectListModel.fromJson(v)); + }); + _appState.setHMCProjectList(hMCProjectListModel); + + apiResponse.data["ProjectDetailList"].forEach((v) { + projectDetailListModel.add(ProjectDetailListModel.fromJson(v)); + }); + _appState.setProjectsDetailList(projectDetailListModel); + } + }, + ); + } } diff --git a/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart b/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart index f8e0f59..77fec61 100644 --- a/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart +++ b/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart @@ -2,6 +2,10 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:flutter_nfc_kit/flutter_nfc_kit.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/common_models/privilege/ProjectDetailListModel.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/location_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'; @@ -25,8 +29,12 @@ class AppointmentCheckinBottomSheet extends StatelessWidget { bool _supportsNFC = false; + late LocationUtils locationUtils; + ProjectDetailListModel projectDetailListModel = ProjectDetailListModel(); + @override Widget build(BuildContext context) { + AppState _appState = getIt.get(); FlutterNfcKit.nfcAvailability.then((value) { _supportsNFC = (value == NFCAvailability.available); }); @@ -38,7 +46,25 @@ class AppointmentCheckinBottomSheet extends StatelessWidget { "Live Location".needTranslation, "Verify your location to be at hospital to check in".needTranslation, ).onPress(() { - Navigator.of(context).pop(); + // locationUtils = LocationUtils( + // isShowConfirmDialog: false, + // navigationService: myAppointmentsViewModel.navigationService, + // appState: myAppointmentsViewModel.appState, + // ); + locationUtils.getCurrentLocation(callBack: (value) { + projectDetailListModel = Utils.getProjectDetailObj(_appState, patientAppointmentHistoryResponseModel.projectID); + double dist = Utils.distance(value.latitude, value.longitude, double.parse(projectDetailListModel.latitude!), double.parse(projectDetailListModel.longitude!)).ceilToDouble() * 1000; + print(dist); + if (dist <= projectDetailListModel.geofenceRadius!) { + sendCheckInRequest(projectDetailListModel.checkInQrCode!, context); + } else { + showCommonBottomSheetWithoutHeight(context, + title: "Error".needTranslation, + child: Utils.getErrorWidget(loadingText: "Please ensure you're within the hospital location to perform online check-in.".needTranslation), callBackFunc: () { + Navigator.of(context).pop(); + }, isFullScreen: false); + } + }); }), SizedBox(height: 16.h), checkInOptionCard( diff --git a/lib/presentation/authentication/quick_login.dart b/lib/presentation/authentication/quick_login.dart index c5f540b..ff70dab 100644 --- a/lib/presentation/authentication/quick_login.dart +++ b/lib/presentation/authentication/quick_login.dart @@ -122,7 +122,7 @@ class _QuickLogin extends State { child: CustomButton( text: LocaleKeys.notNow.tr(), onPressed: () { - Navigator.pop(context, true); + Navigator.pop(context, "true"); }, backgroundColor: Color(0xffFEE9EA), borderColor: Color(0xffFEE9EA), diff --git a/lib/presentation/authentication/saved_login_screen.dart b/lib/presentation/authentication/saved_login_screen.dart index 55cbadf..589616f 100644 --- a/lib/presentation/authentication/saved_login_screen.dart +++ b/lib/presentation/authentication/saved_login_screen.dart @@ -34,13 +34,17 @@ class _SavedLogin extends State { void initState() { authVm = context.read(); appState = getIt.get(); - loginType = LoginTypeExtension.fromValue(appState.getSelectDeviceByImeiRespModelElement!.logInType!)!; + loginType = LoginTypeExtension.fromValue(appState.getSelectDeviceByImeiRespModelElement!.logInType!)!; authVm.phoneNumberController.text = appState.getSelectDeviceByImeiRespModelElement!.mobile!.startsWith("0") ? appState.getSelectDeviceByImeiRespModelElement!.mobile!.replaceFirst("0", "") : appState.getSelectDeviceByImeiRespModelElement!.mobile!; authVm.nationalIdController.text = appState.getSelectDeviceByImeiRespModelElement!.identificationNo!; + if (loginType == LoginTypeEnum.fingerprint || loginType == LoginTypeEnum.face) { + authVm.loginWithFingerPrintFace(); + } + super.initState(); } @@ -80,8 +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") : '--') @@ -89,7 +92,8 @@ class _SavedLogin extends State { Container( margin: EdgeInsets.all(16.h), - child: Utils.buildSvgWithAssets(icon: getTypeIcons(appState.getSelectDeviceByImeiRespModelElement!.logInType!), height: 54, width: 54, iconColor: loginType.toInt == 4 ? null : AppColors.primaryRedColor)), + child: Utils.buildSvgWithAssets( + icon: getTypeIcons(appState.getSelectDeviceByImeiRespModelElement!.logInType!), height: 54, width: 54, iconColor: loginType.toInt == 4 ? null : AppColors.primaryRedColor)), // Face ID login button SizedBox( height: 45, @@ -232,7 +236,6 @@ class _SavedLogin extends State { textColor: Color(0xFF2E3039), borderWidth: 2, padding: EdgeInsets.fromLTRB(0, 14, 0, 14), - ), const Spacer(flex: 2), // OR divider diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index b55632b..55afffe 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -42,7 +42,7 @@ class _LandingPageState extends State { authVM.savePushTokenToAppState(); if (mounted) { authVM.checkLastLoginStatus(() { - showQuickLogin(context, false); + showQuickLogin(context, false); }); } super.initState(); @@ -326,19 +326,17 @@ class _LandingPageState extends State { context, title: "", child: QuickLogin( - isDone: isDone, + isDone: isDone, onPressed: () { // sharedPref.setBool(HAS_ENABLED_QUICK_LOGIN, true); authVM.loginWithFingerPrintFace(); }, ), - 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: (str) { isDone = true; - setState(() { - - }); + setState(() {}); }, ); } diff --git a/lib/splashPage.dart b/lib/splashPage.dart index b852cd4..f85f01e 100644 --- a/lib/splashPage.dart +++ b/lib/splashPage.dart @@ -9,6 +9,8 @@ import 'package:flutter_zoom_videosdk/native/zoom_videosdk.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/app_assets.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/presentation/authantication/login.dart'; import 'package:hmg_patient_app_new/presentation/home/landing_page.dart'; import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart'; @@ -26,6 +28,8 @@ class SplashPage extends StatefulWidget { } class _SplashScreenState extends State { + late AuthenticationViewModel authVm; + Future initializeStuff() async { Timer( Duration(milliseconds: 500), @@ -34,7 +38,8 @@ class _SplashScreenState extends State { PushNotificationHandler().init(context); // Asyncronously }, ); - Timer(Duration(seconds: 3, milliseconds: 500), () async { + await authVm.getServicePrivilege(); + Timer(Duration(seconds: 2, milliseconds: 500), () async { LocalNotification.init(onNotificationClick: (payload) {}); Navigator.of(context).pushReplacement( FadePage( @@ -51,8 +56,6 @@ class _SplashScreenState extends State { zoom.initSdk(initConfig); } - - /// load the Privilege from service Future loadPrivilege() async { // ProjectViewModel projectProvider = Provider.of(context, listen: false); @@ -71,9 +74,9 @@ class _SplashScreenState extends State { PushNotificationHandler().init(context); // Asyncronously } - @override void initState() { + authVm = context.read(); super.initState(); initializeStuff(); } @@ -87,8 +90,7 @@ class _SplashScreenState extends State { children: [ Padding( padding: EdgeInsets.symmetric(horizontal: 53), - child: Image.asset(AppAssets.hmg_logo, - fit: BoxFit.fitWidth, width: MediaQuery.of(context).size.width), + child: Image.asset(AppAssets.hmg_logo, fit: BoxFit.fitWidth, width: MediaQuery.of(context).size.width), ), Align( alignment: Alignment.bottomCenter, @@ -97,18 +99,12 @@ class _SplashScreenState extends State { children: [ Text( "Powered by", - style: TextStyle( - fontSize: 14, - fontWeight: FontWeight.w400, - color: AppColors.textColor, - letterSpacing: -0.56, - height: 16 / 14), + style: TextStyle(fontSize: 14, fontWeight: FontWeight.w400, color: AppColors.textColor, letterSpacing: -0.56, height: 16 / 14), ), SizedBox( height: 5, ), - Utils.buildSvgWithAssets( - icon: AppAssets.cloud_logo, width: 40, height: 40), + Utils.buildSvgWithAssets(icon: AppAssets.cloud_logo, width: 40, height: 40), SizedBox(height: 7), // Text( // "Version 1.1.0",