From 6f07bc7c2ccfe84e1ab217274958d0df3523ac36 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Wed, 3 Sep 2025 12:08:58 +0300 Subject: [PATCH 1/6] Radiology Orders implemented --- assets/langs/ar-SA.json | 3 +- assets/langs/en-US.json | 3 +- lib/core/dependencies.dart | 10 + .../patient_radiology_response_model.dart | 232 ++++++++++++++++ lib/features/radiology/radiology_repo.dart | 82 ++++++ .../radiology/radiology_view_model.dart | 45 ++++ lib/generated/locale_keys.g.dart | 1 + lib/main.dart | 7 + .../home/widgets/small_service_card.dart | 6 + lib/presentation/lab/lab_orders_page.dart | 12 +- .../radiology/radiology_orders_page.dart | 251 ++++++++++++++++++ 11 files changed, 640 insertions(+), 12 deletions(-) create mode 100644 lib/features/radiology/models/resp_models/patient_radiology_response_model.dart create mode 100644 lib/features/radiology/radiology_repo.dart create mode 100644 lib/features/radiology/radiology_view_model.dart create mode 100644 lib/presentation/radiology/radiology_orders_page.dart diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index 304dba1..6355b66 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -780,5 +780,6 @@ "loginOrRegister": "تسجيل الدخول أو التسجيل", "myFiles" : "ملفاتي", "resultsPending": "النتائج معلقة", - "resultsAvailable": "النتائج متاحة" + "resultsAvailable": "النتائج متاحة", + "viewReport": "عرض التقرير" } \ No newline at end of file diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index e369613..c18eb97 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -776,5 +776,6 @@ "loginOrRegister": "Login or Register", "myFiles": "My Files", "resultsPending": "Results Pending", - "resultsAvailable": "Results Available" + "resultsAvailable": "Results Available", + "viewReport": "View Report" } \ No newline at end of file diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index dca08fc..a9fc9d6 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -9,6 +9,8 @@ import 'package:hmg_patient_app_new/features/common/common_repo.dart'; import 'package:hmg_patient_app_new/features/lab/lab_repo.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_repo.dart'; +import 'package:hmg_patient_app_new/features/radiology/radiology_repo.dart'; +import 'package:hmg_patient_app_new/features/radiology/radiology_view_model.dart'; import 'package:hmg_patient_app_new/services/analytics/analytics_service.dart'; import 'package:hmg_patient_app_new/services/cache_service.dart'; import 'package:hmg_patient_app_new/services/dialog_service.dart'; @@ -59,6 +61,7 @@ class AppDependencies { getIt.registerLazySingleton(() => BookAppointmentsRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => MyAppointmentsRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => LabRepoImp(loggerService: getIt(), apiClient: getIt())); + getIt.registerLazySingleton(() => RadiologyRepoImp(loggerService: getIt(), apiClient: getIt())); // ViewModels // Global/shared VMs → LazySingleton @@ -70,6 +73,13 @@ class AppDependencies { ), ); + getIt.registerLazySingleton( + () => RadiologyViewModel( + radiologyRepo: getIt(), + errorHandlerService: getIt(), + ), + ); + getIt.registerLazySingleton( () => AuthenticationViewModel( authenticationRepo: getIt(), 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 new file mode 100644 index 0000000..b5568bf --- /dev/null +++ b/lib/features/radiology/models/resp_models/patient_radiology_response_model.dart @@ -0,0 +1,232 @@ +class PatientRadiologyResponseModel { + String? setupID; + int? projectID; + dynamic patientID; + int? invoiceLineItemNo; + int? invoiceNo; + int? doctorID; + int? clinicID; + String? orderDate; + String? reportData; + String? imageURL; + String? procedureID; + int? appointmentNo; + dynamic dIAPacsURL; + bool? isRead; + String? readOn; + dynamic admissionNo; + bool? isInOutPatient; + int? actualDoctorRate; + dynamic admissionDate; + dynamic admissionNumber; + dynamic appointmentDate; + dynamic appointmentNumber; + dynamic appointmentTime; + String? clinicDescription; + String? dIAPACSURL; + dynamic decimalDoctorRate; + String? description; + String? doctorImageURL; + String? doctorName; + num? doctorRate; + num? doctorStarsRate; + String? doctorTitle; + String? examId; + int? gender; + dynamic genderDescription; + int? invoiceNoVP; + String? invoiceType; + bool? isActiveDoctorProfile; + bool? isExecludeDoctor; + String? isInOutPatientDescription; + String? isInOutPatientDescriptionN; + bool? isLiveCareAppointment; + dynamic nationalityFlagURL; + int? noOfPatientsRate; + int? orderNo; + dynamic procedureName; + String? projectName; + String? qR; + String? reportDataHTML; + String? reportDataTextString; + dynamic strAppointmentDate; + dynamic strOrderDate; + bool? isCVI; + bool? isRadMedicalReport; + dynamic vida3Id; + + PatientRadiologyResponseModel( + {this.setupID, + this.projectID, + this.patientID, + this.invoiceLineItemNo, + this.invoiceNo, + this.doctorID, + this.clinicID, + this.orderDate, + this.reportData, + this.imageURL, + this.procedureID, + this.appointmentNo, + this.dIAPacsURL, + this.isRead, + this.readOn, + this.admissionNo, + this.isInOutPatient, + this.actualDoctorRate, + this.admissionDate, + this.admissionNumber, + this.appointmentDate, + this.appointmentNumber, + this.appointmentTime, + this.clinicDescription, + this.dIAPACSURL, + this.decimalDoctorRate, + this.description, + this.doctorImageURL, + this.doctorName, + this.doctorRate, + this.doctorStarsRate, + this.doctorTitle, + this.examId, + this.gender, + this.genderDescription, + this.invoiceNoVP, + this.invoiceType, + this.isActiveDoctorProfile, + this.isExecludeDoctor, + this.isInOutPatientDescription, + this.isInOutPatientDescriptionN, + this.isLiveCareAppointment, + this.nationalityFlagURL, + this.noOfPatientsRate, + this.orderNo, + this.procedureName, + this.projectName, + this.qR, + this.reportDataHTML, + this.reportDataTextString, + this.strAppointmentDate, + this.strOrderDate, + this.isCVI, + this.isRadMedicalReport, + this.vida3Id}); + + PatientRadiologyResponseModel.fromJson(Map json) { + setupID = json['SetupID']; + projectID = json['ProjectID']; + patientID = json['PatientID']; + invoiceLineItemNo = json['InvoiceLineItemNo']; + invoiceNo = json['InvoiceNo']; + doctorID = json['DoctorID']; + clinicID = json['ClinicID']; + orderDate = json['OrderDate']; + reportData = json['ReportData']; + imageURL = json['ImageURL']; + procedureID = json['ProcedureID']; + appointmentNo = json['AppointmentNo']; + dIAPacsURL = json['DIAPacsURL']; + isRead = json['IsRead']; + readOn = json['ReadOn']; + admissionNo = json['AdmissionNo']; + isInOutPatient = json['IsInOutPatient']; + actualDoctorRate = json['ActualDoctorRate']; + admissionDate = json['AdmissionDate']; + admissionNumber = json['AdmissionNumber']; + appointmentDate = json['AppointmentDate']; + appointmentNumber = json['AppointmentNumber']; + appointmentTime = json['AppointmentTime']; + clinicDescription = json['ClinicDescription']; + dIAPACSURL = json['DIA_PACS_URL']; + decimalDoctorRate = json['DecimalDoctorRate']; + description = json['Description']; + doctorImageURL = json['DoctorImageURL']; + doctorName = json['DoctorName']; + doctorRate = json['DoctorRate']; + doctorStarsRate = json['DoctorStarsRate']; + doctorTitle = json['DoctorTitle']; + examId = json['Exam_id']; + gender = json['Gender']; + genderDescription = json['GenderDescription']; + invoiceNoVP = json['InvoiceNo_VP']; + invoiceType = json['InvoiceType']; + isActiveDoctorProfile = json['IsActiveDoctorProfile']; + isExecludeDoctor = json['IsExecludeDoctor']; + isInOutPatientDescription = json['IsInOutPatientDescription']; + isInOutPatientDescriptionN = json['IsInOutPatientDescriptionN']; + isLiveCareAppointment = json['IsLiveCareAppointment']; + nationalityFlagURL = json['NationalityFlagURL']; + noOfPatientsRate = json['NoOfPatientsRate']; + orderNo = json['OrderNo']; + procedureName = json['ProcedureName']; + projectName = json['ProjectName']; + qR = json['QR']; + reportDataHTML = json['ReportDataHTML']; + reportDataTextString = json['ReportDataTextString']; + strAppointmentDate = json['StrAppointmentDate']; + strOrderDate = json['StrOrderDate']; + isCVI = json['isCVI']; + isRadMedicalReport = json['isRadMedicalReport']; + vida3Id = json['vida3Id']; + } + + Map toJson() { + final Map data = new Map(); + data['SetupID'] = this.setupID; + data['ProjectID'] = this.projectID; + data['PatientID'] = this.patientID; + data['InvoiceLineItemNo'] = this.invoiceLineItemNo; + data['InvoiceNo'] = this.invoiceNo; + data['DoctorID'] = this.doctorID; + data['ClinicID'] = this.clinicID; + data['OrderDate'] = this.orderDate; + data['ReportData'] = this.reportData; + data['ImageURL'] = this.imageURL; + data['ProcedureID'] = this.procedureID; + data['AppointmentNo'] = this.appointmentNo; + data['DIAPacsURL'] = this.dIAPacsURL; + data['IsRead'] = this.isRead; + data['ReadOn'] = this.readOn; + data['AdmissionNo'] = this.admissionNo; + data['IsInOutPatient'] = this.isInOutPatient; + data['ActualDoctorRate'] = this.actualDoctorRate; + data['AdmissionDate'] = this.admissionDate; + data['AdmissionNumber'] = this.admissionNumber; + data['AppointmentDate'] = this.appointmentDate; + data['AppointmentNumber'] = this.appointmentNumber; + data['AppointmentTime'] = this.appointmentTime; + data['ClinicDescription'] = this.clinicDescription; + data['DIA_PACS_URL'] = this.dIAPACSURL; + data['DecimalDoctorRate'] = this.decimalDoctorRate; + data['Description'] = this.description; + data['DoctorImageURL'] = this.doctorImageURL; + data['DoctorName'] = this.doctorName; + data['DoctorRate'] = this.doctorRate; + data['DoctorStarsRate'] = this.doctorStarsRate; + data['DoctorTitle'] = this.doctorTitle; + data['Exam_id'] = this.examId; + data['Gender'] = this.gender; + data['GenderDescription'] = this.genderDescription; + data['InvoiceNo_VP'] = this.invoiceNoVP; + data['InvoiceType'] = this.invoiceType; + data['IsActiveDoctorProfile'] = this.isActiveDoctorProfile; + data['IsExecludeDoctor'] = this.isExecludeDoctor; + data['IsInOutPatientDescription'] = this.isInOutPatientDescription; + data['IsInOutPatientDescriptionN'] = this.isInOutPatientDescriptionN; + data['IsLiveCareAppointment'] = this.isLiveCareAppointment; + data['NationalityFlagURL'] = this.nationalityFlagURL; + data['NoOfPatientsRate'] = this.noOfPatientsRate; + data['OrderNo'] = this.orderNo; + data['ProcedureName'] = this.procedureName; + data['ProjectName'] = this.projectName; + data['QR'] = this.qR; + data['ReportDataHTML'] = this.reportDataHTML; + data['ReportDataTextString'] = this.reportDataTextString; + data['StrAppointmentDate'] = this.strAppointmentDate; + data['StrOrderDate'] = this.strOrderDate; + data['isCVI'] = this.isCVI; + data['isRadMedicalReport'] = this.isRadMedicalReport; + data['vida3Id'] = this.vida3Id; + return data; + } +} diff --git a/lib/features/radiology/radiology_repo.dart b/lib/features/radiology/radiology_repo.dart new file mode 100644 index 0000000..ea916e6 --- /dev/null +++ b/lib/features/radiology/radiology_repo.dart @@ -0,0 +1,82 @@ +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/exceptions/api_failure.dart'; +import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; +import 'package:dartz/dartz.dart'; +import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_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}); +} + +class RadiologyRepoImp implements RadiologyRepo { + final ApiClient apiClient; + final LoggerService loggerService; + + RadiologyRepoImp({required this.loggerService, required this.apiClient}); + + @override + Future>>> getPatientRadiologyOrders({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!?" + }; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + GET_PATIENT_ORDERS, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus}) { + final radOrders; + try { + if (response['FinalRadiologyList'] != null && response['FinalRadiologyList'].length != 0) { + final list = response['FinalRadiologyList']; + radOrders = list.map((item) => PatientRadiologyResponseModel.fromJson(item as Map)).toList().cast(); + } else { + final list = response['FinalRadiologyListAPI']; + radOrders = list.map((item) => PatientRadiologyResponseModel.fromJson(item as Map)).toList().cast(); + } + // final list = response['ListPLO']; + // if (list == null || list.isEmpty) { + // throw Exception("lab list is empty"); + // } + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: radOrders, + ); + } 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 new file mode 100644 index 0000000..22857d0 --- /dev/null +++ b/lib/features/radiology/radiology_view_model.dart @@ -0,0 +1,45 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/features/lab/lab_repo.dart'; +import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart'; +import 'package:hmg_patient_app_new/features/radiology/radiology_repo.dart'; +import 'package:hmg_patient_app_new/services/error_handler_service.dart'; + +import 'models/resp_models/patient_radiology_response_model.dart'; + +class RadiologyViewModel extends ChangeNotifier { + bool isRadiologyOrdersLoading = false; + + RadiologyRepo radiologyRepo; + ErrorHandlerService errorHandlerService; + + List patientRadiologyOrders = []; + + RadiologyViewModel({required this.radiologyRepo, required this.errorHandlerService}); + + initRadiologyProvider() { + patientRadiologyOrders.clear(); + isRadiologyOrdersLoading = true; + getPatientRadiologyOrders(); + notifyListeners(); + } + + Future getPatientRadiologyOrders({Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await radiologyRepo.getPatientRadiologyOrders(patientId: "1231755"); + + 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) { + patientRadiologyOrders = apiResponse.data!; + isRadiologyOrdersLoading = false; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } +} diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index a1082b3..3defa60 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -779,5 +779,6 @@ abstract class LocaleKeys { static const myFiles = 'myFiles'; static const resultsPending = 'resultsPending'; static const resultsAvailable = 'resultsAvailable'; + static const viewReport = 'viewReport'; } diff --git a/lib/main.dart b/lib/main.dart index 3f09649..f9ff19e 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -8,6 +8,7 @@ import 'package:flutter/services.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; +import 'package:hmg_patient_app_new/features/radiology/radiology_view_model.dart'; import 'package:hmg_patient_app_new/routes/app_routes.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; @@ -57,6 +58,12 @@ void main() async { errorHandlerService: getIt(), ), ), + ChangeNotifierProvider( + create: (_) => RadiologyViewModel( + radiologyRepo: getIt(), + errorHandlerService: getIt(), + ), + ), ChangeNotifierProvider( create: (_) => AuthenticationViewModel( authenticationRepo: getIt(), diff --git a/lib/presentation/home/widgets/small_service_card.dart b/lib/presentation/home/widgets/small_service_card.dart index 0983a06..4cf90f5 100644 --- a/lib/presentation/home/widgets/small_service_card.dart +++ b/lib/presentation/home/widgets/small_service_card.dart @@ -7,6 +7,7 @@ import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; import '../../../core/utils/utils.dart'; import '../../../theme/colors.dart'; +import '../../radiology/radiology_orders_page.dart' show RadiologyOrdersPage; class SmallServiceCard extends StatelessWidget { final String serviceName; @@ -62,6 +63,11 @@ class SmallServiceCard extends StatelessWidget { ); break; case "radiology_results": + Navigator.of(context).push( + FadePage( + page: RadiologyOrdersPage(), + ), + ); break; case "prescriptions": break; diff --git a/lib/presentation/lab/lab_orders_page.dart b/lib/presentation/lab/lab_orders_page.dart index a60f3cf..03c9eb0 100644 --- a/lib/presentation/lab/lab_orders_page.dart +++ b/lib/presentation/lab/lab_orders_page.dart @@ -28,14 +28,6 @@ class _LabOrdersPageState extends State { int? expandedIndex; - // Sample data for demonstration - final List labOrders = [ - 'Mohammad Al Harbi', - 'Mohammad Al Harbi', - 'Mohammad Al Harbi', - 'Mohammad Al Harbi', - ]; - @override void initState() { scheduleMicrotask(() { @@ -50,7 +42,7 @@ class _LabOrdersPageState extends State { return Scaffold( backgroundColor: AppColors.bgScaffoldColor, appBar: AppBar( - title: const Text('Lab Results'), + title: LocaleKeys.labResults.tr(context: context).toText18(), backgroundColor: AppColors.bgScaffoldColor, ), body: Padding( @@ -191,7 +183,7 @@ class _LabOrdersPageState extends State { icon: AppAssets.view_report_icon, iconColor: AppColors.primaryRedColor, iconSize: 16.h, - text: "View Report", + text: LocaleKeys.viewReport.tr(context: context), onPressed: () {}, backgroundColor: AppColors.secondaryLightRedColor, borderColor: AppColors.secondaryLightRedColor, diff --git a/lib/presentation/radiology/radiology_orders_page.dart b/lib/presentation/radiology/radiology_orders_page.dart new file mode 100644 index 0000000..d1b3830 --- /dev/null +++ b/lib/presentation/radiology/radiology_orders_page.dart @@ -0,0 +1,251 @@ +import 'dart:async'; + +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/shimmer/movies_shimmer_widget.dart'; +import 'package:provider/provider.dart'; + +import '../../features/radiology/radiology_view_model.dart'; + +class RadiologyOrdersPage extends StatefulWidget { + const RadiologyOrdersPage({super.key}); + + @override + State createState() => _RadiologyOrdersPageState(); +} + +class _RadiologyOrdersPageState extends State { + late RadiologyViewModel radiologyViewModel; + + int? expandedIndex; + + @override + void initState() { + scheduleMicrotask(() { + radiologyViewModel.initRadiologyProvider(); + }); + super.initState(); + } + + @override + Widget build(BuildContext context) { + radiologyViewModel = Provider.of(context); + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + appBar: AppBar( + title: LocaleKeys.radiology.tr(context: context).toText18(), + backgroundColor: AppColors.bgScaffoldColor, + ), + body: Padding( + padding: EdgeInsets.all(24.h), + child: SingleChildScrollView( + child: Consumer( + builder: (context, model, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + LocaleKeys.radiology.tr(context: context).toText24(isBold: true), + Utils.buildSvgWithAssets(icon: AppAssets.search_icon), + ], + ), + SizedBox(height: 16.h), + // Build Tab Bar + SizedBox(height: 16.h), + // Expandable list + ListView.builder( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: model.isRadiologyOrdersLoading ? 5 : model.patientRadiologyOrders.length, + itemBuilder: (context, index) { + final isExpanded = expandedIndex == index; + return model.isRadiologyOrdersLoading + ? const MoviesShimmerWidget() + : AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: AnimatedContainer( + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + margin: EdgeInsets.symmetric(vertical: 8.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: true), + child: InkWell( + onTap: () { + setState(() { + expandedIndex = isExpanded ? null : index; + }); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + CustomButton( + text: LocaleKeys.resultsAvailable.tr(context: context), + onPressed: () {}, + backgroundColor: AppColors.successColor.withOpacity(0.15), + borderColor: AppColors.successColor.withOpacity(0.01), + textColor: AppColors.successColor, + fontSize: 10, + fontWeight: FontWeight.w500, + borderRadius: 8, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 30.h, + ), + Icon(isExpanded ? Icons.expand_less : Icons.expand_more), + ], + ), + SizedBox(height: 8.h), + Row( + children: [ + Image.network( + model.patientRadiologyOrders[index].doctorImageURL!, + width: 24.h, + height: 24.h, + fit: BoxFit.fill, + ).circle(100), + SizedBox(width: 4.h), + model.patientRadiologyOrders[index].doctorName!.toText16(isBold: true) + ], + ), + SizedBox(height: 8.h), + Row( + children: [ + CustomButton( + text: DateUtil.formatDateToDate(DateUtil.convertStringToDate(model.patientRadiologyOrders[index].orderDate), false), + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 12, + fontWeight: FontWeight.w500, + borderRadius: 8, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 24.h, + ), + SizedBox(width: 8.h), + CustomButton( + text: model.patientRadiologyOrders[index].clinicDescription!, + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 12, + fontWeight: FontWeight.w500, + borderRadius: 8, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 24.h, + ), + ], + ), + ], + ), + ), + AnimatedCrossFade( + firstChild: SizedBox.shrink(), + secondChild: Padding( + padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 8.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.only(bottom: 8.h), + child: '● ${model.patientRadiologyOrders[index].description}'.toText14(weight: FontWeight.w500), + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox(), + CustomButton( + icon: AppAssets.view_report_icon, + iconColor: AppColors.primaryRedColor, + iconSize: 16.h, + text: LocaleKeys.viewReport.tr(context: context), + onPressed: () {}, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: 14, + fontWeight: FontWeight.bold, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + ), + ], + ), + ], + ), + ), + crossFadeState: isExpanded ? CrossFadeState.showSecond : CrossFadeState.showFirst, + duration: Duration(milliseconds: 300), + ), + ], + ), + ), + ), + ), + ), + ); + }, + ), + ], + ); + }, + ), + ), + ), + ); + } + + Color getLabOrderStatusColor(num status) { + switch (status) { + case 44: + return AppColors.warningColorYellow; + case 45: + return AppColors.warningColorYellow; + case 16: + return AppColors.successColor; + case 17: + return AppColors.successColor; + default: + return AppColors.greyColor; + } + } + + String getLabOrderStatusText(num status) { + switch (status) { + case 44: + return LocaleKeys.resultsPending.tr(context: context); + case 45: + return LocaleKeys.resultsPending.tr(context: context); + case 16: + return LocaleKeys.resultsAvailable.tr(context: context); + case 17: + return LocaleKeys.resultsAvailable.tr(context: context); + default: + return ""; + } + } +} From 8bc49929d47a791781866de6136dc86e67027a0c Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Wed, 3 Sep 2025 14:30:03 +0300 Subject: [PATCH 2/6] prescription orders list page implemented --- assets/images/svg/forward_arrow_icon.svg | 3 + .../images/svg/prescription_refill_icon.svg | 5 + assets/langs/ar-SA.json | 3 +- assets/langs/en-US.json | 5 +- lib/core/app_assets.dart | 3 +- lib/core/dependencies.dart | 10 + .../patient_prescriptions_response_model.dart | 174 +++++++++++ .../prescriptions/prescriptions_repo.dart | 75 +++++ .../prescriptions_view_model.dart | 82 +++++ lib/features/radiology/radiology_repo.dart | 5 - .../radiology/radiology_view_model.dart | 2 - lib/generated/locale_keys.g.dart | 1 + lib/main.dart | 7 + .../home/widgets/small_service_card.dart | 6 + lib/presentation/lab/lab_orders_page.dart | 91 +++--- .../prescriptions_list_page.dart | 293 ++++++++++++++++++ lib/theme/colors.dart | 12 +- pubspec.yaml | 8 +- 18 files changed, 725 insertions(+), 60 deletions(-) create mode 100644 assets/images/svg/forward_arrow_icon.svg create mode 100644 assets/images/svg/prescription_refill_icon.svg create mode 100644 lib/features/prescriptions/models/resp_models/patient_prescriptions_response_model.dart create mode 100644 lib/features/prescriptions/prescriptions_repo.dart create mode 100644 lib/features/prescriptions/prescriptions_view_model.dart create mode 100644 lib/presentation/prescriptions/prescriptions_list_page.dart diff --git a/assets/images/svg/forward_arrow_icon.svg b/assets/images/svg/forward_arrow_icon.svg new file mode 100644 index 0000000..d8a3d51 --- /dev/null +++ b/assets/images/svg/forward_arrow_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/assets/images/svg/prescription_refill_icon.svg b/assets/images/svg/prescription_refill_icon.svg new file mode 100644 index 0000000..7a9706b --- /dev/null +++ b/assets/images/svg/prescription_refill_icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index 6355b66..718bf83 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -781,5 +781,6 @@ "myFiles" : "ملفاتي", "resultsPending": "النتائج معلقة", "resultsAvailable": "النتائج متاحة", - "viewReport": "عرض التقرير" + "viewReport": "عرض التقرير", + "prescriptionDeliveryError": "هذه العيادة لا تدعم إعادة التعبئة والتسليم." } \ No newline at end of file diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index c18eb97..6743993 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -665,7 +665,7 @@ "healthWeatherIndicators": "Health Weather Indicators", "healthTipsBasedOnCurrentWeather": "Health Tips Based On Current Weather", "moreDetails": "More details", - "resendOrder": "Refill & Delivery", + "resendOrder": "Refill and Delivery", "ports": "Ports", "way": "Way", "dailyDoses": "Daily Doses", @@ -777,5 +777,6 @@ "myFiles": "My Files", "resultsPending": "Results Pending", "resultsAvailable": "Results Available", - "viewReport": "View Report" + "viewReport": "View Report", + "prescriptionDeliveryError": "This clinic doesn't support refill" } \ No newline at end of file diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index 53adac7..a43406e 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -62,6 +62,8 @@ class AppAssets { static const String eye_result_icon = '$svgBasePath/eye_results_icon.svg'; static const String search_icon = '$svgBasePath/search_icon.svg'; static const String view_report_icon = '$svgBasePath/view_report_icon.svg'; + static const String forward_arrow_icon = '$svgBasePath/forward_arrow_icon.svg'; + static const String prescription_refill_icon = '$svgBasePath/prescription_refill_icon.svg'; //bottom navigation// @@ -71,7 +73,6 @@ class AppAssets { static const String toDoBottom = '$svgBasePath/todo_bottom.svg'; static const String servicesBottom = '$svgBasePath/services_bottom.svg'; - // PNGS // static const String hmg_logo = '$pngBasePath/hmg_logo.png'; static const String livecare_service = '$pngBasePath/livecare_service.png'; diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index a9fc9d6..65fd2c6 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -9,6 +9,8 @@ import 'package:hmg_patient_app_new/features/common/common_repo.dart'; import 'package:hmg_patient_app_new/features/lab/lab_repo.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_repo.dart'; +import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_repo.dart'; +import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart'; import 'package:hmg_patient_app_new/features/radiology/radiology_repo.dart'; import 'package:hmg_patient_app_new/features/radiology/radiology_view_model.dart'; import 'package:hmg_patient_app_new/services/analytics/analytics_service.dart'; @@ -62,6 +64,7 @@ class AppDependencies { getIt.registerLazySingleton(() => MyAppointmentsRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => LabRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => RadiologyRepoImp(loggerService: getIt(), apiClient: getIt())); + getIt.registerLazySingleton(() => PrescriptionsRepoImp(loggerService: getIt(), apiClient: getIt())); // ViewModels // Global/shared VMs → LazySingleton @@ -80,6 +83,13 @@ class AppDependencies { ), ); + getIt.registerLazySingleton( + () => PrescriptionsViewModel( + prescriptionsRepo: getIt(), + errorHandlerService: getIt(), + ), + ); + getIt.registerLazySingleton( () => AuthenticationViewModel( authenticationRepo: getIt(), diff --git a/lib/features/prescriptions/models/resp_models/patient_prescriptions_response_model.dart b/lib/features/prescriptions/models/resp_models/patient_prescriptions_response_model.dart new file mode 100644 index 0000000..7b1d879 --- /dev/null +++ b/lib/features/prescriptions/models/resp_models/patient_prescriptions_response_model.dart @@ -0,0 +1,174 @@ +class PatientPrescriptionsResponseModel { + String? setupID; + int? projectID; + int? patientID; + int? appointmentNo; + String? appointmentDate; + String? doctorName; + String? clinicDescription; + String? name; + int? episodeID; + num? actualDoctorRate; + int? admission; + int? clinicID; + String? companyName; + num? decimalDoctorRate; + String? despensedStatus; + String? dischargeDate; + int? dischargeNo; + int? doctorID; + String? doctorImageURL; + num? doctorRate; + num? doctorStarsRate; + String? doctorTitle; + int? gender; + String? genderDescription; + bool? isActiveDoctorProfile; + bool? isDoctorAllowVedioCall; + bool? isExecludeDoctor; + bool? isHomeMedicineDeliverySupported; + bool? isInOutPatient; + String? isInOutPatientDescription; + String? isInOutPatientDescriptionN; + bool? isInsurancePatient; + bool? isLiveCareAppointment; + String? nationalityFlagURL; + int? noOfPatientsRate; + String? qR; + + // List? speciality; + String? strAppointmentDate; + + PatientPrescriptionsResponseModel( + {this.setupID, + this.projectID, + this.patientID, + this.appointmentNo, + this.appointmentDate, + this.doctorName, + this.clinicDescription, + this.name, + this.episodeID, + this.actualDoctorRate, + this.admission, + this.clinicID, + this.companyName, + this.decimalDoctorRate, + this.despensedStatus, + this.dischargeDate, + this.dischargeNo, + this.doctorID, + this.doctorImageURL, + this.doctorRate, + this.doctorStarsRate, + this.doctorTitle, + this.gender, + this.genderDescription, + this.isActiveDoctorProfile, + this.isDoctorAllowVedioCall, + this.isExecludeDoctor, + this.isHomeMedicineDeliverySupported, + this.isInOutPatient, + this.isInOutPatientDescription, + this.isInOutPatientDescriptionN, + this.isInsurancePatient, + this.isLiveCareAppointment, + this.nationalityFlagURL, + this.noOfPatientsRate, + this.qR, + // this.speciality, + this.strAppointmentDate}); + + PatientPrescriptionsResponseModel.fromJson(Map json) { + setupID = json['SetupID']; + projectID = json['ProjectID']; + patientID = json['PatientID']; + appointmentNo = json['AppointmentNo']; + appointmentDate = json['AppointmentDate']; + doctorName = json['DoctorName']; + clinicDescription = json['ClinicDescription']; + name = json['Name']; + episodeID = json['EpisodeID']; + actualDoctorRate = json['ActualDoctorRate']; + admission = json['Admission']; + clinicID = json['ClinicID']; + companyName = json['CompanyName']; + decimalDoctorRate = json['DecimalDoctorRate']; + despensedStatus = json['Despensed_Status']; + dischargeDate = json['DischargeDate']; + dischargeNo = json['DischargeNo']; + doctorID = json['DoctorID']; + doctorImageURL = json['DoctorImageURL']; + doctorRate = json['DoctorRate']; + doctorStarsRate = json['DoctorStarsRate']; + doctorTitle = json['DoctorTitle']; + gender = json['Gender']; + genderDescription = json['GenderDescription']; + isActiveDoctorProfile = json['IsActiveDoctorProfile']; + isDoctorAllowVedioCall = json['IsDoctorAllowVedioCall']; + isExecludeDoctor = json['IsExecludeDoctor']; + isHomeMedicineDeliverySupported = json['IsHomeMedicineDeliverySupported']; + isInOutPatient = json['IsInOutPatient']; + isInOutPatientDescription = json['IsInOutPatientDescription']; + isInOutPatientDescriptionN = json['IsInOutPatientDescriptionN']; + isInsurancePatient = json['IsInsurancePatient']; + isLiveCareAppointment = json['IsLiveCareAppointment']; + nationalityFlagURL = json['NationalityFlagURL']; + noOfPatientsRate = json['NoOfPatientsRate']; + qR = json['QR']; + // speciality = json['Speciality'].cast(); + strAppointmentDate = json['StrAppointmentDate']; + } + + Map toJson() { + final Map data = new Map(); + data['SetupID'] = this.setupID; + data['ProjectID'] = this.projectID; + data['PatientID'] = this.patientID; + data['AppointmentNo'] = this.appointmentNo; + data['AppointmentDate'] = this.appointmentDate; + data['DoctorName'] = this.doctorName; + data['ClinicDescription'] = this.clinicDescription; + data['Name'] = this.name; + data['EpisodeID'] = this.episodeID; + data['ActualDoctorRate'] = this.actualDoctorRate; + data['Admission'] = this.admission; + data['ClinicID'] = this.clinicID; + data['CompanyName'] = this.companyName; + data['DecimalDoctorRate'] = this.decimalDoctorRate; + data['Despensed_Status'] = this.despensedStatus; + data['DischargeDate'] = this.dischargeDate; + data['DischargeNo'] = this.dischargeNo; + data['DoctorID'] = this.doctorID; + data['DoctorImageURL'] = this.doctorImageURL; + data['DoctorRate'] = this.doctorRate; + data['DoctorStarsRate'] = this.doctorStarsRate; + data['DoctorTitle'] = this.doctorTitle; + data['Gender'] = this.gender; + data['GenderDescription'] = this.genderDescription; + data['IsActiveDoctorProfile'] = this.isActiveDoctorProfile; + data['IsDoctorAllowVedioCall'] = this.isDoctorAllowVedioCall; + data['IsExecludeDoctor'] = this.isExecludeDoctor; + data['IsHomeMedicineDeliverySupported'] = this.isHomeMedicineDeliverySupported; + data['IsInOutPatient'] = this.isInOutPatient; + data['IsInOutPatientDescription'] = this.isInOutPatientDescription; + data['IsInOutPatientDescriptionN'] = this.isInOutPatientDescriptionN; + data['IsInsurancePatient'] = this.isInsurancePatient; + data['IsLiveCareAppointment'] = this.isLiveCareAppointment; + data['NationalityFlagURL'] = this.nationalityFlagURL; + data['NoOfPatientsRate'] = this.noOfPatientsRate; + data['QR'] = this.qR; + // data['Speciality'] = this.speciality; + data['StrAppointmentDate'] = this.strAppointmentDate; + return data; + } +} + +class PrescriptionsList { + String? filterName = ""; + List? prescriptionsList = []; + + PrescriptionsList({this.filterName, PatientPrescriptionsResponseModel? prescriptions}) { + prescriptionsList!.add(prescriptions!); + } +} diff --git a/lib/features/prescriptions/prescriptions_repo.dart b/lib/features/prescriptions/prescriptions_repo.dart new file mode 100644 index 0000000..53cf827 --- /dev/null +++ b/lib/features/prescriptions/prescriptions_repo.dart @@ -0,0 +1,75 @@ +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/exceptions/api_failure.dart'; +import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; +import 'package:dartz/dartz.dart'; +import 'package:hmg_patient_app_new/features/prescriptions/models/resp_models/patient_prescriptions_response_model.dart'; +import 'package:hmg_patient_app_new/services/logger_service.dart'; + +abstract class PrescriptionsRepo { + Future>>> getPatientPrescriptionOrders({required String patientId}); +} + +class PrescriptionsRepoImp implements PrescriptionsRepo { + final ApiClient apiClient; + final LoggerService loggerService; + + PrescriptionsRepoImp({required this.loggerService, required this.apiClient}); + + @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!?" + }; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + PRESCRIPTIONS, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus}) { + try { + final list = response['PatientPrescriptionList']; + if (list == null || list.isEmpty) { + throw Exception("lab list is empty"); + } + + final prescriptionOrders = list.map((item) => PatientPrescriptionsResponseModel.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: prescriptionOrders, + ); + } 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/prescriptions/prescriptions_view_model.dart b/lib/features/prescriptions/prescriptions_view_model.dart new file mode 100644 index 0000000..031b26e --- /dev/null +++ b/lib/features/prescriptions/prescriptions_view_model.dart @@ -0,0 +1,82 @@ +import 'package:flutter/material.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_repo.dart'; +import 'package:hmg_patient_app_new/services/error_handler_service.dart'; + +class PrescriptionsViewModel extends ChangeNotifier { + bool isPrescriptionsOrdersLoading = false; + + PrescriptionsRepo prescriptionsRepo; + ErrorHandlerService errorHandlerService; + + List patientPrescriptionOrders = []; + + List patientPrescriptionOrdersByClinic = []; + List patientPrescriptionOrdersByHospital = []; + + List patientPrescriptionOrdersViewList = []; + + bool isSortByClinic = true; + + PrescriptionsViewModel({required this.prescriptionsRepo, required this.errorHandlerService}); + + initPrescriptionsViewModel() { + patientPrescriptionOrders.clear(); + patientPrescriptionOrdersByClinic.clear(); + patientPrescriptionOrdersByHospital.clear(); + patientPrescriptionOrdersViewList.clear(); + isPrescriptionsOrdersLoading = true; + isSortByClinic = true; + getPatientPrescriptionOrders(); + notifyListeners(); + } + + setIsSortByClinic(bool value) { + isSortByClinic = value; + if (isSortByClinic) { + patientPrescriptionOrdersViewList = patientPrescriptionOrdersByClinic; + } else { + patientPrescriptionOrdersViewList = patientPrescriptionOrdersByHospital; + } + notifyListeners(); + } + + Future getPatientPrescriptionOrders({Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await prescriptionsRepo.getPatientPrescriptionOrders(patientId: "1231755"); + + 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) { + patientPrescriptionOrders = apiResponse.data!; + isPrescriptionsOrdersLoading = false; + + for (var element in patientPrescriptionOrders) { + List prescriptionsByClinic = patientPrescriptionOrdersByClinic.where((elementClinic) => elementClinic.filterName == element.clinicDescription).toList(); + + if (prescriptionsByClinic.isNotEmpty) { + patientPrescriptionOrdersByClinic[patientPrescriptionOrdersByClinic.indexOf(prescriptionsByClinic[0])].prescriptionsList!.add(element); + } else { + patientPrescriptionOrdersByClinic.add(PrescriptionsList(filterName: element.clinicDescription, prescriptions: element)); + } + + List prescriptionsByHospital = patientPrescriptionOrdersByHospital.where((elementClinic) => elementClinic.filterName == element.name).toList(); + + if (prescriptionsByHospital.isNotEmpty) { + patientPrescriptionOrdersByHospital[patientPrescriptionOrdersByHospital.indexOf(prescriptionsByHospital[0])].prescriptionsList!.add(element); + } else { + patientPrescriptionOrdersByHospital.add(PrescriptionsList(filterName: element.name, prescriptions: element)); + } + } + patientPrescriptionOrdersViewList = patientPrescriptionOrdersByClinic; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } +} diff --git a/lib/features/radiology/radiology_repo.dart b/lib/features/radiology/radiology_repo.dart index ea916e6..d3e4f3d 100644 --- a/lib/features/radiology/radiology_repo.dart +++ b/lib/features/radiology/radiology_repo.dart @@ -3,7 +3,6 @@ import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; import 'package:dartz/dartz.dart'; -import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_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'; @@ -56,10 +55,6 @@ class RadiologyRepoImp implements RadiologyRepo { final list = response['FinalRadiologyListAPI']; radOrders = list.map((item) => PatientRadiologyResponseModel.fromJson(item as Map)).toList().cast(); } - // final list = response['ListPLO']; - // if (list == null || list.isEmpty) { - // throw Exception("lab list is empty"); - // } apiResponse = GenericApiModel>( messageStatus: messageStatus, diff --git a/lib/features/radiology/radiology_view_model.dart b/lib/features/radiology/radiology_view_model.dart index 22857d0..1bdba04 100644 --- a/lib/features/radiology/radiology_view_model.dart +++ b/lib/features/radiology/radiology_view_model.dart @@ -1,6 +1,4 @@ import 'package:flutter/material.dart'; -import 'package:hmg_patient_app_new/features/lab/lab_repo.dart'; -import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart'; import 'package:hmg_patient_app_new/features/radiology/radiology_repo.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 3defa60..54a1136 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -780,5 +780,6 @@ abstract class LocaleKeys { static const resultsPending = 'resultsPending'; static const resultsAvailable = 'resultsAvailable'; static const viewReport = 'viewReport'; + static const prescriptionDeliveryError = 'prescriptionDeliveryError'; } diff --git a/lib/main.dart b/lib/main.dart index f9ff19e..035c6bf 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -8,6 +8,7 @@ import 'package:flutter/services.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; +import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart'; import 'package:hmg_patient_app_new/features/radiology/radiology_view_model.dart'; import 'package:hmg_patient_app_new/routes/app_routes.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; @@ -64,6 +65,12 @@ void main() async { errorHandlerService: getIt(), ), ), + ChangeNotifierProvider( + create: (_) => PrescriptionsViewModel( + prescriptionsRepo: getIt(), + errorHandlerService: getIt(), + ), + ), ChangeNotifierProvider( create: (_) => AuthenticationViewModel( authenticationRepo: getIt(), diff --git a/lib/presentation/home/widgets/small_service_card.dart b/lib/presentation/home/widgets/small_service_card.dart index 4cf90f5..297ed2b 100644 --- a/lib/presentation/home/widgets/small_service_card.dart +++ b/lib/presentation/home/widgets/small_service_card.dart @@ -3,6 +3,7 @@ import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_orders_page.dart'; +import 'package:hmg_patient_app_new/presentation/prescriptions/prescriptions_list_page.dart'; import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; import '../../../core/utils/utils.dart'; @@ -70,6 +71,11 @@ class SmallServiceCard extends StatelessWidget { ); break; case "prescriptions": + Navigator.of(context).push( + FadePage( + page: PrescriptionsListPage(), + ), + ); break; case "insurance_update": break; diff --git a/lib/presentation/lab/lab_orders_page.dart b/lib/presentation/lab/lab_orders_page.dart index 03c9eb0..8a0ce26 100644 --- a/lib/presentation/lab/lab_orders_page.dart +++ b/lib/presentation/lab/lab_orders_page.dart @@ -161,46 +161,59 @@ class _LabOrdersPageState extends State { ], ), ), - AnimatedCrossFade( - firstChild: SizedBox.shrink(), - secondChild: Padding( - padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 8.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ...model.patientLabOrders[index].testDetails!.map((detail) { - return Padding( - padding: EdgeInsets.only(bottom: 8.h), - child: '● ${detail.description}'.toText14(weight: FontWeight.w500), - ); - }).toList(), - SizedBox(height: 16.h), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - SizedBox(), - CustomButton( - icon: AppAssets.view_report_icon, - iconColor: AppColors.primaryRedColor, - iconSize: 16.h, - text: LocaleKeys.viewReport.tr(context: context), - onPressed: () {}, - backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, - textColor: AppColors.primaryRedColor, - fontSize: 14, - fontWeight: FontWeight.bold, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, - ), - ], - ), - ], - ), - ), - crossFadeState: isExpanded ? CrossFadeState.showSecond : CrossFadeState.showFirst, + AnimatedSwitcher( duration: Duration(milliseconds: 300), + switchInCurve: Curves.easeIn, + switchOutCurve: Curves.easeOut, + transitionBuilder: (Widget child, Animation animation) { + return FadeTransition( + opacity: animation, + child: SizeTransition( + sizeFactor: animation, + axisAlignment: 0.0, + child: child, + ), + ); + }, + child: isExpanded + ? Container( + key: ValueKey(index), + padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 8.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ...model.patientLabOrders[index].testDetails!.map((detail) { + return Padding( + padding: EdgeInsets.only(bottom: 8.h), + child: '● ${detail.description}'.toText14(weight: FontWeight.w500), + ); + }).toList(), + SizedBox(height: 16.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + SizedBox(), + CustomButton( + icon: AppAssets.view_report_icon, + iconColor: AppColors.primaryRedColor, + iconSize: 16.h, + text: LocaleKeys.viewReport.tr(context: context), + onPressed: () {}, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: 14, + fontWeight: FontWeight.bold, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + ), + ], + ), + ], + ), + ) + : SizedBox.shrink(key: ValueKey(-index)), ), ], ), diff --git a/lib/presentation/prescriptions/prescriptions_list_page.dart b/lib/presentation/prescriptions/prescriptions_list_page.dart new file mode 100644 index 0000000..c678a8e --- /dev/null +++ b/lib/presentation/prescriptions/prescriptions_list_page.dart @@ -0,0 +1,293 @@ +import 'dart:async'; + +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_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/shimmer/movies_shimmer_widget.dart'; +import 'package:provider/provider.dart'; + +class PrescriptionsListPage extends StatefulWidget { + const PrescriptionsListPage({super.key}); + + @override + State createState() => _PrescriptionsListPageState(); +} + +class _PrescriptionsListPageState extends State { + int? expandedIndex; + + late PrescriptionsViewModel prescriptionsViewModel; + + @override + void initState() { + scheduleMicrotask(() { + prescriptionsViewModel.initPrescriptionsViewModel(); + }); + super.initState(); + } + + @override + Widget build(BuildContext context) { + prescriptionsViewModel = Provider.of(context, listen: false); + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + appBar: AppBar( + title: LocaleKeys.prescriptions.tr(context: context).toText18(), + backgroundColor: AppColors.bgScaffoldColor, + ), + body: Padding( + padding: EdgeInsets.all(24.h), + child: SingleChildScrollView( + child: Consumer(builder: (context, model, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.prescriptions.tr(context: context).toText24(isBold: true), + SizedBox(height: 16.h), + // Build Tab Bar + SizedBox(height: 16.h), + // Clinic & Hospital Sort + Row( + children: [ + CustomButton( + text: LocaleKeys.byClinic.tr(context: context), + onPressed: () { + model.setIsSortByClinic(true); + }, + backgroundColor: model.isSortByClinic ? AppColors.bgRedLightColor : AppColors.whiteColor, + borderColor: model.isSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withOpacity(0.2), + textColor: model.isSortByClinic ? AppColors.primaryRedColor : AppColors.blackColor, + fontSize: 12, + fontWeight: FontWeight.w500, + borderRadius: 10, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + ), + SizedBox(width: 8.h), + CustomButton( + text: LocaleKeys.byHospital.tr(context: context), + onPressed: () { + model.setIsSortByClinic(false); + }, + backgroundColor: model.isSortByClinic ? AppColors.whiteColor : AppColors.bgRedLightColor, + borderColor: model.isSortByClinic ? AppColors.textColor.withOpacity(0.2) : AppColors.primaryRedColor, + textColor: model.isSortByClinic ? AppColors.blackColor : AppColors.primaryRedColor, + fontSize: 12, + fontWeight: FontWeight.w500, + borderRadius: 10, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + ), + ], + ), + SizedBox(height: 20.h), + // Expandable list + ListView.builder( + itemCount: model.isPrescriptionsOrdersLoading ? 4 : model.patientPrescriptionOrdersViewList.length, + physics: NeverScrollableScrollPhysics(), + shrinkWrap: true, + padding: const EdgeInsets.only(left: 0, right: 8), + itemBuilder: (context, index) { + final isExpanded = expandedIndex == index; + return model.isPrescriptionsOrdersLoading + ? const MoviesShimmerWidget() + : AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: AnimatedContainer( + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + margin: EdgeInsets.symmetric(vertical: 8.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: true), + child: InkWell( + onTap: () { + setState(() { + expandedIndex = isExpanded ? null : index; + }); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + CustomButton( + text: "${model.patientPrescriptionOrdersViewList[index].prescriptionsList!.length} Prescriptions Available", + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 10, + fontWeight: FontWeight.w500, + borderRadius: 8, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 30.h, + ), + Icon(isExpanded ? Icons.expand_less : Icons.expand_more), + ], + ), + SizedBox(height: 8.h), + model.patientPrescriptionOrdersViewList[index].filterName!.toText16(isBold: true) + ], + ), + ), + AnimatedSwitcher( + duration: Duration(milliseconds: 500), + switchInCurve: Curves.easeIn, + switchOutCurve: Curves.easeOut, + transitionBuilder: (Widget child, Animation animation) { + return FadeTransition( + opacity: animation, + child: SizeTransition( + sizeFactor: animation, + axisAlignment: 0.0, + child: child, + ), + ); + }, + child: isExpanded + ? Container( + key: ValueKey(index), + padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 8.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ...model.patientPrescriptionOrdersViewList[index].prescriptionsList!.map((prescription) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Image.network( + prescription.doctorImageURL!, + width: 24.h, + height: 24.h, + fit: BoxFit.fill, + ).circle(100), + SizedBox(width: 8.h), + Expanded(child: prescription.doctorName!.toText14(weight: FontWeight.w500)), + ], + ), + SizedBox(height: 8.h), + Row( + children: [ + CustomButton( + text: DateUtil.formatDateToDate(DateUtil.convertStringToDate(prescription.appointmentDate), false), + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 12, + fontWeight: FontWeight.w500, + borderRadius: 8, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 24.h, + ), + SizedBox(width: 8.h), + CustomButton( + text: model.isSortByClinic ? prescription.name! : prescription.clinicDescription!, + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 12, + fontWeight: FontWeight.w500, + borderRadius: 8, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 24.h, + ), + ], + ), + SizedBox(height: 8.h), + Row( + children: [ + Expanded( + flex: 6, + child: CustomButton( + text: prescription.isHomeMedicineDeliverySupported! + ? LocaleKeys.resendOrder.tr(context: context) + : LocaleKeys.prescriptionDeliveryError.tr(context: context), + onPressed: () {}, + backgroundColor: prescription.isHomeMedicineDeliverySupported! ? AppColors.successColor.withOpacity(0.15) : AppColors.greyF7Color, + borderColor: AppColors.successColor.withOpacity(0.01), + textColor: prescription.isHomeMedicineDeliverySupported! ? AppColors.successColor : AppColors.textColor.withOpacity(0.35), + fontSize: prescription.isHomeMedicineDeliverySupported! ? 14 : 12, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + icon: AppAssets.prescription_refill_icon, + iconColor: prescription.isHomeMedicineDeliverySupported! ? AppColors.successColor : AppColors.textColor.withOpacity(0.35), + iconSize: 14.h, + ), + ), + SizedBox(width: 8.h), + Expanded( + flex: 1, + child: Container( + height: 40.h, + width: 40.h, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.textColor, + borderRadius: 10.h, + ), + child: Padding( + padding: EdgeInsets.all(8.h), + child: Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon, + width: 10.h, + height: 10.h, + fit: BoxFit.contain, + ), + ), + ), + ), + ], + ), + SizedBox(height: 12.h), + Divider(color: AppColors.borderOnlyColor.withOpacity(0.05), height: 1.h), + SizedBox(height: 12.h), + ], + ); + }).toList(), + ], + ), + ) + : SizedBox.shrink(), + ), + ], + ), + ), + ), + ), + ), + ); + }, + ), + ], + ); + }), + ), + ), + ); + } +} diff --git a/lib/theme/colors.dart b/lib/theme/colors.dart index 5e9dbdc..d9cf167 100644 --- a/lib/theme/colors.dart +++ b/lib/theme/colors.dart @@ -41,12 +41,12 @@ static const Color infoColor = Color(0xFF0B85F7); static const Color warningColor = Color(0xFFFFCC00); static const Color greyColor = Color(0xFFEFEFF0); -static const Color successLightColor = Color(0xFF18C27326); -static const Color errorLightColor = Color(0xFFED1C2B1A); -static const Color alertLightColor = Color(0xFFD48D0526); -static const Color infoLightColor = Color(0xFF0B85F726); -static const Color warningLightColor = Color(0xFFFFCC0026); -static const Color greyLightColor = Color(0xFFEFEFF026); +static const Color successLightColor = Color(0xFF18C273); +static const Color errorLightColor = Color(0xFFED1C2B); +static const Color alertLightColor = Color(0xFFD48D05); +static const Color infoLightColor = Color(0xFF0B85F7); +static const Color warningLightColor = Color(0xFFFFCC00); +static const Color greyLightColor = Color(0xFFEFEFF0); static const Color bottomNAVBorder = Color(0xFFEEEEEE); } diff --git a/pubspec.yaml b/pubspec.yaml index 4c2b81f..1263e33 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -98,8 +98,8 @@ flutter: # weight: 700 - asset: assets/fonts/poppins/Poppins-SemiBold.ttf weight: 600 -# - asset: assets/fonts/poppins/Poppins-Medium.ttf -# weight: 500 + - asset: assets/fonts/poppins/Poppins-Medium.ttf + weight: 500 - asset: assets/fonts/poppins/Poppins-Regular.ttf weight: 400 # - asset: assets/fonts/poppins/Poppins-Light.ttf @@ -114,8 +114,8 @@ flutter: fonts: - asset: assets/fonts/gess_two/GE_SS_Two_Bold.otf weight: 600 -# - asset: assets/fonts/gess_two/GE_SS_Two_Medium.otf -# weight: 500 + - asset: assets/fonts/gess_two/GE_SS_Two_Medium.otf + weight: 500 - asset: assets/fonts/gess_two/GE_SS_Two_Light.otf weight: 400 From 886825a4b97bf47b004713fa07b072b5220bda23 Mon Sep 17 00:00:00 2001 From: Sultan khan Date: Wed, 3 Sep 2025 16:53:36 +0300 Subject: [PATCH 3/6] bottom navigation updated. --- lib/extensions/string_extensions.dart | 124 +++++++++++++++--- lib/presentation/home/landing_page.dart | 3 +- lib/presentation/home/navigation_screen.dart | 43 ++++++ lib/splashPage.dart | 3 +- .../bottom_navigation/bottom_navigation.dart | 118 +++++++++++------ 5 files changed, 232 insertions(+), 59 deletions(-) create mode 100644 lib/presentation/home/navigation_screen.dart diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart index b9d4ba8..484ebc1 100644 --- a/lib/extensions/string_extensions.dart +++ b/lib/extensions/string_extensions.dart @@ -11,13 +11,16 @@ extension CapExtension on String { String get allInCaps => this.toUpperCase(); - String get capitalizeFirstofEach => this.trim().length > 0 ? this.trim().toLowerCase().split(" ").map((str) => str.inCaps).join(" ") : ""; + String get capitalizeFirstofEach => + this.trim().length > 0 ? this.trim().toLowerCase().split(" ").map((str) => str.inCaps).join(" ") : ""; } extension EmailValidator on String { Widget get toWidget => Text(this); - Widget toText8({Color? color, bool isBold = false, int? maxlines, FontStyle? fontStyle, TextOverflow? textOverflow}) => Text( + Widget toText8( + {Color? color, bool isBold = false, int? maxlines, FontStyle? fontStyle, TextOverflow? textOverflow}) => + Text( this, maxLines: maxlines, overflow: textOverflow, @@ -30,7 +33,14 @@ extension EmailValidator on String { ), ); - Widget toText10({Color? color, bool isBold = false, bool isUnderLine = false, int? maxlines, FontStyle? fontStyle, TextOverflow? textOverflow}) => Text( + Widget toText10( + {Color? color, + bool isBold = false, + bool isUnderLine = false, + int? maxlines, + FontStyle? fontStyle, + TextOverflow? textOverflow}) => + Text( this, maxLines: maxlines, overflow: textOverflow, @@ -44,7 +54,15 @@ extension EmailValidator on String { decorationColor: color ?? AppColors.blackColor), ); - Widget toText11({Color? color, FontWeight? weight, bool isUnderLine = false, bool isCenter = false, bool isBold = false, int maxLine = 0, double letterSpacing = 0.64}) => Text( + Widget toText11( + {Color? color, + FontWeight? weight, + bool isUnderLine = false, + bool isCenter = false, + bool isBold = false, + int maxLine = 0, + double letterSpacing = 0.64}) => + Text( this, textAlign: isCenter ? TextAlign.center : null, maxLines: (maxLine > 0) ? maxLine : null, @@ -58,21 +76,33 @@ extension EmailValidator on String { ), ); - Widget toText12({Color? color, bool isUnderLine = false, bool isBold = false, bool isCenter = false, int maxLine = 0}) => Text( + Widget toText12( + {Color? color, + bool isUnderLine = false, + bool isBold = false, + FontWeight? fontWeight, + bool isCenter = false, + double? height, + int maxLine = 0}) => + Text( this, textAlign: isCenter ? TextAlign.center : null, maxLines: (maxLine > 0) ? maxLine : null, style: TextStyle( + fontSize: 12.fSize, - fontWeight: isBold ? FontWeight.bold : FontWeight.normal, + fontWeight: fontWeight ?? (isBold ? FontWeight.bold : FontWeight.normal), color: color ?? AppColors.blackColor, letterSpacing: -0.4, + height: height, decorationColor: isUnderLine ? AppColors.blackColor : null, decoration: isUnderLine ? TextDecoration.underline : null, ), ); - Widget toText12Auto({Color? color, bool isUnderLine = false, bool isBold = false, bool isCenter = false, int maxLine = 0}) => AutoSizeText( + Widget toText12Auto( + {Color? color, bool isUnderLine = false, bool isBold = false, bool isCenter = false, int maxLine = 0}) => + AutoSizeText( this, textAlign: isCenter ? TextAlign.center : null, maxLines: (maxLine > 0) ? maxLine : null, @@ -133,7 +163,14 @@ extension EmailValidator on String { decoration: isUnderLine ? TextDecoration.underline : null), ); - Widget toText14({Color? color, bool isUnderLine = false, bool isBold = false, bool isCenter = false, FontWeight? weight, int? maxlines}) => Text( + Widget toText14( + {Color? color, + bool isUnderLine = false, + bool isBold = false, + bool isCenter = false, + FontWeight? weight, + int? maxlines}) => + Text( this, textAlign: isCenter ? TextAlign.center : null, maxLines: maxlines, @@ -145,7 +182,14 @@ extension EmailValidator on String { decoration: isUnderLine ? TextDecoration.underline : null), ); - Widget toText15({Color? color, bool isUnderLine = false, bool isBold = false, bool isCenter = false, FontWeight? weight, int? maxlines}) => Text( + Widget toText15( + {Color? color, + bool isUnderLine = false, + bool isBold = false, + bool isCenter = false, + FontWeight? weight, + int? maxlines}) => + Text( this, textAlign: isCenter ? TextAlign.center : null, maxLines: maxlines, @@ -181,53 +225,93 @@ extension EmailValidator on String { Widget toText17({Color? color, bool isBold = false, bool isCenter = false}) => Text( this, textAlign: isCenter ? TextAlign.center : null, - style: TextStyle(color: color ?? AppColors.blackColor, fontSize: 17.fSize, letterSpacing: -0.4, fontWeight: isBold ? FontWeight.bold : FontWeight.normal), + style: TextStyle( + color: color ?? AppColors.blackColor, + fontSize: 17.fSize, + letterSpacing: -0.4, + fontWeight: isBold ? FontWeight.bold : FontWeight.normal), ); Widget toText18({Color? color, bool isBold = false, bool isCenter = false, int? maxlines}) => Text( maxLines: maxlines, textAlign: isCenter ? TextAlign.center : null, this, - style: TextStyle(fontSize: 18.fSize, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, color: color ?? AppColors.blackColor, letterSpacing: -0.4), + style: TextStyle( + fontSize: 18.fSize, + fontWeight: isBold ? FontWeight.bold : FontWeight.normal, + color: color ?? AppColors.blackColor, + letterSpacing: -0.4), ); Widget toText19({Color? color, bool isBold = false}) => Text( this, - style: TextStyle(fontSize: 19.fSize, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, color: color ?? AppColors.blackColor, letterSpacing: -0.4), + style: TextStyle( + fontSize: 19.fSize, + fontWeight: isBold ? FontWeight.bold : FontWeight.normal, + color: color ?? AppColors.blackColor, + letterSpacing: -0.4), ); Widget toText20({Color? color, bool isBold = false}) => Text( this, - style: TextStyle(fontSize: 20.fSize, fontWeight: isBold ? FontWeight.bold : FontWeight.normal, color: color ?? AppColors.blackColor, letterSpacing: -0.4), + style: TextStyle( + fontSize: 20.fSize, + fontWeight: isBold ? FontWeight.bold : FontWeight.normal, + color: color ?? AppColors.blackColor, + letterSpacing: -0.4), ); Widget toText21({Color? color, bool isBold = false, FontWeight? weight, int? maxlines}) => Text( this, maxLines: maxlines, - style: TextStyle(color: color ?? AppColors.blackColor, fontSize: 21.fSize, letterSpacing: -0.4, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal)), + style: TextStyle( + color: color ?? AppColors.blackColor, + fontSize: 21.fSize, + letterSpacing: -0.4, + fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal)), ); Widget toText22({Color? color, bool isBold = false, bool isCenter = false}) => Text( this, textAlign: isCenter ? TextAlign.center : null, - style: TextStyle(height: 1, color: color ?? AppColors.blackColor, fontSize: 22.fSize, letterSpacing: -0.4, fontWeight: isBold ? FontWeight.bold : FontWeight.normal), + style: TextStyle( + height: 1, + color: color ?? AppColors.blackColor, + fontSize: 22.fSize, + letterSpacing: -0.4, + fontWeight: isBold ? FontWeight.bold : FontWeight.normal), ); Widget toText24({Color? color, bool isBold = false, bool isCenter = false}) => Text( this, textAlign: isCenter ? TextAlign.center : null, - style: TextStyle(height: 23 / 24, color: color ?? AppColors.blackColor, fontSize: 24.fSize, letterSpacing: -0.4, fontWeight: isBold ? FontWeight.bold : FontWeight.normal), + style: TextStyle( + height: 23 / 24, + color: color ?? AppColors.blackColor, + fontSize: 24.fSize, + letterSpacing: -0.4, + fontWeight: isBold ? FontWeight.bold : FontWeight.normal), ); Widget toText32({Color? color, bool isBold = false, bool isCenter = false}) => Text( this, textAlign: isCenter ? TextAlign.center : null, - style: TextStyle(height: 32 / 32, color: color ?? AppColors.blackColor, fontSize: 32.fSize, letterSpacing: -0.4, fontWeight: isBold ? FontWeight.bold : FontWeight.normal), + style: TextStyle( + height: 32 / 32, + color: color ?? AppColors.blackColor, + fontSize: 32.fSize, + letterSpacing: -0.4, + fontWeight: isBold ? FontWeight.bold : FontWeight.normal), ); Widget toText44({Color? color, bool isBold = false}) => Text( this, - style: TextStyle(height: 32 / 32, color: color ?? AppColors.blackColor, fontSize: 44.fSize, letterSpacing: -0.4, fontWeight: isBold ? FontWeight.bold : FontWeight.normal), + style: TextStyle( + height: 32 / 32, + color: color ?? AppColors.blackColor, + fontSize: 44.fSize, + letterSpacing: -0.4, + fontWeight: isBold ? FontWeight.bold : FontWeight.normal), ); Widget toSectionHeading({String upperHeading = "", String lowerHeading = ""}) { @@ -263,7 +347,9 @@ extension EmailValidator on String { } bool isValidEmail() { - return RegExp(r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$').hasMatch(this); + return RegExp( + r'^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$') + .hasMatch(this); } String toFormattedDate() { diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index 7435df9..4ade673 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -17,7 +17,6 @@ import 'package:hmg_patient_app_new/presentation/home/widgets/large_service_card import 'package:hmg_patient_app_new/presentation/home/widgets/small_service_card.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; -import 'package:hmg_patient_app_new/widgets/bottom_navigation/bottom_navigation.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; import 'package:provider/provider.dart'; @@ -30,6 +29,7 @@ class LandingPage extends StatefulWidget { } class _LandingPageState extends State { + @override Widget build(BuildContext context) { AppState appState = getIt.get(); @@ -273,7 +273,6 @@ class _LandingPageState extends State { ), ), ), - bottomNavigationBar: BottomNavigation(), ); } } diff --git a/lib/presentation/home/navigation_screen.dart b/lib/presentation/home/navigation_screen.dart new file mode 100644 index 0000000..f3e088b --- /dev/null +++ b/lib/presentation/home/navigation_screen.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/presentation/home/landing_page.dart'; +import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart'; +import 'package:hmg_patient_app_new/widgets/bottom_navigation/bottom_navigation.dart'; + +class LandingNavigation extends StatefulWidget { + const LandingNavigation({super.key}); + + @override + State createState() => _LandingNavigationState(); +} + +class _LandingNavigationState extends State { + int _currentIndex = 0; + final PageController _pageController = PageController(); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: PageView( + controller: _pageController, + physics: const NeverScrollableScrollPhysics(), + children: const [ + LandingPage(), + MedicalFilePage(), + LandingPage(), + LandingPage(), + LandingPage(), + ], + ), + bottomNavigationBar: BottomNavigation( + currentIndex: _currentIndex, + onTap: (index) { + setState(() => _currentIndex = index); + _pageController.animateToPage( + index, + duration: const Duration(milliseconds: 300), + curve: Curves.easeInOut); + }, + ), + ); + } +} diff --git a/lib/splashPage.dart b/lib/splashPage.dart index bc3e542..6136d22 100644 --- a/lib/splashPage.dart +++ b/lib/splashPage.dart @@ -11,6 +11,7 @@ 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/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'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; import 'package:provider/provider.dart'; @@ -37,7 +38,7 @@ class _SplashScreenState extends State { LocalNotification.init(onNotificationClick: (payload) {}); Navigator.of(context).pushReplacement( FadePage( - page: LandingPage(), + page: LandingNavigation(), // page: LoginScreen(), ), ); diff --git a/lib/widgets/bottom_navigation/bottom_navigation.dart b/lib/widgets/bottom_navigation/bottom_navigation.dart index a052bc6..7f45464 100644 --- a/lib/widgets/bottom_navigation/bottom_navigation.dart +++ b/lib/widgets/bottom_navigation/bottom_navigation.dart @@ -1,59 +1,103 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/utils/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/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; class BottomNavigation extends StatelessWidget { - const BottomNavigation({super.key}); + final int currentIndex; + final ValueChanged onTap; + + const BottomNavigation({ + super.key, + required this.currentIndex, + required this.onTap, + }); @override Widget build(BuildContext context) { - return Container( - padding: const EdgeInsets.symmetric(vertical: 12), - decoration: const BoxDecoration( - color: Colors.white, - border: Border( - top: BorderSide(color: AppColors.bottomNAVBorder, width: 0.5), - ), - + final items = [ + BottomNavItem(icon: AppAssets.homeBottom, label: LocaleKeys.home.tr()), + BottomNavItem(icon: AppAssets.myFilesBottom, label: LocaleKeys.myFiles.tr()), + BottomNavItem( + icon: AppAssets.bookAppoBottom, + label: LocaleKeys.appointment.tr(), + iconSize: 27, + isSpecial: true, ), + BottomNavItem(icon: AppAssets.toDoBottom, label: LocaleKeys.todoList.tr()), + BottomNavItem(icon: AppAssets.servicesBottom, label: LocaleKeys.services2.tr()), + ]; + + return Container( + decoration: _containerDecoration, + padding: _containerPadding, child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, - children: [ - _buildNavItem(AppAssets.homeBottom, LocaleKeys.home.tr()), - _buildNavItem(AppAssets.myFilesBottom, LocaleKeys.myFiles.tr()), - _buildNavItem(AppAssets.bookAppoBottom, LocaleKeys.appointment.tr(), iconSize: 32), - _buildNavItem(AppAssets.toDoBottom, LocaleKeys.todoList.tr()), - _buildNavItem(AppAssets.servicesBottom, LocaleKeys.services2.tr()), - ], + children: List.generate( + items.length, + (index) => _buildNavItem(items[index], index), + ), ), ); } - Widget _buildNavItem(String iconName, String label,{ double iconSize = 24}) { - return Column( - mainAxisSize: MainAxisSize.min, - children: [ - Container( - padding: const EdgeInsets.all(10), - child: Utils.buildSvgWithAssets( - icon: iconName, - height: iconSize, - width: iconSize - ), - ), - // const SizedBox(height: 4), - Text( - label, - style: TextStyle( - fontSize: 13, - fontWeight: FontWeight.w500, - color: Colors.black87, + Widget _buildNavItem(BottomNavItem item, int index) { + final bool isSelected = currentIndex == index; + + return GestureDetector( + onTap: () => onTap(index), + behavior: HitTestBehavior.opaque, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + Center( + child: Utils.buildSvgWithAssets( + icon: item.icon, + height: item.iconSize.h, + width: item.iconSize.h, + // iconColor: isSelected ? Colors.black87 : Colors.black87, + ), + ), + const SizedBox(height: 10), + item.label.toText12( + fontWeight:FontWeight.w500, + // color: Colors.black87, + // textAlign: TextAlign.center, ), - ), - ], + SizedBox(height: item.isSpecial ? 5:0 ) + ], + + ), ); } } + +class BottomNavItem { + final String icon; + final String label; + final double iconSize; + final bool isSpecial; + + const BottomNavItem({ + required this.icon, + required this.label, + this.iconSize = 21, + this.isSpecial = false, + }); +} + +// Constants +const EdgeInsets _containerPadding = EdgeInsets.all(15); +const BoxDecoration _containerDecoration = BoxDecoration( + color: Colors.white, + border: Border( + top: BorderSide( + color: AppColors.bottomNAVBorder, + width: 0.5, + ), + ), +); From 712e11c69aa5657f499d97780476cc06e0b12a42 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Wed, 3 Sep 2025 17:26:54 +0300 Subject: [PATCH 4/6] prescription details page implementation contd. --- assets/images/svg/doctor_calendar_icon.svg | 8 + .../images/svg/prescription_remarks_icon.svg | 5 + assets/images/svg/rating_icon.svg | 3 + lib/core/app_assets.dart | 3 + .../prescription_detail_response_model.dart | 144 ++++++ .../prescriptions/prescriptions_repo.dart | 66 +++ .../prescriptions_view_model.dart | 34 +- .../prescription_detail_page.dart | 313 ++++++++++++ .../prescriptions_list_page.dart | 466 +++++++++--------- lib/theme/colors.dart | 1 + 10 files changed, 812 insertions(+), 231 deletions(-) create mode 100644 assets/images/svg/doctor_calendar_icon.svg create mode 100644 assets/images/svg/prescription_remarks_icon.svg create mode 100644 assets/images/svg/rating_icon.svg create mode 100644 lib/features/prescriptions/models/resp_models/prescription_detail_response_model.dart create mode 100644 lib/presentation/prescriptions/prescription_detail_page.dart diff --git a/assets/images/svg/doctor_calendar_icon.svg b/assets/images/svg/doctor_calendar_icon.svg new file mode 100644 index 0000000..f76c49f --- /dev/null +++ b/assets/images/svg/doctor_calendar_icon.svg @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/assets/images/svg/prescription_remarks_icon.svg b/assets/images/svg/prescription_remarks_icon.svg new file mode 100644 index 0000000..a8d7adb --- /dev/null +++ b/assets/images/svg/prescription_remarks_icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/svg/rating_icon.svg b/assets/images/svg/rating_icon.svg new file mode 100644 index 0000000..dee25d4 --- /dev/null +++ b/assets/images/svg/rating_icon.svg @@ -0,0 +1,3 @@ + + + diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index a43406e..bd3e541 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -64,6 +64,9 @@ class AppAssets { static const String view_report_icon = '$svgBasePath/view_report_icon.svg'; static const String forward_arrow_icon = '$svgBasePath/forward_arrow_icon.svg'; static const String prescription_refill_icon = '$svgBasePath/prescription_refill_icon.svg'; + static const String rating_icon = '$svgBasePath/rating_icon.svg'; + static const String doctor_calendar_icon = '$svgBasePath/doctor_calendar_icon.svg'; + static const String prescription_remarks_icon = '$svgBasePath/prescription_remarks_icon.svg'; //bottom navigation// diff --git a/lib/features/prescriptions/models/resp_models/prescription_detail_response_model.dart b/lib/features/prescriptions/models/resp_models/prescription_detail_response_model.dart new file mode 100644 index 0000000..3ca8396 --- /dev/null +++ b/lib/features/prescriptions/models/resp_models/prescription_detail_response_model.dart @@ -0,0 +1,144 @@ +class PrescriptionDetailResponseModel { + String? address; + num? appointmentNo; + String? clinic; + dynamic companyName; + num? days; + String? doctorName; + num? doseDailyQuantity; + String? frequency; + num? frequencyNumber; + dynamic image; + dynamic imageExtension; + String? imageSRCUrl; + dynamic imageString; + String? imageThumbUrl; + String? isCovered; + String? itemDescription; + num? itemID; + String? orderDate; + num? patientID; + String? patientName; + String? phoneOffice1; + dynamic prescriptionQR; + num? prescriptionTimes; + dynamic productImage; + dynamic productImageBase64; + String? productImageString; + num? projectID; + String? projectName; + String? remarks; + String? route; + String? sKU; + num? scaleOffset; + String? startDate; + + PrescriptionDetailResponseModel( + {this.address, + this.appointmentNo, + this.clinic, + this.companyName, + this.days, + this.doctorName, + this.doseDailyQuantity, + this.frequency, + this.frequencyNumber, + this.image, + this.imageExtension, + this.imageSRCUrl, + this.imageString, + this.imageThumbUrl, + this.isCovered, + this.itemDescription, + this.itemID, + this.orderDate, + this.patientID, + this.patientName, + this.phoneOffice1, + this.prescriptionQR, + this.prescriptionTimes, + this.productImage, + this.productImageBase64, + this.productImageString, + this.projectID, + this.projectName, + this.remarks, + this.route, + this.sKU, + this.scaleOffset, + this.startDate}); + + PrescriptionDetailResponseModel.fromJson(Map json) { + address = json['Address']; + appointmentNo = json['AppointmentNo']; + clinic = json['Clinic']; + companyName = json['CompanyName']; + days = json['Days']; + doctorName = json['DoctorName']; + doseDailyQuantity = json['DoseDailyQuantity']; + frequency = json['Frequency']; + frequencyNumber = json['FrequencyNumber']; + image = json['Image']; + imageExtension = json['ImageExtension']; + imageSRCUrl = json['ImageSRCUrl']; + imageString = json['ImageString']; + imageThumbUrl = json['ImageThumbUrl']; + isCovered = json['IsCovered']; + itemDescription = json['ItemDescription']; + itemID = json['ItemID']; + orderDate = json['OrderDate']; + patientID = json['PatientID']; + patientName = json['PatientName']; + phoneOffice1 = json['PhoneOffice1']; + prescriptionQR = json['PrescriptionQR']; + prescriptionTimes = json['PrescriptionTimes']; + productImage = json['ProductImage']; + productImageBase64 = json['ProductImageBase64']; + productImageString = json['ProductImageString']; + projectID = json['ProjectID']; + projectName = json['ProjectName']; + remarks = json['Remarks']; + route = json['Route']; + sKU = json['SKU']; + scaleOffset = json['ScaleOffset']; + startDate = json['StartDate']; + } + + Map toJson() { + final Map data = new Map(); + data['Address'] = address; + data['AppointmentNo'] = appointmentNo; + data['Clinic'] = clinic; + data['CompanyName'] = companyName; + data['Days'] = days; + data['DoctorName'] = doctorName; + data['DoseDailyQuantity'] = doseDailyQuantity; + data['Frequency'] = frequency; + data['FrequencyNumber'] = frequencyNumber; + data['Image'] = image; + data['ImageExtension'] = imageExtension; + data['ImageSRCUrl'] = imageSRCUrl; + data['ImageString'] = imageString; + data['ImageThumbUrl'] = imageThumbUrl; + data['IsCovered'] = isCovered; + data['ItemDescription'] = itemDescription; + data['ItemID'] = itemID; + data['OrderDate'] = orderDate; + data['PatientID'] = patientID; + data['PatientName'] = patientName; + data['PhoneOffice1'] = phoneOffice1; + data['PrescriptionQR'] = prescriptionQR; + data['PrescriptionTimes'] = prescriptionTimes; + data['ProductImage'] = productImage; + data['ProductImageBase64'] = productImageBase64; + data['ProductImageString'] = productImageString; + data['ProjectID'] = projectID; + data['ProjectName'] = projectName; + data['Remarks'] = remarks; + data['Route'] = route; + data['SKU'] = sKU; + data['ScaleOffset'] = scaleOffset; + data['StartDate'] = startDate; + return data; + } +} diff --git a/lib/features/prescriptions/prescriptions_repo.dart b/lib/features/prescriptions/prescriptions_repo.dart index 53cf827..de7bc4b 100644 --- a/lib/features/prescriptions/prescriptions_repo.dart +++ b/lib/features/prescriptions/prescriptions_repo.dart @@ -4,10 +4,13 @@ import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; import 'package:dartz/dartz.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/models/resp_models/prescription_detail_response_model.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; abstract class PrescriptionsRepo { Future>>> getPatientPrescriptionOrders({required String patientId}); + + Future>>> getPatientPrescriptionDetails({required PatientPrescriptionsResponseModel prescriptionsResponseModel}); } class PrescriptionsRepoImp implements PrescriptionsRepo { @@ -72,4 +75,67 @@ class PrescriptionsRepoImp implements PrescriptionsRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future>>> getPatientPrescriptionDetails({required PatientPrescriptionsResponseModel prescriptionsResponseModel}) async { + final 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!?" + }; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + prescriptionsResponseModel.isInOutPatient! ? GET_PRESCRIPTION_REPORT_ENH : GET_PRESCRIPTION_REPORT, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus}) { + try { + final list = prescriptionsResponseModel.isInOutPatient! ? response['ListPRM'] : response['INP_GetPrescriptionReport_List']; + if (list == null || list.isEmpty) { + throw Exception("prescription list is empty"); + } + + final prescriptionOrders = list.map((item) => PrescriptionDetailResponseModel.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: prescriptionOrders, + ); + } 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/prescriptions/prescriptions_view_model.dart b/lib/features/prescriptions/prescriptions_view_model.dart index 031b26e..23a818e 100644 --- a/lib/features/prescriptions/prescriptions_view_model.dart +++ b/lib/features/prescriptions/prescriptions_view_model.dart @@ -1,21 +1,26 @@ import 'package:flutter/material.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/models/resp_models/prescription_detail_response_model.dart'; import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_repo.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; class PrescriptionsViewModel extends ChangeNotifier { bool isPrescriptionsOrdersLoading = false; + bool isPrescriptionsDetailsLoading = false; PrescriptionsRepo prescriptionsRepo; ErrorHandlerService errorHandlerService; + // Prescription Orders Lists List patientPrescriptionOrders = []; List patientPrescriptionOrdersByClinic = []; List patientPrescriptionOrdersByHospital = []; - List patientPrescriptionOrdersViewList = []; + // Prescription Details List + List prescriptionDetailsList = []; + bool isSortByClinic = true; PrescriptionsViewModel({required this.prescriptionsRepo, required this.errorHandlerService}); @@ -26,11 +31,18 @@ class PrescriptionsViewModel extends ChangeNotifier { patientPrescriptionOrdersByHospital.clear(); patientPrescriptionOrdersViewList.clear(); isPrescriptionsOrdersLoading = true; + isPrescriptionsDetailsLoading = true; isSortByClinic = true; getPatientPrescriptionOrders(); notifyListeners(); } + setPrescriptionsDetailsLoading() { + isPrescriptionsDetailsLoading = true; + prescriptionDetailsList.clear(); + notifyListeners(); + } + setIsSortByClinic(bool value) { isSortByClinic = value; if (isSortByClinic) { @@ -79,4 +91,24 @@ class PrescriptionsViewModel extends ChangeNotifier { }, ); } + + Future getPrescriptionDetails(PatientPrescriptionsResponseModel prescriptionsResponseModel, {Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await prescriptionsRepo.getPatientPrescriptionDetails(prescriptionsResponseModel: prescriptionsResponseModel); + + 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) { + prescriptionDetailsList = apiResponse.data!; + isPrescriptionsDetailsLoading = false; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } } diff --git a/lib/presentation/prescriptions/prescription_detail_page.dart b/lib/presentation/prescriptions/prescription_detail_page.dart new file mode 100644 index 0000000..0231c67 --- /dev/null +++ b/lib/presentation/prescriptions/prescription_detail_page.dart @@ -0,0 +1,313 @@ +import 'dart:async'; + +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/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/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/shimmer/movies_shimmer_widget.dart'; +import 'package:provider/provider.dart'; + +class PrescriptionDetailPage extends StatefulWidget { + PrescriptionDetailPage({super.key, required this.prescriptionsResponseModel}); + + PatientPrescriptionsResponseModel prescriptionsResponseModel; + + @override + State createState() => _PrescriptionDetailPageState(); +} + +class _PrescriptionDetailPageState extends State { + late PrescriptionsViewModel prescriptionsViewModel; + + @override + void initState() { + scheduleMicrotask(() { + prescriptionsViewModel.getPrescriptionDetails(widget.prescriptionsResponseModel); + }); + super.initState(); + } + + @override + Widget build(BuildContext context) { + prescriptionsViewModel = Provider.of(context, listen: false); + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + appBar: AppBar( + title: LocaleKeys.prescriptions.tr(context: context).toText18(), + backgroundColor: AppColors.bgScaffoldColor, + ), + body: SingleChildScrollView( + child: Consumer(builder: (context, prescriptionVM, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.prescriptions.tr(context: context).toText24(isBold: true).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 24.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: true, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Image.network( + widget.prescriptionsResponseModel.doctorImageURL!, + width: 24.h, + height: 24.h, + fit: BoxFit.fill, + ).circle(100), + SizedBox(width: 8.h), + Expanded(child: widget.prescriptionsResponseModel.doctorName!.toText16(isBold: true)), + ], + ), + SizedBox(height: 16.h), + Wrap( + direction: Axis.horizontal, + spacing: 6.h, + runSpacing: 6.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: 12, + fontWeight: FontWeight.w500, + borderRadius: 8, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 30.h, + ), + ], + ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + CustomButton( + text: widget.prescriptionsResponseModel.clinicDescription!, + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 12, + fontWeight: FontWeight.w500, + borderRadius: 8, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 30.h, + ), + ], + ), + 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: 12, + fontWeight: FontWeight.w500, + borderRadius: 8, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 30.h, + ), + ], + ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + CustomButton( + text: widget.prescriptionsResponseModel.name!, + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 12, + fontWeight: FontWeight.w500, + borderRadius: 8, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 30.h, + ), + ], + ), + ], + ), + ], + ), + ), + ).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 16.h), + ListView.builder( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: prescriptionVM.isPrescriptionsDetailsLoading ? 5 : prescriptionVM.prescriptionDetailsList.length, + itemBuilder: (context, index) { + return prescriptionVM.isPrescriptionsDetailsLoading + ? const MoviesShimmerWidget() + : AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: AnimatedContainer( + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + margin: EdgeInsets.symmetric(vertical: 8.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: true), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.all(16.h), + child: Container( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Image.network( + prescriptionVM.prescriptionDetailsList[index].imageSRCUrl!, + width: 60.h, + height: 60.h, + fit: BoxFit.fill, + ).circle(100), + SizedBox(width: 8.h), + Expanded( + child: prescriptionVM.prescriptionDetailsList[index].itemDescription!.toText16(isBold: true, maxlines: 2), + ), + ], + ), + SizedBox(height: 16.h), + Wrap( + direction: Axis.horizontal, + spacing: 6.h, + runSpacing: 6.h, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + CustomButton( + text: "${LocaleKeys.route.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].route}", + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 12, + fontWeight: FontWeight.w500, + borderRadius: 8, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 30.h, + ), + ], + ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + CustomButton( + text: "${LocaleKeys.frequency.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].frequency}", + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 12, + fontWeight: FontWeight.w500, + borderRadius: 8, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 30.h, + ), + ], + ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + CustomButton( + text: "${LocaleKeys.dailyDoses.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].doseDailyQuantity}", + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 12, + fontWeight: FontWeight.w500, + borderRadius: 8, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 30.h, + ), + ], + ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + CustomButton( + text: "${LocaleKeys.days.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].days}", + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 12, + fontWeight: FontWeight.w500, + borderRadius: 8, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 30.h, + ), + ], + ), + ], + ), + SizedBox(height: 8.h), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.prescription_remarks_icon, width: 18.h, height: 18.h), + SizedBox(width: 9.h), + Expanded(child: "${LocaleKeys.remarks.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].remarks!}".toText10(isBold: true)), + ], + ) + ], + ), + ), + ) + ], + ), + ), + ), + ), + ); + }, + ).paddingSymmetrical(24.h, 0.h), + ], + ); + }), + ), + ); + } +} diff --git a/lib/presentation/prescriptions/prescriptions_list_page.dart b/lib/presentation/prescriptions/prescriptions_list_page.dart index c678a8e..4aa1a1b 100644 --- a/lib/presentation/prescriptions/prescriptions_list_page.dart +++ b/lib/presentation/prescriptions/prescriptions_list_page.dart @@ -11,9 +11,11 @@ import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/prescriptions/prescription_detail_page.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; 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'; class PrescriptionsListPage extends StatefulWidget { @@ -45,248 +47,252 @@ class _PrescriptionsListPageState extends State { title: LocaleKeys.prescriptions.tr(context: context).toText18(), backgroundColor: AppColors.bgScaffoldColor, ), - body: Padding( - padding: EdgeInsets.all(24.h), - child: SingleChildScrollView( - child: Consumer(builder: (context, model, child) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LocaleKeys.prescriptions.tr(context: context).toText24(isBold: true), - SizedBox(height: 16.h), - // Build Tab Bar - SizedBox(height: 16.h), - // Clinic & Hospital Sort - Row( - children: [ - CustomButton( - text: LocaleKeys.byClinic.tr(context: context), - onPressed: () { - model.setIsSortByClinic(true); - }, - backgroundColor: model.isSortByClinic ? AppColors.bgRedLightColor : AppColors.whiteColor, - borderColor: model.isSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withOpacity(0.2), - textColor: model.isSortByClinic ? AppColors.primaryRedColor : AppColors.blackColor, - fontSize: 12, - fontWeight: FontWeight.w500, - borderRadius: 10, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, - ), - SizedBox(width: 8.h), - CustomButton( - text: LocaleKeys.byHospital.tr(context: context), - onPressed: () { - model.setIsSortByClinic(false); - }, - backgroundColor: model.isSortByClinic ? AppColors.whiteColor : AppColors.bgRedLightColor, - borderColor: model.isSortByClinic ? AppColors.textColor.withOpacity(0.2) : AppColors.primaryRedColor, - textColor: model.isSortByClinic ? AppColors.blackColor : AppColors.primaryRedColor, - fontSize: 12, - fontWeight: FontWeight.w500, - borderRadius: 10, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, - ), - ], - ), - SizedBox(height: 20.h), - // Expandable list - ListView.builder( - itemCount: model.isPrescriptionsOrdersLoading ? 4 : model.patientPrescriptionOrdersViewList.length, - physics: NeverScrollableScrollPhysics(), - shrinkWrap: true, - padding: const EdgeInsets.only(left: 0, right: 8), - itemBuilder: (context, index) { - final isExpanded = expandedIndex == index; - return model.isPrescriptionsOrdersLoading - ? const MoviesShimmerWidget() - : AnimationConfiguration.staggeredList( - position: index, - duration: const Duration(milliseconds: 500), - child: SlideAnimation( - verticalOffset: 100.0, - child: FadeInAnimation( - child: AnimatedContainer( - duration: Duration(milliseconds: 300), - curve: Curves.easeInOut, - margin: EdgeInsets.symmetric(vertical: 8.h), - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: true), - child: InkWell( - onTap: () { - setState(() { - expandedIndex = isExpanded ? null : index; - }); - }, - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Padding( - padding: EdgeInsets.all(16.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - CustomButton( - text: "${model.patientPrescriptionOrdersViewList[index].prescriptionsList!.length} Prescriptions Available", - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 10, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - Icon(isExpanded ? Icons.expand_less : Icons.expand_more), - ], - ), - SizedBox(height: 8.h), - model.patientPrescriptionOrdersViewList[index].filterName!.toText16(isBold: true) - ], - ), + body: SingleChildScrollView( + child: Consumer(builder: (context, model, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.prescriptions.tr(context: context).toText24(isBold: true).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 16.h), + // Build Tab Bar + SizedBox(height: 16.h), + // Clinic & Hospital Sort + Row( + children: [ + CustomButton( + text: LocaleKeys.byClinic.tr(context: context), + onPressed: () { + model.setIsSortByClinic(true); + }, + backgroundColor: model.isSortByClinic ? AppColors.bgRedLightColor : AppColors.whiteColor, + borderColor: model.isSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withOpacity(0.2), + textColor: model.isSortByClinic ? AppColors.primaryRedColor : AppColors.blackColor, + fontSize: 12, + fontWeight: FontWeight.w500, + borderRadius: 10, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + ), + SizedBox(width: 8.h), + CustomButton( + text: LocaleKeys.byHospital.tr(context: context), + onPressed: () { + model.setIsSortByClinic(false); + }, + backgroundColor: model.isSortByClinic ? AppColors.whiteColor : AppColors.bgRedLightColor, + borderColor: model.isSortByClinic ? AppColors.textColor.withOpacity(0.2) : AppColors.primaryRedColor, + textColor: model.isSortByClinic ? AppColors.blackColor : AppColors.primaryRedColor, + fontSize: 12, + fontWeight: FontWeight.w500, + borderRadius: 10, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + ), + ], + ).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 20.h), + // Expandable list + ListView.builder( + itemCount: model.isPrescriptionsOrdersLoading ? 4 : model.patientPrescriptionOrdersViewList.length, + physics: NeverScrollableScrollPhysics(), + shrinkWrap: true, + padding: const EdgeInsets.only(left: 0, right: 8), + itemBuilder: (context, index) { + final isExpanded = expandedIndex == index; + return model.isPrescriptionsOrdersLoading + ? const MoviesShimmerWidget() + : AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: AnimatedContainer( + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + margin: EdgeInsets.symmetric(vertical: 8.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: true), + child: InkWell( + onTap: () { + setState(() { + expandedIndex = isExpanded ? null : index; + }); + }, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + CustomButton( + text: "${model.patientPrescriptionOrdersViewList[index].prescriptionsList!.length} Prescriptions Available", + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 10, + fontWeight: FontWeight.w500, + borderRadius: 8, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 30.h, + ), + Icon(isExpanded ? Icons.expand_less : Icons.expand_more), + ], + ), + SizedBox(height: 8.h), + model.patientPrescriptionOrdersViewList[index].filterName!.toText16(isBold: true) + ], ), - AnimatedSwitcher( - duration: Duration(milliseconds: 500), - switchInCurve: Curves.easeIn, - switchOutCurve: Curves.easeOut, - transitionBuilder: (Widget child, Animation animation) { - return FadeTransition( - opacity: animation, - child: SizeTransition( - sizeFactor: animation, - axisAlignment: 0.0, - child: child, - ), - ); - }, - child: isExpanded - ? Container( - key: ValueKey(index), - padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 8.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - ...model.patientPrescriptionOrdersViewList[index].prescriptionsList!.map((prescription) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisSize: MainAxisSize.min, - children: [ - Image.network( - prescription.doctorImageURL!, - width: 24.h, - height: 24.h, - fit: BoxFit.fill, - ).circle(100), - SizedBox(width: 8.h), - Expanded(child: prescription.doctorName!.toText14(weight: FontWeight.w500)), - ], - ), - SizedBox(height: 8.h), - Row( - children: [ - CustomButton( - text: DateUtil.formatDateToDate(DateUtil.convertStringToDate(prescription.appointmentDate), false), + ), + AnimatedSwitcher( + duration: Duration(milliseconds: 500), + switchInCurve: Curves.easeIn, + switchOutCurve: Curves.easeOut, + transitionBuilder: (Widget child, Animation animation) { + return FadeTransition( + opacity: animation, + child: SizeTransition( + sizeFactor: animation, + axisAlignment: 0.0, + child: child, + ), + ); + }, + child: isExpanded + ? Container( + key: ValueKey(index), + padding: EdgeInsets.symmetric(horizontal: 16.h, vertical: 8.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + ...model.patientPrescriptionOrdersViewList[index].prescriptionsList!.map((prescription) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Image.network( + prescription.doctorImageURL!, + width: 24.h, + height: 24.h, + fit: BoxFit.fill, + ).circle(100), + SizedBox(width: 8.h), + Expanded(child: prescription.doctorName!.toText14(weight: FontWeight.w500)), + ], + ), + SizedBox(height: 8.h), + Row( + children: [ + CustomButton( + text: DateUtil.formatDateToDate(DateUtil.convertStringToDate(prescription.appointmentDate), false), + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 12, + fontWeight: FontWeight.w500, + borderRadius: 8, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 24.h, + ), + SizedBox(width: 8.h), + CustomButton( + text: model.isSortByClinic ? prescription.name! : prescription.clinicDescription!, + onPressed: () {}, + backgroundColor: AppColors.greyColor, + borderColor: AppColors.greyColor, + textColor: AppColors.blackColor, + fontSize: 12, + fontWeight: FontWeight.w500, + borderRadius: 8, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 24.h, + ), + ], + ), + SizedBox(height: 8.h), + Row( + children: [ + Expanded( + flex: 6, + child: CustomButton( + text: prescription.isHomeMedicineDeliverySupported! + ? LocaleKeys.resendOrder.tr(context: context) + : LocaleKeys.prescriptionDeliveryError.tr(context: context), onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 12, + backgroundColor: prescription.isHomeMedicineDeliverySupported! ? AppColors.successColor.withOpacity(0.15) : AppColors.greyF7Color, + borderColor: AppColors.successColor.withOpacity(0.01), + textColor: prescription.isHomeMedicineDeliverySupported! ? AppColors.successColor : AppColors.textColor.withOpacity(0.35), + fontSize: prescription.isHomeMedicineDeliverySupported! ? 14 : 12, fontWeight: FontWeight.w500, - borderRadius: 8, + borderRadius: 12, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 24.h, + height: 40.h, + icon: AppAssets.prescription_refill_icon, + iconColor: prescription.isHomeMedicineDeliverySupported! ? AppColors.successColor : AppColors.textColor.withOpacity(0.35), + iconSize: 14.h, ), - SizedBox(width: 8.h), - CustomButton( - text: model.isSortByClinic ? prescription.name! : prescription.clinicDescription!, - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 12, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 24.h, - ), - ], - ), - SizedBox(height: 8.h), - Row( - children: [ - Expanded( - flex: 6, - child: CustomButton( - text: prescription.isHomeMedicineDeliverySupported! - ? LocaleKeys.resendOrder.tr(context: context) - : LocaleKeys.prescriptionDeliveryError.tr(context: context), - onPressed: () {}, - backgroundColor: prescription.isHomeMedicineDeliverySupported! ? AppColors.successColor.withOpacity(0.15) : AppColors.greyF7Color, - borderColor: AppColors.successColor.withOpacity(0.01), - textColor: prescription.isHomeMedicineDeliverySupported! ? AppColors.successColor : AppColors.textColor.withOpacity(0.35), - fontSize: prescription.isHomeMedicineDeliverySupported! ? 14 : 12, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 40.h, - icon: AppAssets.prescription_refill_icon, - iconColor: prescription.isHomeMedicineDeliverySupported! ? AppColors.successColor : AppColors.textColor.withOpacity(0.35), - iconSize: 14.h, + ), + SizedBox(width: 8.h), + Expanded( + flex: 1, + child: Container( + height: 40.h, + width: 40.h, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.textColor, + borderRadius: 10.h, ), - ), - SizedBox(width: 8.h), - Expanded( - flex: 1, - child: Container( - height: 40.h, - width: 40.h, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.textColor, - borderRadius: 10.h, - ), - child: Padding( - padding: EdgeInsets.all(8.h), - child: Utils.buildSvgWithAssets( - icon: AppAssets.forward_arrow_icon, - width: 10.h, - height: 10.h, - fit: BoxFit.contain, - ), + child: Padding( + padding: EdgeInsets.all(8.h), + child: Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon, + width: 10.h, + height: 10.h, + fit: BoxFit.contain, ), ), - ), - ], - ), - SizedBox(height: 12.h), - Divider(color: AppColors.borderOnlyColor.withOpacity(0.05), height: 1.h), - SizedBox(height: 12.h), - ], - ); - }).toList(), - ], - ), - ) - : SizedBox.shrink(), - ), - ], - ), + ).onPress(() { + model.setPrescriptionsDetailsLoading(); + Navigator.of(context).push( + FadePage( + page: PrescriptionDetailPage(prescriptionsResponseModel: prescription), + ), + ); + }), + ), + ], + ), + SizedBox(height: 12.h), + Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h), + SizedBox(height: 12.h), + ], + ); + }).toList(), + ], + ), + ) + : SizedBox.shrink(), + ), + ], ), ), ), ), - ); - }, - ), - ], - ); - }), - ), + ), + ); + }, + ).paddingSymmetrical(24.h, 0.h), + ], + ); + }), ), ); } diff --git a/lib/theme/colors.dart b/lib/theme/colors.dart index d9cf167..018727c 100644 --- a/lib/theme/colors.dart +++ b/lib/theme/colors.dart @@ -32,6 +32,7 @@ class AppColors { static const Color borderOnlyColor = Color(0xFF2E3039); static const Color dividerColor = Color(0xFFD2D2D2); static const Color warningColorYellow = Color(0xFFF4A308); + static const Color ratingColorYellow = Color(0xFFFFAF15); //Chips static const Color successColor = Color(0xff18C273); From e8f8f6074ca829ae537ff9a4f1082f1d769689b3 Mon Sep 17 00:00:00 2001 From: Haroon Amjad <> Date: Thu, 4 Sep 2025 08:44:37 +0300 Subject: [PATCH 5/6] prescription details updates --- .../prescription_detail_page.dart | 195 +++++++++--------- 1 file changed, 97 insertions(+), 98 deletions(-) diff --git a/lib/presentation/prescriptions/prescription_detail_page.dart b/lib/presentation/prescriptions/prescription_detail_page.dart index 0231c67..d14da43 100644 --- a/lib/presentation/prescriptions/prescription_detail_page.dart +++ b/lib/presentation/prescriptions/prescription_detail_page.dart @@ -95,7 +95,7 @@ class _PrescriptionDetailPageState extends State { backgroundColor: AppColors.greyColor, borderColor: AppColors.greyColor, textColor: AppColors.blackColor, - fontSize: 12, + fontSize: 10, fontWeight: FontWeight.w500, borderRadius: 8, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), @@ -112,7 +112,7 @@ class _PrescriptionDetailPageState extends State { backgroundColor: AppColors.greyColor, borderColor: AppColors.greyColor, textColor: AppColors.blackColor, - fontSize: 12, + fontSize: 10, fontWeight: FontWeight.w500, borderRadius: 8, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), @@ -132,7 +132,7 @@ class _PrescriptionDetailPageState extends State { backgroundColor: AppColors.greyColor, borderColor: AppColors.greyColor, textColor: AppColors.blackColor, - fontSize: 12, + fontSize: 10, fontWeight: FontWeight.w500, borderRadius: 8, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), @@ -149,7 +149,7 @@ class _PrescriptionDetailPageState extends State { backgroundColor: AppColors.greyColor, borderColor: AppColors.greyColor, textColor: AppColors.blackColor, - fontSize: 12, + fontSize: 10, fontWeight: FontWeight.w500, borderRadius: 8, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), @@ -185,116 +185,115 @@ class _PrescriptionDetailPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Padding( - padding: EdgeInsets.all(16.h), - child: Container( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 16.h), + Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Image.network( + prescriptionVM.prescriptionDetailsList[index].imageThumbUrl!, + width: 60.h, + height: 60.h, + fit: BoxFit.fill, + ).circle(100), + SizedBox(width: 8.h), + Expanded( + child: prescriptionVM.prescriptionDetailsList[index].itemDescription!.toText16(isBold: true, maxlines: 2), + ), + ], + ).paddingSymmetrical(16.h, 0.h), + SizedBox(height: 16.h), + Wrap( + direction: Axis.horizontal, + spacing: 6.h, + runSpacing: 6.h, children: [ Row( mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.start, children: [ - Image.network( - prescriptionVM.prescriptionDetailsList[index].imageSRCUrl!, - width: 60.h, - height: 60.h, - fit: BoxFit.fill, - ).circle(100), - SizedBox(width: 8.h), - Expanded( - child: prescriptionVM.prescriptionDetailsList[index].itemDescription!.toText16(isBold: true, maxlines: 2), + CustomButton( + text: "${LocaleKeys.route.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].route}", + 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, ), ], ), - SizedBox(height: 16.h), - Wrap( - direction: Axis.horizontal, - spacing: 6.h, - runSpacing: 6.h, + Row( + mainAxisSize: MainAxisSize.min, children: [ - Row( - mainAxisSize: MainAxisSize.min, - children: [ - CustomButton( - text: "${LocaleKeys.route.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].route}", - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 12, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - ], - ), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - CustomButton( - text: "${LocaleKeys.frequency.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].frequency}", - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 12, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - ], - ), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - CustomButton( - text: "${LocaleKeys.dailyDoses.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].doseDailyQuantity}", - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 12, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - ], + CustomButton( + text: "${LocaleKeys.frequency.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].frequency}", + 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, ), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - CustomButton( - text: "${LocaleKeys.days.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].days}", - onPressed: () {}, - backgroundColor: AppColors.greyColor, - borderColor: AppColors.greyColor, - textColor: AppColors.blackColor, - fontSize: 12, - fontWeight: FontWeight.w500, - borderRadius: 8, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 30.h, - ), - ], + ], + ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + CustomButton( + text: "${LocaleKeys.dailyDoses.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].doseDailyQuantity}", + 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, ), ], ), - SizedBox(height: 8.h), Row( - crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, children: [ - Utils.buildSvgWithAssets(icon: AppAssets.prescription_remarks_icon, width: 18.h, height: 18.h), - SizedBox(width: 9.h), - Expanded(child: "${LocaleKeys.remarks.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].remarks!}".toText10(isBold: true)), + CustomButton( + text: "${LocaleKeys.days.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].days}", + 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, + ), ], - ) + ), + ], + ).paddingSymmetrical(16.h, 0.h), + SizedBox(height: 8.h), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.prescription_remarks_icon, width: 18.h, height: 18.h), + SizedBox(width: 9.h), + Expanded(child: "${LocaleKeys.remarks.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].remarks!}".toText10(isBold: true)), ], - ), - ), + ).paddingSymmetrical(16.h, 0.h), + SizedBox(height: 16.h), + Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h), + SizedBox(height: 16.h), + ], ) ], ), From efb22aec4ef08c5cbb5cb01d068a41ec7a8ab9fa Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Thu, 4 Sep 2025 11:27:25 +0300 Subject: [PATCH 6/6] prescription details page implemented --- .../images/svg/prescription_reminder_icon.svg | 5 + assets/langs/ar-SA.json | 5 +- assets/langs/en-US.json | 4 +- lib/core/api_consts.dart | 1 - lib/core/app_assets.dart | 1 + lib/core/utils/calendar_utils.dart | 4 - .../prescription_detail_response_model.dart | 5 +- .../prescriptions_view_model.dart | 8 + lib/generated/locale_keys.g.dart | 2 + .../prescription_detail_page.dart | 599 ++++++++++-------- .../prescriptions_list_page.dart | 4 +- lib/theme/colors.dart | 1 + 12 files changed, 374 insertions(+), 265 deletions(-) create mode 100644 assets/images/svg/prescription_reminder_icon.svg diff --git a/assets/images/svg/prescription_reminder_icon.svg b/assets/images/svg/prescription_reminder_icon.svg new file mode 100644 index 0000000..3a854c3 --- /dev/null +++ b/assets/images/svg/prescription_reminder_icon.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/langs/ar-SA.json b/assets/langs/ar-SA.json index 718bf83..91d5d4f 100644 --- a/assets/langs/ar-SA.json +++ b/assets/langs/ar-SA.json @@ -782,5 +782,8 @@ "resultsPending": "النتائج معلقة", "resultsAvailable": "النتائج متاحة", "viewReport": "عرض التقرير", - "prescriptionDeliveryError": "هذه العيادة لا تدعم إعادة التعبئة والتسليم." + "prescriptionDeliveryError": "هذه العيادة لا تدعم إعادة التعبئة والتسليم.", + + "checkAvailability": "التحقق من التوفر", + "readInstructions": "قراءة التعليمات" } \ No newline at end of file diff --git a/assets/langs/en-US.json b/assets/langs/en-US.json index 6743993..f91d98b 100644 --- a/assets/langs/en-US.json +++ b/assets/langs/en-US.json @@ -778,5 +778,7 @@ "resultsPending": "Results Pending", "resultsAvailable": "Results Available", "viewReport": "View Report", - "prescriptionDeliveryError": "This clinic doesn't support refill" + "prescriptionDeliveryError": "This clinic doesn't support refill", + "checkAvailability": "Check Availability", + "readInstructions": "Read Instructions" } \ No newline at end of file diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 6e1e92a..11c44ba 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -749,7 +749,6 @@ class ApiConsts { static final String sendActivationCode = 'Services/Authentication.svc/REST/SendActivationCodebyOTPNotificationType'; - static setBackendURLs() { if (isDevelopment) { baseUrl = "https://uat.hmgwebservices.com/"; diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index bd3e541..7d32262 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -67,6 +67,7 @@ class AppAssets { static const String rating_icon = '$svgBasePath/rating_icon.svg'; static const String doctor_calendar_icon = '$svgBasePath/doctor_calendar_icon.svg'; static const String prescription_remarks_icon = '$svgBasePath/prescription_remarks_icon.svg'; + static const String prescription_reminder_icon = '$svgBasePath/prescription_reminder_icon.svg'; //bottom navigation// diff --git a/lib/core/utils/calendar_utils.dart b/lib/core/utils/calendar_utils.dart index 39c7a14..8786f7f 100644 --- a/lib/core/utils/calendar_utils.dart +++ b/lib/core/utils/calendar_utils.dart @@ -4,8 +4,6 @@ import 'dart:io'; import 'dart:ui'; import 'package:device_calendar/device_calendar.dart'; -import 'package:easy_localization/easy_localization.dart'; -import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:manage_calendar_events/manage_calendar_events.dart' as ios; import 'package:timezone/data/latest.dart' as tzl; @@ -137,14 +135,12 @@ class CalendarUtils { print("catchError " + e.toString()); }).whenComplete(() { print("whenComplete Calender ID " + eventId!); - // Utils.showToast(LocaleKeys.appoReminderSuccess.tr()); }); } else { await _myPlugin.createEvent(calendarId: writableCalendars.id!, event: iosCalEvent).catchError((e) { print("catchError " + e.toString()); }).whenComplete(() { print("whenComplete Calender ID iOS " + eventId!); - // Utils.showToast(LocaleKeys.appoReminderSuccess.tr()); }); } } diff --git a/lib/features/prescriptions/models/resp_models/prescription_detail_response_model.dart b/lib/features/prescriptions/models/resp_models/prescription_detail_response_model.dart index 3ca8396..b35cfef 100644 --- a/lib/features/prescriptions/models/resp_models/prescription_detail_response_model.dart +++ b/lib/features/prescriptions/models/resp_models/prescription_detail_response_model.dart @@ -32,6 +32,7 @@ class PrescriptionDetailResponseModel { String? sKU; num? scaleOffset; String? startDate; + bool? hasReminder; PrescriptionDetailResponseModel( {this.address, @@ -66,7 +67,8 @@ class PrescriptionDetailResponseModel { this.route, this.sKU, this.scaleOffset, - this.startDate}); + this.startDate, + this.hasReminder = false}); PrescriptionDetailResponseModel.fromJson(Map json) { address = json['Address']; @@ -102,6 +104,7 @@ class PrescriptionDetailResponseModel { sKU = json['SKU']; scaleOffset = json['ScaleOffset']; startDate = json['StartDate']; + hasReminder = false; } Map toJson() { diff --git a/lib/features/prescriptions/prescriptions_view_model.dart b/lib/features/prescriptions/prescriptions_view_model.dart index 23a818e..ff93f84 100644 --- a/lib/features/prescriptions/prescriptions_view_model.dart +++ b/lib/features/prescriptions/prescriptions_view_model.dart @@ -43,6 +43,14 @@ class PrescriptionsViewModel extends ChangeNotifier { notifyListeners(); } + setPrescriptionItemReminder(bool value, PrescriptionDetailResponseModel item) { + int index = prescriptionDetailsList.indexOf(item); + if (index != -1) { + prescriptionDetailsList[index].hasReminder = value; + notifyListeners(); + } + } + setIsSortByClinic(bool value) { isSortByClinic = value; if (isSortByClinic) { diff --git a/lib/generated/locale_keys.g.dart b/lib/generated/locale_keys.g.dart index 54a1136..cd59c15 100644 --- a/lib/generated/locale_keys.g.dart +++ b/lib/generated/locale_keys.g.dart @@ -781,5 +781,7 @@ abstract class LocaleKeys { static const resultsAvailable = 'resultsAvailable'; static const viewReport = 'viewReport'; static const prescriptionDeliveryError = 'prescriptionDeliveryError'; + static const checkAvailability = 'checkAvailability'; + static const readInstructions = 'readInstructions'; } diff --git a/lib/presentation/prescriptions/prescription_detail_page.dart b/lib/presentation/prescriptions/prescription_detail_page.dart index d14da43..65dc550 100644 --- a/lib/presentation/prescriptions/prescription_detail_page.dart +++ b/lib/presentation/prescriptions/prescription_detail_page.dart @@ -29,6 +29,8 @@ class PrescriptionDetailPage extends StatefulWidget { class _PrescriptionDetailPageState extends State { late PrescriptionsViewModel prescriptionsViewModel; + bool _isSwitched = false; // Initial state of the switch + @override void initState() { scheduleMicrotask(() { @@ -46,266 +48,353 @@ class _PrescriptionDetailPageState extends State { title: LocaleKeys.prescriptions.tr(context: context).toText18(), backgroundColor: AppColors.bgScaffoldColor, ), - body: SingleChildScrollView( - child: Consumer(builder: (context, prescriptionVM, child) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - LocaleKeys.prescriptions.tr(context: context).toText24(isBold: true).paddingSymmetrical(24.h, 0.h), - SizedBox(height: 24.h), - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 20.h, - hasShadow: true, - ), - child: Padding( - padding: EdgeInsets.all(16.h), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Row( - mainAxisSize: MainAxisSize.min, - children: [ - Image.network( - widget.prescriptionsResponseModel.doctorImageURL!, - width: 24.h, - height: 24.h, - fit: BoxFit.fill, - ).circle(100), - SizedBox(width: 8.h), - Expanded(child: widget.prescriptionsResponseModel.doctorName!.toText16(isBold: true)), - ], - ), - SizedBox(height: 16.h), - Wrap( - direction: Axis.horizontal, - spacing: 6.h, - runSpacing: 6.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, - ), - ], - ), - 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, - ), - ], - ), - 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, - ), - ], - ), - 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, - ), - ], - ), - ], + body: Column( + children: [ + Expanded( + child: SingleChildScrollView( + child: Consumer(builder: (context, prescriptionVM, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.prescriptions.tr(context: context).toText24(isBold: true).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 24.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: true, ), - ], - ), - ), - ).paddingSymmetrical(24.h, 0.h), - SizedBox(height: 16.h), - ListView.builder( - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - itemCount: prescriptionVM.isPrescriptionsDetailsLoading ? 5 : prescriptionVM.prescriptionDetailsList.length, - itemBuilder: (context, index) { - return prescriptionVM.isPrescriptionsDetailsLoading - ? const MoviesShimmerWidget() - : AnimationConfiguration.staggeredList( - position: index, - duration: const Duration(milliseconds: 500), - child: SlideAnimation( - verticalOffset: 100.0, - child: FadeInAnimation( - child: AnimatedContainer( - duration: Duration(milliseconds: 300), - curve: Curves.easeInOut, - margin: EdgeInsets.symmetric(vertical: 8.h), - decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: true), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + Image.network( + widget.prescriptionsResponseModel.doctorImageURL!, + width: 24.h, + height: 24.h, + fit: BoxFit.fill, + ).circle(100), + SizedBox(width: 8.h), + Expanded(child: widget.prescriptionsResponseModel.doctorName!.toText16(isBold: true)), + ], + ), + SizedBox(height: 16.h), + Wrap( + direction: Axis.horizontal, + spacing: 6.h, + runSpacing: 6.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, + ), + ], + ), + 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, + ), + ], + ), + Row( + mainAxisSize: MainAxisSize.min, children: [ - Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox(height: 16.h), - Row( - mainAxisSize: MainAxisSize.min, - mainAxisAlignment: MainAxisAlignment.start, - children: [ - Image.network( - prescriptionVM.prescriptionDetailsList[index].imageThumbUrl!, - width: 60.h, - height: 60.h, - fit: BoxFit.fill, - ).circle(100), - SizedBox(width: 8.h), - Expanded( - child: prescriptionVM.prescriptionDetailsList[index].itemDescription!.toText16(isBold: true, maxlines: 2), - ), - ], - ).paddingSymmetrical(16.h, 0.h), - SizedBox(height: 16.h), - Wrap( - direction: Axis.horizontal, - spacing: 6.h, - runSpacing: 6.h, - children: [ - Row( - mainAxisSize: MainAxisSize.min, - children: [ - CustomButton( - text: "${LocaleKeys.route.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].route}", - 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, - ), - ], - ), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - CustomButton( - text: "${LocaleKeys.frequency.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].frequency}", - 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, - ), - ], - ), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - CustomButton( - text: "${LocaleKeys.dailyDoses.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].doseDailyQuantity}", - 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, - ), - ], - ), - Row( - mainAxisSize: MainAxisSize.min, - children: [ - CustomButton( - text: "${LocaleKeys.days.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].days}", - 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, - ), - ], - ), - ], - ).paddingSymmetrical(16.h, 0.h), - SizedBox(height: 8.h), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Utils.buildSvgWithAssets(icon: AppAssets.prescription_remarks_icon, width: 18.h, height: 18.h), - SizedBox(width: 9.h), - Expanded(child: "${LocaleKeys.remarks.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].remarks!}".toText10(isBold: true)), - ], - ).paddingSymmetrical(16.h, 0.h), - SizedBox(height: 16.h), - Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h), - SizedBox(height: 16.h), - ], - ) + 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, + ), ], ), - ), + 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, + ), + ], + ), + ], ), - ), - ); - }, - ).paddingSymmetrical(24.h, 0.h), - ], - ); - }), + ], + ), + ), + ).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 16.h), + ListView.builder( + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: prescriptionVM.isPrescriptionsDetailsLoading ? 5 : prescriptionVM.prescriptionDetailsList.length, + itemBuilder: (context, index) { + return prescriptionVM.isPrescriptionsDetailsLoading + ? const MoviesShimmerWidget() + : AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: AnimatedContainer( + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + margin: EdgeInsets.symmetric(vertical: 8.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: true), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 16.h), + Row( + mainAxisSize: MainAxisSize.min, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Image.network( + prescriptionVM.prescriptionDetailsList[index].imageThumbUrl!, + width: 60.h, + height: 60.h, + fit: BoxFit.fill, + ).circle(100), + SizedBox(width: 8.h), + Expanded( + child: prescriptionVM.prescriptionDetailsList[index].itemDescription!.toText16(isBold: true, maxlines: 2), + ), + ], + ).paddingSymmetrical(16.h, 0.h), + SizedBox(height: 16.h), + Wrap( + direction: Axis.horizontal, + spacing: 6.h, + runSpacing: 6.h, + children: [ + Row( + mainAxisSize: MainAxisSize.min, + children: [ + CustomButton( + text: "${LocaleKeys.route.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].route}", + 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, + ), + ], + ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + CustomButton( + text: "${LocaleKeys.frequency.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].frequency}", + 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, + ), + ], + ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + CustomButton( + text: "${LocaleKeys.dailyDoses.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].doseDailyQuantity}", + 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, + ), + ], + ), + Row( + mainAxisSize: MainAxisSize.min, + children: [ + CustomButton( + text: "${LocaleKeys.days.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].days}", + 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, + ), + ], + ), + ], + ).paddingSymmetrical(16.h, 0.h), + SizedBox(height: 8.h), + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.prescription_remarks_icon, width: 18.h, height: 18.h), + SizedBox(width: 9.h), + Expanded(child: "${LocaleKeys.remarks.tr(context: context)}: ${prescriptionVM.prescriptionDetailsList[index].remarks!}".toText10(isBold: true)), + ], + ).paddingSymmetrical(16.h, 0.h), + SizedBox(height: 14.h), + Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h), + SizedBox(height: 14.h), + 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, + children: [ + LocaleKeys.setReminder.tr(context: context).toText13(isBold: true), + "Notify me before the consumption time".toText10(color: AppColors.textColorLight), + ], + ), + SizedBox(width: 12.h), + Switch( + activeColor: AppColors.successColor, + value: prescriptionVM.prescriptionDetailsList[index].hasReminder!, + onChanged: (newValue) { + setState(() { + prescriptionVM.setPrescriptionItemReminder(newValue, prescriptionVM.prescriptionDetailsList[index]); + }); + }, + ), + ], + ).paddingSymmetrical(16.h, 0.h), + SizedBox(height: 14.h), + Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.05), height: 1.h), + Row( + children: [ + Expanded( + child: CustomButton( + text: LocaleKeys.checkAvailability.tr(context: context), + onPressed: () {}, + backgroundColor: AppColors.primaryRedColor.withOpacity(0.1), + borderColor: AppColors.primaryRedColor.withOpacity(0.0), + textColor: AppColors.primaryRedColor, + fontSize: 13, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + ), + ), + SizedBox(width: 16.h), + Expanded( + child: CustomButton( + text: LocaleKeys.readInstructions.tr(context: context), + onPressed: () {}, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 13, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + ), + ), + ], + ).paddingSymmetrical(16.h, 16.h), + ], + ) + ], + ), + ), + ), + ), + ); + }, + ).paddingSymmetrical(24.h, 0.h), + ], + ); + }), + ), + ), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: true, + ), + child: CustomButton( + text: widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported! ? LocaleKeys.resendOrder.tr(context: context) : LocaleKeys.prescriptionDeliveryError.tr(context: context), + onPressed: () {}, + backgroundColor: widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported! ? AppColors.successColor : AppColors.greyF7Color, + borderColor: AppColors.successColor.withOpacity(0.01), + textColor: widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported! ? AppColors.whiteColor : AppColors.textColor.withOpacity(0.35), + fontSize: 16, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 50.h, + icon: AppAssets.prescription_refill_icon, + iconColor: widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported! ? AppColors.whiteColor : AppColors.textColor.withOpacity(0.35), + iconSize: 20.h, + ).paddingSymmetrical(24.h, 24.h), + ), + ], ), ); } diff --git a/lib/presentation/prescriptions/prescriptions_list_page.dart b/lib/presentation/prescriptions/prescriptions_list_page.dart index 4aa1a1b..9574f28 100644 --- a/lib/presentation/prescriptions/prescriptions_list_page.dart +++ b/lib/presentation/prescriptions/prescriptions_list_page.dart @@ -196,7 +196,7 @@ class _PrescriptionsListPageState extends State { backgroundColor: AppColors.greyColor, borderColor: AppColors.greyColor, textColor: AppColors.blackColor, - fontSize: 12, + fontSize: 10, fontWeight: FontWeight.w500, borderRadius: 8, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), @@ -209,7 +209,7 @@ class _PrescriptionsListPageState extends State { backgroundColor: AppColors.greyColor, borderColor: AppColors.greyColor, textColor: AppColors.blackColor, - fontSize: 12, + fontSize: 10, fontWeight: FontWeight.w500, borderRadius: 8, padding: EdgeInsets.fromLTRB(10, 0, 10, 0), diff --git a/lib/theme/colors.dart b/lib/theme/colors.dart index 018727c..61938b0 100644 --- a/lib/theme/colors.dart +++ b/lib/theme/colors.dart @@ -29,6 +29,7 @@ class AppColors { static const Color bgRedLightColor = Color(0xFFFEE9EA); static const Color bgGreenColor = Color(0xFF18C273); static const Color textColor = Color(0xFF2E3039); + static const Color textColorLight = Color(0xFF5E5E5E); static const Color borderOnlyColor = Color(0xFF2E3039); static const Color dividerColor = Color(0xFFD2D2D2); static const Color warningColorYellow = Color(0xFFF4A308);