diff --git a/assets/images/svg/home_lab_result_icon.svg b/assets/images/svg/home_lab_result_icon.svg new file mode 100644 index 0000000..245048d --- /dev/null +++ b/assets/images/svg/home_lab_result_icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index b0a5c78..a75a1ab 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -176,8 +176,8 @@ class ApiClientImp implements ApiClient { body[_appState.isAuthenticated ? 'TokenID' : 'LogInTokenID'] = _appState.appAuthToken; } - // body['TokenID'] = "@dm!n"; - // body['PatientID'] = "4767477"; + body['TokenID'] = "@dm!n"; + body['PatientID'] = 3628599; } body.removeWhere((key, value) => value == null); diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index 0a414d0..aead424 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -131,6 +131,7 @@ class AppAssets { static const String smart_phone_fill = '$svgBasePath/smart_phone_fill.svg'; static const String touch_face_id = '$svgBasePath/touch_face_id.svg'; static const String minus = '$svgBasePath/minus.svg'; + static const String home_lab_result_icon = '$svgBasePath/home_lab_result_icon.svg'; //bottom navigation// static const String homeBottom = '$svgBasePath/home_bottom.svg'; diff --git a/lib/features/radiology/models/resp_models/patient_radiology_response_model.dart b/lib/features/radiology/models/resp_models/patient_radiology_response_model.dart index b5568bf..740c275 100644 --- a/lib/features/radiology/models/resp_models/patient_radiology_response_model.dart +++ b/lib/features/radiology/models/resp_models/patient_radiology_response_model.dart @@ -1,3 +1,5 @@ +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; + class PatientRadiologyResponseModel { String? setupID; int? projectID; @@ -6,7 +8,7 @@ class PatientRadiologyResponseModel { int? invoiceNo; int? doctorID; int? clinicID; - String? orderDate; + DateTime? orderDate; String? reportData; String? imageURL; String? procedureID; @@ -120,7 +122,7 @@ class PatientRadiologyResponseModel { invoiceNo = json['InvoiceNo']; doctorID = json['DoctorID']; clinicID = json['ClinicID']; - orderDate = json['OrderDate']; + orderDate = DateUtil.convertStringToDate(json['OrderDate']); reportData = json['ReportData']; imageURL = json['ImageURL']; procedureID = json['ProcedureID']; diff --git a/lib/features/radiology/radiology_repo.dart b/lib/features/radiology/radiology_repo.dart index 9db803d..0a44428 100644 --- a/lib/features/radiology/radiology_repo.dart +++ b/lib/features/radiology/radiology_repo.dart @@ -3,11 +3,17 @@ import 'package:hmg_patient_app_new/core/api/api_client.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/features/authentication/models/resp_models/authenticated_user_resp_model.dart'; import 'package:hmg_patient_app_new/features/radiology/models/resp_models/patient_radiology_response_model.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; abstract class RadiologyRepo { Future>>> getPatientRadiologyOrders({required String patientId}); + + Future>> getRadiologyImage({required PatientRadiologyResponseModel patientRadiologyResponseModel}); + + Future>> getRadiologyReportPDF({required PatientRadiologyResponseModel patientRadiologyResponseModel, required AuthenticatedUser authenticatedUser}); } class RadiologyRepoImp implements RadiologyRepo { @@ -58,4 +64,99 @@ class RadiologyRepoImp implements RadiologyRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future>> getRadiologyImage({required PatientRadiologyResponseModel patientRadiologyResponseModel}) async { + Map mapDevice = { + "InvoiceNo": Utils.isVidaPlusProject(patientRadiologyResponseModel.projectID!) ? "0" : patientRadiologyResponseModel.invoiceNo, + "InvoiceNo_VP": Utils.isVidaPlusProject(patientRadiologyResponseModel.projectID!) ? patientRadiologyResponseModel.invoiceNo : "0", + "LineItemNo": patientRadiologyResponseModel.invoiceLineItemNo, + "ProjectID": patientRadiologyResponseModel.projectID!, + "InvoiceType": patientRadiologyResponseModel.invoiceType!, + "ExamId": patientRadiologyResponseModel.examId ?? "", + }; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + GET_RAD_IMAGE_URL, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: response["Data"], + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } + + @override + Future>> getRadiologyReportPDF({required PatientRadiologyResponseModel patientRadiologyResponseModel, required AuthenticatedUser authenticatedUser}) async { + Map mapDevice = { + "InvoiceNo": Utils.isVidaPlusProject(patientRadiologyResponseModel.projectID!) ? 0 : patientRadiologyResponseModel.invoiceNo, + "InvoiceNo_VP": Utils.isVidaPlusProject(patientRadiologyResponseModel.projectID!) ? patientRadiologyResponseModel.invoiceNo : 0, + "LineItemNo": patientRadiologyResponseModel.invoiceLineItemNo, + "InvoiceLineItemNo": patientRadiologyResponseModel.invoiceLineItemNo, + "ProjectID": patientRadiologyResponseModel.projectID!, + "InvoiceType": patientRadiologyResponseModel.invoiceType!, + "SetupID": patientRadiologyResponseModel.setupID!, + // "ExamId": patientRadiologyResponseModel.examId ?? "", + "IsDownload": true, + 'ClinicName': patientRadiologyResponseModel.clinicDescription, + 'DateofBirth': authenticatedUser.dateofBirth, + 'DoctorName': patientRadiologyResponseModel.doctorName, + 'OrderDate': '${patientRadiologyResponseModel.orderDate!.year}-${patientRadiologyResponseModel.orderDate!.month}-${patientRadiologyResponseModel.orderDate!.day}', + 'PatientIditificationNum': authenticatedUser.patientIdentificationNo, + 'PatientMobileNumber': authenticatedUser.mobileNumber, + 'PatientName': "${authenticatedUser.firstName!} ${authenticatedUser.lastName!}", + 'ProjectName': patientRadiologyResponseModel.projectName, + 'RadResult': patientRadiologyResponseModel.reportData, + "To": authenticatedUser.emailAddress + }; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + SEND_RAD_REPORT_EMAIL, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: response["Base64Data"], + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } } diff --git a/lib/features/radiology/radiology_view_model.dart b/lib/features/radiology/radiology_view_model.dart index 1bdba04..3441881 100644 --- a/lib/features/radiology/radiology_view_model.dart +++ b/lib/features/radiology/radiology_view_model.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/features/authentication/models/resp_models/authenticated_user_resp_model.dart'; import 'package:hmg_patient_app_new/features/radiology/radiology_repo.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; @@ -6,17 +7,23 @@ import 'models/resp_models/patient_radiology_response_model.dart'; class RadiologyViewModel extends ChangeNotifier { bool isRadiologyOrdersLoading = false; + bool isRadiologyPDFReportLoading = false; RadiologyRepo radiologyRepo; ErrorHandlerService errorHandlerService; List patientRadiologyOrders = []; + String radiologyImageURL = ""; + String patientRadiologyReportPDFBase64 = ""; + RadiologyViewModel({required this.radiologyRepo, required this.errorHandlerService}); initRadiologyProvider() { patientRadiologyOrders.clear(); isRadiologyOrdersLoading = true; + isRadiologyPDFReportLoading = true; + radiologyImageURL = ""; getPatientRadiologyOrders(); notifyListeners(); } @@ -40,4 +47,50 @@ class RadiologyViewModel extends ChangeNotifier { }, ); } + + Future getRadiologyImage({required PatientRadiologyResponseModel patientRadiologyResponseModel, Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await radiologyRepo.getRadiologyImage(patientRadiologyResponseModel: patientRadiologyResponseModel); + + result.fold( + (failure) async => await errorHandlerService.handleError(failure: failure), + (apiResponse) { + if (apiResponse.messageStatus == 2) { + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + radiologyImageURL = apiResponse.data!; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } + + Future getRadiologyPDF( + {required PatientRadiologyResponseModel patientRadiologyResponseModel, required AuthenticatedUser authenticatedUser, Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await radiologyRepo.getRadiologyReportPDF(patientRadiologyResponseModel: patientRadiologyResponseModel, authenticatedUser: authenticatedUser); + + result.fold( + (failure) async => await errorHandlerService.handleError( + failure: failure, + onOkPressed: () { + onError!(failure.message); + }, + ), + (apiResponse) { + if (apiResponse.messageStatus == 2) { + onError!(apiResponse.errorMessage!); + // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); + } else if (apiResponse.messageStatus == 1) { + patientRadiologyReportPDFBase64 = apiResponse.data!; + isRadiologyPDFReportLoading = false; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } } diff --git a/lib/presentation/home/data/landing_page_data.dart b/lib/presentation/home/data/landing_page_data.dart index ad524e3..31b4598 100644 --- a/lib/presentation/home/data/landing_page_data.dart +++ b/lib/presentation/home/data/landing_page_data.dart @@ -80,7 +80,7 @@ class LandingPageData { ), ServiceCardData( serviceName: "lab_results", - icon: AppAssets.lab_result_icon, + icon: AppAssets.home_lab_result_icon, title: "My Lab", subtitle: "Results", backgroundColor: AppColors.whiteColor, @@ -90,7 +90,7 @@ class LandingPageData { ), ServiceCardData( serviceName: "radiology_results", - icon: AppAssets.lab_result_icon, + icon: AppAssets.home_lab_result_icon, title: "My Radiology", subtitle: "Results", backgroundColor: AppColors.whiteColor, diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index 09ab37e..22de759 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -416,8 +416,8 @@ class _MedicalFilePageState extends State { children: [ Image.network( prescriptionVM.patientPrescriptionOrders[index].doctorImageURL!, - width: 63.h, - height: 63.h, + width: 40.h, + height: 40.h, fit: BoxFit.fill, ).circle(100), SizedBox(width: 16.h), @@ -442,7 +442,7 @@ class _MedicalFilePageState extends State { ], ), ), - SizedBox(width: 40.h), + // SizedBox(width: 40.h), Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, width: 15.h, height: 15.h, fit: BoxFit.contain, iconColor: AppColors.textColor), ], ).onPress(() { diff --git a/lib/presentation/radiology/radiology_orders_page.dart b/lib/presentation/radiology/radiology_orders_page.dart index ad28a5a..c7732a4 100644 --- a/lib/presentation/radiology/radiology_orders_page.dart +++ b/lib/presentation/radiology/radiology_orders_page.dart @@ -119,9 +119,7 @@ class _RadiologyOrdersPageState extends State { children: [ AppCustomChipWidget( icon: AppAssets.doctor_calendar_icon, - labelText: model.isRadiologyOrdersLoading - ? "01 Jan 2025" - : DateUtil.formatDateToDate(DateUtil.convertStringToDate(model.patientRadiologyOrders[index].orderDate), false), + labelText: model.isRadiologyOrdersLoading ? "01 Jan 2025" : DateUtil.formatDateToDate(model.patientRadiologyOrders[index].orderDate!, false), ).toShimmer2(isShow: model.isRadiologyOrdersLoading), AppCustomChipWidget( labelText: model.isRadiologyOrdersLoading ? "01 Jan 2025" : model.patientRadiologyOrders[index].clinicDescription!, diff --git a/lib/presentation/radiology/radiology_result_page.dart b/lib/presentation/radiology/radiology_result_page.dart index cc4a4e7..f0f9af4 100644 --- a/lib/presentation/radiology/radiology_result_page.dart +++ b/lib/presentation/radiology/radiology_result_page.dart @@ -1,13 +1,24 @@ +import 'dart:async'; + import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/radiology/models/resp_models/patient_radiology_response_model.dart'; +import 'package:hmg_patient_app_new/features/radiology/radiology_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/lab/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package: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/loader/bottomsheet_loader.dart'; +import 'package:open_filex/open_filex.dart'; +import 'package:provider/provider.dart'; +import 'package:url_launcher/url_launcher.dart'; class RadiologyResultPage extends StatefulWidget { RadiologyResultPage({super.key, required this.patientRadiologyResponseModel}); @@ -19,57 +30,132 @@ class RadiologyResultPage extends StatefulWidget { } class _RadiologyResultPageState extends State { + late RadiologyViewModel radiologyViewModel; + + @override + void initState() { + scheduleMicrotask(() { + radiologyViewModel.getRadiologyImage(patientRadiologyResponseModel: widget.patientRadiologyResponseModel); + }); + super.initState(); + } + @override Widget build(BuildContext context) { + radiologyViewModel = Provider.of(context); + AppState _appState = getIt.get(); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, - body: CollapsingListView( - title: "Radiology Result".needTranslation, - child: SingleChildScrollView( - child: Padding( - padding: EdgeInsets.symmetric(horizontal: 24.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox(height: 24.h), - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 20.h, - hasShadow: true, - ), + body: Column( + children: [ + Expanded( + child: CollapsingListView( + title: "Radiology Result".needTranslation, + child: SingleChildScrollView( + child: Padding( + padding: EdgeInsets.symmetric(horizontal: 24.h), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - SizedBox(height: 16.h), - widget.patientRadiologyResponseModel.description!.toText16(isBold: true), - SizedBox(height: 8.h), - widget.patientRadiologyResponseModel.reportData!.trim().toText12(isBold: true, color: AppColors.textColorLight), - SizedBox(height: 16.h), - CustomButton( - text: "View Radiology Image".needTranslation, - onPressed: () async {}, - backgroundColor: AppColors.primaryRedColor, - borderColor: AppColors.primaryRedColor, - textColor: AppColors.whiteColor, - fontSize: 14, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, - icon: AppAssets.calendar, - iconColor: AppColors.whiteColor, - iconSize: 20.h, + SizedBox(height: 24.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: true, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 16.h), + widget.patientRadiologyResponseModel.description!.toText16(isBold: true), + SizedBox(height: 8.h), + widget.patientRadiologyResponseModel.reportData!.trim().toText12(isBold: true, color: AppColors.textColorLight), + SizedBox(height: 16.h), + CustomButton( + text: "View Radiology Image".needTranslation, + onPressed: () async { + if (radiologyViewModel.radiologyImageURL.isNotEmpty) { + Uri uri = Uri.parse(radiologyViewModel.radiologyImageURL); + launchUrl(uri, mode: LaunchMode.platformDefault, webOnlyWindowName: ""); + } else { + Utils.showToast("Radiology image not available".needTranslation); + } + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 14, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + icon: AppAssets.download, + iconColor: AppColors.whiteColor, + iconSize: 20.h, + ), + SizedBox(height: 16.h), + ], + ).paddingSymmetrical(16.h, 0.h), ), - SizedBox(height: 16.h), + SizedBox(height: 24.h), ], - ).paddingSymmetrical(16.h, 0.h), + ), ), - SizedBox(height: 24.h), - ], + ), + ), + ), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: true, ), + child: CustomButton( + text: "Download report".needTranslation, + onPressed: () async { + LoaderBottomSheet.showLoader(); + await radiologyViewModel.getRadiologyPDF(patientRadiologyResponseModel: widget.patientRadiologyResponseModel, authenticatedUser: _appState.getAuthenticatedUser()!, onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }).then((val) async { + LoaderBottomSheet.hideLoader(); + if (radiologyViewModel.patientRadiologyReportPDFBase64.isNotEmpty) { + String path = await Utils.createFileFromString(radiologyViewModel.patientRadiologyReportPDFBase64, "pdf"); + try { + OpenFilex.open(path); + } catch (ex) { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: "Cannot open file".needTranslation), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } + } + }); + }, + backgroundColor: AppColors.successColor, + borderColor: AppColors.successColor, + textColor: AppColors.whiteColor, + fontSize: 16, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 45.h, + icon: AppAssets.download, + iconColor: AppColors.whiteColor, + iconSize: 20.h, + ).paddingSymmetrical(24.h, 24.h), ), - ), + ], ), ); }