From 21b7b3201340e019814f0b5a6ece7941fc44dfaa Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Mon, 10 Nov 2025 16:40:55 +0300 Subject: [PATCH 1/5] Ask doctor implementation contd. --- .../my_appointments/my_appointments_repo.dart | 38 +++++++++++++++++++ .../my_appointments_view_model.dart | 18 +++++++++ .../appointment_details_page.dart | 4 +- .../appointment_payment_page.dart | 19 +++++----- .../widgets/appointment_card.dart | 10 ++++- .../appointment_checkin_bottom_sheet.dart | 8 ++-- .../widgets/appointment_doctor_card.dart | 4 +- 7 files changed, 85 insertions(+), 16 deletions(-) diff --git a/lib/features/my_appointments/my_appointments_repo.dart b/lib/features/my_appointments/my_appointments_repo.dart index 87451c2..32ce829 100644 --- a/lib/features/my_appointments/my_appointments_repo.dart +++ b/lib/features/my_appointments/my_appointments_repo.dart @@ -49,6 +49,8 @@ abstract class MyAppointmentsRepo { Future>> getTamaraInstallmentsDetails(); Future>> getActiveAppointmentsCount(); + + Future>> isDoctorAvailable({required int projectID, required int clinicID, required int doctorID}); } class MyAppointmentsRepoImp implements MyAppointmentsRepo { @@ -618,4 +620,40 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future> isDoctorAvailable({required int projectID, required int clinicID, required int doctorID}) async { + Map mapDevice = {"isDentalAllowedBackend": false, "DoctorID": doctorID, "ProjectID": projectID, "ClinicID": clinicID}; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + IS_DOCTOR_AVAILABLE_BY_CALENDAR_SCHEDULE, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final isDoctorAvailable = response['IsDoctorAvailable']; + + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: isDoctorAvailable, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } } diff --git a/lib/features/my_appointments/my_appointments_view_model.dart b/lib/features/my_appointments/my_appointments_view_model.dart index e8b2650..e8a5720 100644 --- a/lib/features/my_appointments/my_appointments_view_model.dart +++ b/lib/features/my_appointments/my_appointments_view_model.dart @@ -568,4 +568,22 @@ class MyAppointmentsViewModel extends ChangeNotifier { }, ); } + + Future isDoctorAvailable({required int projectID, required int doctorId, required int clinicId, Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await myAppointmentsRepo.isDoctorAvailable(projectID: projectID, doctorID: doctorId, clinicID: clinicId); + + result.fold( + (failure) async => await errorHandlerService.handleError(failure: failure), + (apiResponse) { + if (apiResponse.messageStatus == 2) { + onError!(apiResponse.errorMessage!); + } else if (apiResponse.messageStatus == 1) { + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse.data); + } + } + }, + ); + } } diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart index c772c80..7350fe4 100644 --- a/lib/presentation/appointments/appointment_details_page.dart +++ b/lib/presentation/appointments/appointment_details_page.dart @@ -81,7 +81,9 @@ class _AppointmentDetailsPageState extends State { children: [ AppointmentDoctorCard( patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, - onAskDoctorTap: () {}, + onAskDoctorTap: () { + print("Ask Doctor"); + }, onCancelTap: () async { myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); LoaderBottomSheet.showLoader(loadingText: "Cancelling Appointment, Please Wait...".needTranslation); diff --git a/lib/presentation/appointments/appointment_payment_page.dart b/lib/presentation/appointments/appointment_payment_page.dart index e259233..d4e579b 100644 --- a/lib/presentation/appointments/appointment_payment_page.dart +++ b/lib/presentation/appointments/appointment_payment_page.dart @@ -59,19 +59,20 @@ class _AppointmentPaymentPageState extends State { void initState() { scheduleMicrotask(() { payfortViewModel.initPayfortViewModel(); - myAppointmentsViewModel.getTamaraInstallmentsDetails().then((val) { - if (myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax! >= myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel!.minLimit!.amount! && - myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax! <= myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel!.maxLimit!.amount!) { - setState(() { - isShowTamara = true; - }); - } - }); payfortViewModel.setIsApplePayConfigurationLoading(false); myAppointmentsViewModel.getPatientShareAppointment( widget.patientAppointmentHistoryResponseModel.projectID, widget.patientAppointmentHistoryResponseModel.clinicID, - widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false, onError: (err) { + widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(), widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false, onSuccess: (val) { + myAppointmentsViewModel.getTamaraInstallmentsDetails().then((val) { + if (myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax! >= myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel!.minLimit!.amount! && + myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax! <= myAppointmentsViewModel.getTamaraInstallmentsDetailsResponseModel!.maxLimit!.amount!) { + setState(() { + isShowTamara = true; + }); + } + }); + }, onError: (err) { Navigator.of(context).pop(); Navigator.of(context).pop(); }); diff --git a/lib/presentation/appointments/widgets/appointment_card.dart b/lib/presentation/appointments/widgets/appointment_card.dart index 627d527..25ecb4f 100644 --- a/lib/presentation/appointments/widgets/appointment_card.dart +++ b/lib/presentation/appointments/widgets/appointment_card.dart @@ -244,7 +244,15 @@ class AppointmentCard extends StatelessWidget { if (isRecent) { return CustomButton( text: LocaleKeys.askDoctor.tr(context: context), - onPressed: () {}, + onPressed: () async { + await myAppointmentsViewModel.isDoctorAvailable( + projectID: patientAppointmentHistoryResponseModel.projectID, + doctorId: patientAppointmentHistoryResponseModel.doctorID, + clinicId: patientAppointmentHistoryResponseModel.clinicID, + onSuccess: (value) { + if (value) {} + }); + }, backgroundColor: AppColors.secondaryLightRedColor, borderColor: AppColors.secondaryLightRedColor, textColor: AppColors.primaryRedColor, diff --git a/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart b/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart index 1863875..74ab6b7 100644 --- a/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart +++ b/lib/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart @@ -18,6 +18,7 @@ import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:barcode_scan2/barcode_scan2.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:hmg_patient_app_new/widgets/nfc/nfc_reader_sheet.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; @@ -139,14 +140,13 @@ class AppointmentCheckinBottomSheet extends StatelessWidget { } void sendCheckInRequest(String scannedCode, BuildContext context) async { - showCommonBottomSheet(context, - child: Utils.getLoadingWidget(), callBackFunc: (str) {}, title: "", height: ResponsiveExtension.screenHeight * 0.3, isCloseButtonVisible: false, isDismissible: false, isFullScreen: false); + LoaderBottomSheet.showLoader(loadingText: "Processing Check-In...".needTranslation); await myAppointmentsViewModel.sendCheckInNfcRequest( patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel, scannedCode: scannedCode, checkInType: 2, onSuccess: (apiResponse) { - Navigator.of(context).pop(); + LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight(context, title: "Success".needTranslation, child: Utils.getSuccessWidget(loadingText: LocaleKeys.success.tr()), callBackFunc: () { Navigator.of(context).pop(); Navigator.pushAndRemoveUntil( @@ -161,7 +161,7 @@ class AppointmentCheckinBottomSheet extends StatelessWidget { }, isFullScreen: false); }, onError: (error) { - Navigator.of(context).pop(); + LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight(context, title: "Error".needTranslation, child: Utils.getErrorWidget(loadingText: error), callBackFunc: () { Navigator.of(context).pop(); }, isFullScreen: false); diff --git a/lib/presentation/appointments/widgets/appointment_doctor_card.dart b/lib/presentation/appointments/widgets/appointment_doctor_card.dart index 2ea75d1..02c905a 100644 --- a/lib/presentation/appointments/widgets/appointment_doctor_card.dart +++ b/lib/presentation/appointments/widgets/appointment_doctor_card.dart @@ -112,7 +112,9 @@ class AppointmentDoctorCard extends StatelessWidget { return DateTime.now().difference(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate)).inDays <= 15 ? CustomButton( text: LocaleKeys.askDoctor.tr(), - onPressed: () {}, + onPressed: () { + onAskDoctorTap(); + }, backgroundColor: AppColors.secondaryLightRedColor, borderColor: AppColors.secondaryLightRedColor, textColor: AppColors.primaryRedColor, From c600b0ee7a97dd87fbbc81f301dd1118ff382512 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Wed, 12 Nov 2025 13:01:42 +0300 Subject: [PATCH 2/5] Ask doctor implemented --- ...sk_doctor_request_type_response_model.dart | 92 ++++++++++++ .../my_appointments/my_appointments_repo.dart | 89 +++++++++++ .../my_appointments_view_model.dart | 52 +++++++ .../appointment_details_page.dart | 31 +++- .../widgets/appointment_card.dart | 25 +++- .../ask_doctor_request_type_select.dart | 138 ++++++++++++++++++ 6 files changed, 423 insertions(+), 4 deletions(-) create mode 100644 lib/features/my_appointments/models/resp_models/ask_doctor_request_type_response_model.dart create mode 100644 lib/presentation/appointments/widgets/ask_doctor_request_type_select.dart diff --git a/lib/features/my_appointments/models/resp_models/ask_doctor_request_type_response_model.dart b/lib/features/my_appointments/models/resp_models/ask_doctor_request_type_response_model.dart new file mode 100644 index 0000000..3b53214 --- /dev/null +++ b/lib/features/my_appointments/models/resp_models/ask_doctor_request_type_response_model.dart @@ -0,0 +1,92 @@ +class AskDocRequestType { + String? setupID; + int? parameterGroup; + int? parameterType; + int? parameterCode; + String? description; + String? descriptionN; + String? alias; + String? aliasN; + String? prefix; + String? suffix; + String? isColorCodingRequired; + String? backColor; + String? foreColor; + bool? isBuiltIn; + bool? isActive; + int? createdBy; + String? createdOn; + String? editedBy; + String? editedOn; + String? rowVer; + + AskDocRequestType( + {this.setupID, + this.parameterGroup, + this.parameterType, + this.parameterCode, + this.description, + this.descriptionN, + this.alias, + this.aliasN, + this.prefix, + this.suffix, + this.isColorCodingRequired, + this.backColor, + this.foreColor, + this.isBuiltIn, + this.isActive, + this.createdBy, + this.createdOn, + this.editedBy, + this.editedOn, + this.rowVer}); + + AskDocRequestType.fromJson(Map json) { + setupID = json['SetupID']; + parameterGroup = json['ParameterGroup']; + parameterType = json['ParameterType']; + parameterCode = json['ParameterCode']; + description = json['Description']; + descriptionN = json['DescriptionN']; + alias = json['Alias']; + aliasN = json['AliasN']; + prefix = json['Prefix']; + suffix = json['Suffix']; + isColorCodingRequired = json['IsColorCodingRequired']; + backColor = json['BackColor']; + foreColor = json['ForeColor']; + isBuiltIn = json['IsBuiltIn']; + isActive = json['IsActive']; + createdBy = json['CreatedBy']; + createdOn = json['CreatedOn']; + editedBy = json['EditedBy']; + editedOn = json['EditedOn']; + rowVer = json['RowVer']; + } + + Map toJson() { + final Map data = new Map(); + data['SetupID'] = this.setupID; + data['ParameterGroup'] = this.parameterGroup; + data['ParameterType'] = this.parameterType; + data['ParameterCode'] = this.parameterCode; + data['Description'] = this.description; + data['DescriptionN'] = this.descriptionN; + data['Alias'] = this.alias; + data['AliasN'] = this.aliasN; + data['Prefix'] = this.prefix; + data['Suffix'] = this.suffix; + data['IsColorCodingRequired'] = this.isColorCodingRequired; + data['BackColor'] = this.backColor; + data['ForeColor'] = this.foreColor; + data['IsBuiltIn'] = this.isBuiltIn; + data['IsActive'] = this.isActive; + data['CreatedBy'] = this.createdBy; + data['CreatedOn'] = this.createdOn; + data['EditedBy'] = this.editedBy; + data['EditedOn'] = this.editedOn; + data['RowVer'] = this.rowVer; + return data; + } +} diff --git a/lib/features/my_appointments/my_appointments_repo.dart b/lib/features/my_appointments/my_appointments_repo.dart index 32ce829..5fd7c12 100644 --- a/lib/features/my_appointments/my_appointments_repo.dart +++ b/lib/features/my_appointments/my_appointments_repo.dart @@ -6,7 +6,9 @@ import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/cache_consts.dart'; import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/ask_doctor_request_type_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/get_tamara_installments_details_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart' show HospitalsModel; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; @@ -50,7 +52,12 @@ abstract class MyAppointmentsRepo { Future>> getActiveAppointmentsCount(); + Future>>> getAskDoctorRequestTypes(); + Future>> isDoctorAvailable({required int projectID, required int clinicID, required int doctorID}); + + Future>> sendAskDocCallRequest( + {required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, required String requestType, required String remarks, required String userMobileNumber}); } class MyAppointmentsRepoImp implements MyAppointmentsRepo { @@ -656,4 +663,86 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future>>> getAskDoctorRequestTypes() async { + Map mapDevice = {}; + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post(GET_CALL_REQUEST_TYPE, onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['ListReqTypes']; + + final askDoctorRequestTypesList = list.map((item) => AskDocRequestType.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: askDoctorRequestTypesList, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, body: mapDevice); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } + + @override + Future> sendAskDocCallRequest( + {required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, required String requestType, required String remarks, required String userMobileNumber}) async { + Map body = {}; + + body['ProjectID'] = patientAppointmentHistoryResponseModel.projectID; + body['SetupID'] = patientAppointmentHistoryResponseModel.setupID; + body['DoctorID'] = patientAppointmentHistoryResponseModel.doctorID; + body['PatientMobileNumber'] = userMobileNumber; + body['IsMessageSent'] = false; + body['RequestDate'] = DateUtil.convertDateToString(DateTime.now()); + body['RequestTime'] = DateUtil.convertDateToString(DateTime.now()); + body['Remarks'] = remarks; + body['Status'] = 2; // 4 for testing only.."cancelled status insert" else should be changed to 1 in live version + body['CreatedBy'] = 102; + body['CreatedOn'] = DateUtil.convertDateToString(DateTime.now()); + body['EditedBy'] = 102; + body['EditedOn'] = DateUtil.convertDateToString(DateTime.now()); + body['isDentalAllowedBackend'] = false; + body['AppointmentNo'] = patientAppointmentHistoryResponseModel.appointmentNo; + body['ClinicID'] = patientAppointmentHistoryResponseModel.clinicID; + body['QuestionType'] = num.parse(requestType); + body['RequestType'] = num.parse(requestType); + body['RequestTypeID'] = num.parse(requestType); + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post(INSERT_CALL_INFO, onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: true, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, body: body); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } } diff --git a/lib/features/my_appointments/my_appointments_view_model.dart b/lib/features/my_appointments/my_appointments_view_model.dart index e8a5720..371f838 100644 --- a/lib/features/my_appointments/my_appointments_view_model.dart +++ b/lib/features/my_appointments/my_appointments_view_model.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/appointemnet_filters.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/ask_doctor_request_type_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/get_tamara_installments_details_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_share_response_model.dart'; @@ -42,6 +43,8 @@ class MyAppointmentsViewModel extends ChangeNotifier { List patientMyDoctorsList = []; + List askDoctorRequestTypeList = []; + PatientAppointmentShareResponseModel? patientAppointmentShareResponseModel; GetTamaraInstallmentsDetailsResponseModel? getTamaraInstallmentsDetailsResponseModel; @@ -586,4 +589,53 @@ class MyAppointmentsViewModel extends ChangeNotifier { }, ); } + + Future getAskDoctorRequestTypes({Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await myAppointmentsRepo.getAskDoctorRequestTypes(); + + result.fold( + (failure) async => await errorHandlerService.handleError(failure: failure), + (apiResponse) { + if (apiResponse.messageStatus == 2) { + onError!(apiResponse.errorMessage!); + } else if (apiResponse.messageStatus == 1) { + askDoctorRequestTypeList = apiResponse.data!; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse.data); + } + } + }, + ); + } + + Future sendAskDocCallRequest({ + required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, + required String requestType, + required String remarks, + required String userMobileNumber, + Function(dynamic)? onSuccess, + Function(String)? onError, + }) async { + final result = await myAppointmentsRepo.sendAskDocCallRequest( + patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel, requestType: requestType, remarks: remarks, userMobileNumber: userMobileNumber); + + result.fold( + // (failure) async => await errorHandlerService.handleError(failure: failure), + (failure) async { + await errorHandlerService.handleError(failure: failure); + if (onError != null) onError(failure.message); + }, + (apiResponse) { + if (apiResponse.messageStatus == 2) { + onError!(apiResponse.errorMessage!); + } else if (apiResponse.messageStatus == 1) { + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse.data); + } + } + }, + ); + } } diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart index 7350fe4..79084c9 100644 --- a/lib/presentation/appointments/appointment_details_page.dart +++ b/lib/presentation/appointments/appointment_details_page.dart @@ -20,6 +20,7 @@ import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/appointments/appointment_payment_page.dart'; import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart'; import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_doctor_card.dart'; +import 'package:hmg_patient_app_new/presentation/appointments/widgets/ask_doctor_request_type_select.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/appointment_calendar.dart'; import 'package:hmg_patient_app_new/presentation/prescriptions/prescription_detail_page.dart'; import 'package:hmg_patient_app_new/presentation/prescriptions/prescriptions_list_page.dart'; @@ -81,8 +82,34 @@ class _AppointmentDetailsPageState extends State { children: [ AppointmentDoctorCard( patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, - onAskDoctorTap: () { - print("Ask Doctor"); + onAskDoctorTap: () async { + LoaderBottomSheet.showLoader(loadingText: "Checking doctor availability...".needTranslation); + await myAppointmentsViewModel.isDoctorAvailable( + projectID: widget.patientAppointmentHistoryResponseModel.projectID, + doctorId: widget.patientAppointmentHistoryResponseModel.doctorID, + clinicId: widget.patientAppointmentHistoryResponseModel.clinicID, + onSuccess: (value) async { + if (value) { + print("Doctor is available"); + await myAppointmentsViewModel.getAskDoctorRequestTypes(onSuccess: (val) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + title: LocaleKeys.askDoctor.tr(context: context), + child: AskDoctorRequestTypeSelect( + askDoctorRequestTypeList: myAppointmentsViewModel.askDoctorRequestTypeList, + myAppointmentsViewModel: myAppointmentsViewModel, + patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel, + ), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + } else { + print("Doctor is not available"); + } + }); }, onCancelTap: () async { myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true); diff --git a/lib/presentation/appointments/widgets/appointment_card.dart b/lib/presentation/appointments/widgets/appointment_card.dart index 25ecb4f..f20d0b6 100644 --- a/lib/presentation/appointments/widgets/appointment_card.dart +++ b/lib/presentation/appointments/widgets/appointment_card.dart @@ -16,6 +16,7 @@ import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_vie import 'package:hmg_patient_app_new/features/my_appointments/utils/appointment_type.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/appointments/appointment_details_page.dart'; +import 'package:hmg_patient_app_new/presentation/appointments/widgets/ask_doctor_request_type_select.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/appointment_calendar.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; @@ -245,12 +246,32 @@ class AppointmentCard extends StatelessWidget { return CustomButton( text: LocaleKeys.askDoctor.tr(context: context), onPressed: () async { + LoaderBottomSheet.showLoader(loadingText: "Checking doctor availability...".needTranslation); await myAppointmentsViewModel.isDoctorAvailable( projectID: patientAppointmentHistoryResponseModel.projectID, doctorId: patientAppointmentHistoryResponseModel.doctorID, clinicId: patientAppointmentHistoryResponseModel.clinicID, - onSuccess: (value) { - if (value) {} + onSuccess: (value) async { + if (value) { + print("Doctor is available"); + await myAppointmentsViewModel.getAskDoctorRequestTypes(onSuccess: (val) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + title: LocaleKeys.askDoctor.tr(context: context), + child: AskDoctorRequestTypeSelect( + askDoctorRequestTypeList: myAppointmentsViewModel.askDoctorRequestTypeList, + myAppointmentsViewModel: myAppointmentsViewModel, + patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel, + ), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + } else { + print("Doctor is not available"); + } }); }, backgroundColor: AppColors.secondaryLightRedColor, diff --git a/lib/presentation/appointments/widgets/ask_doctor_request_type_select.dart b/lib/presentation/appointments/widgets/ask_doctor_request_type_select.dart new file mode 100644 index 0000000..9f06a4d --- /dev/null +++ b/lib/presentation/appointments/widgets/ask_doctor_request_type_select.dart @@ -0,0 +1,138 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/ask_doctor_request_type_response_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; + +class AskDoctorRequestTypeSelect extends StatelessWidget { + AskDoctorRequestTypeSelect({super.key, required this.askDoctorRequestTypeList, required this.myAppointmentsViewModel, required this.patientAppointmentHistoryResponseModel}); + + final MyAppointmentsViewModel myAppointmentsViewModel; + final PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel; + List askDoctorRequestTypeList = []; + int selectedParameterCodeValue = 2; + int selectedParameterCode = 0; + + final ValueNotifier requestTypeSelectNotifier = ValueNotifier(0); + + @override + Widget build(BuildContext context) { + return Column( + children: [ + Container( + width: double.infinity, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24), + child: ListView.builder( + itemCount: askDoctorRequestTypeList.length, + physics: NeverScrollableScrollPhysics(), + padding: EdgeInsets.only(top: 8, bottom: 8), + shrinkWrap: true, + itemBuilder: (context, index) { + return ValueListenableBuilder( + valueListenable: requestTypeSelectNotifier, + builder: (context, duration, child) { + return Theme( + data: Theme.of(context).copyWith( + listTileTheme: ListTileThemeData(horizontalTitleGap: 4), + ), + child: RadioListTile( + title: (askDoctorRequestTypeList[index].description ?? '').toText14(weight: FontWeight.w500), + value: index, + fillColor: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.selected)) { + return AppColors.primaryRedColor; + } + return Color(0xffEEEEEE); + }), + contentPadding: EdgeInsets.only(left: 12.h, right: 12.h), + groupValue: selectedParameterCode, + onChanged: (int? newValue) { + selectedParameterCode = newValue!; + selectedParameterCodeValue = askDoctorRequestTypeList[index].parameterCode!; + requestTypeSelectNotifier.value = selectedParameterCode; + debugPrint(selectedParameterCodeValue.toString()); + }, + ), + ); + }, + ); + }, + ), + ), + SizedBox(height: 16.h), + Row( + children: [ + Expanded( + child: CustomButton( + text: LocaleKeys.cancel.tr(), + onPressed: () { + Navigator.pop(context); + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + icon: AppAssets.cancel, + iconColor: AppColors.whiteColor, + borderRadius: 12.r, + iconSize: 14.h, + fontSize: 14.f, + height: 40.h, + ), + ), + SizedBox(width: 8.h), + Expanded( + child: CustomButton( + text: LocaleKeys.confirm.tr(), + onPressed: () async { + Navigator.pop(context); + LoaderBottomSheet.showLoader(loadingText: "Sending Request..."); + await myAppointmentsViewModel.sendAskDocCallRequest( + patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel, + requestType: selectedParameterCodeValue.toString(), + remarks: "", + userMobileNumber: myAppointmentsViewModel.appState.getAuthenticatedUser()!.mobileNumber!, + onSuccess: (val) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getSuccessWidget(loadingText: "Request has been sent successfully, you will be contacted soon.".needTranslation), + callBackFunc: () { + Navigator.of(context).pop(); + }, + title: "", + isCloseButtonVisible: true, + isDismissible: false, + isFullScreen: false, + ); + }, + onError: (errMessage) { + LoaderBottomSheet.hideLoader(); + }); + }, + backgroundColor: AppColors.bgGreenColor, + borderColor: AppColors.bgGreenColor, + textColor: Colors.white, + icon: AppAssets.confirm, + iconSize: 14.h, + borderRadius: 12.r, + fontSize: 14.f, + height: 40.h, + ), + ), + ], + ) + ], + ); + } +} From 14f57027f4a95fbb6d3d425fb6ad82f2176e6bfd Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Wed, 12 Nov 2025 15:29:31 +0300 Subject: [PATCH 3/5] Eye measurements done --- lib/core/api/api_client.dart | 4 +- ...nt_appointment_history_response_model.dart | 216 +++++++++++++++++- .../my_appointments/my_appointments_repo.dart | 5 +- .../my_appointments_view_model.dart | 41 ++++ .../widgets/appointment_card.dart | 71 +++--- .../eye_measurement_details_page.dart | 198 ++++++++++++++++ .../eye_measurements_appointments_page.dart | 90 ++++++++ .../medical_file/medical_file_page.dart | 12 +- 8 files changed, 596 insertions(+), 41 deletions(-) create mode 100644 lib/presentation/medical_file/eye_measurement_details_page.dart create mode 100644 lib/presentation/medical_file/eye_measurements_appointments_page.dart diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index 029dbcb..b2cc819 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -173,8 +173,8 @@ class ApiClientImp implements ApiClient { body[_appState.isAuthenticated ? 'TokenID' : 'LogInTokenID'] = _appState.appAuthToken; } - // body['TokenID'] = "@dm!n"; - // body['PatientID'] = 3111528; + body['TokenID'] = "@dm!n"; + body['PatientID'] = 1231755; // body['PatientTypeID'] = 1; // // body['PatientOutSA'] = 0; diff --git a/lib/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart b/lib/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart index 4d8eb9c..e1ae67d 100644 --- a/lib/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart +++ b/lib/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart @@ -55,8 +55,8 @@ class PatientAppointmentHistoryResponseModel { bool? isMedicalReportRequested; bool? isOnlineCheckedIN; String? latitude; - dynamic listHISGetContactLensPerscription; - dynamic listHISGetGlassPerscription; + List? listHISGetContactLensPrescription; + List? listHISGetGlassPrescription; String? longitude; dynamic nextAction; dynamic noOfPatientsRate; @@ -130,8 +130,8 @@ class PatientAppointmentHistoryResponseModel { this.isMedicalReportRequested, this.isOnlineCheckedIN, this.latitude, - this.listHISGetContactLensPerscription, - this.listHISGetGlassPerscription, + this.listHISGetContactLensPrescription, + this.listHISGetGlassPrescription, this.longitude, this.nextAction, this.noOfPatientsRate, @@ -207,8 +207,18 @@ class PatientAppointmentHistoryResponseModel { isMedicalReportRequested = json['IsMedicalReportRequested']; isOnlineCheckedIN = json['IsOnlineCheckedIN']; latitude = json['Latitude']; - listHISGetContactLensPerscription = json['List_HIS_GetContactLensPerscription']; - listHISGetGlassPerscription = json['List_HIS_GetGlassPerscription']; + if (json['List_HIS_GetContactLensPerscription'] != null) { + listHISGetContactLensPrescription = []; + json['List_HIS_GetContactLensPerscription'].forEach((v) { + listHISGetContactLensPrescription!.add(ListHISGetContactLensPrescription.fromJson(v)); + }); + } + if (json['List_HIS_GetGlassPerscription'] != null) { + listHISGetGlassPrescription = []; + json['List_HIS_GetGlassPerscription'].forEach((v) { + listHISGetGlassPrescription!.add(ListHISGetGlassPrescription.fromJson(v)); + }); + } longitude = json['Longitude']; nextAction = json['NextAction']; noOfPatientsRate = json['NoOfPatientsRate']; @@ -285,8 +295,8 @@ class PatientAppointmentHistoryResponseModel { data['IsMedicalReportRequested'] = this.isMedicalReportRequested; data['IsOnlineCheckedIN'] = this.isOnlineCheckedIN; data['Latitude'] = this.latitude; - data['List_HIS_GetContactLensPerscription'] = this.listHISGetContactLensPerscription; - data['List_HIS_GetGlassPerscription'] = this.listHISGetGlassPerscription; + // data['List_HIS_GetContactLensPerscription'] = this.listHISGetContactLensPerscription; + // data['List_HIS_GetGlassPerscription'] = this.listHISGetGlassPerscription; data['Longitude'] = this.longitude; data['NextAction'] = this.nextAction; data['NoOfPatientsRate'] = this.noOfPatientsRate; @@ -301,6 +311,196 @@ class PatientAppointmentHistoryResponseModel { } } +class ListHISGetContactLensPrescription { + String? setupId; + int? projectId; + int? patientType; + int? patientId; + int? encounterType; + int? encounterNo; + int? oDOS; + dynamic brand; + dynamic baseCurve; + dynamic power; + dynamic diameter; + dynamic oZ; + dynamic cT; + dynamic blend; + String? remarks; + int? status; + bool? isActive; + String? createdOn; + + ListHISGetContactLensPrescription( + {this.setupId, + this.projectId, + this.patientType, + this.patientId, + this.encounterType, + this.encounterNo, + this.oDOS, + this.brand, + this.baseCurve, + this.power, + this.diameter, + this.oZ, + this.cT, + this.blend, + this.remarks, + this.status, + this.isActive, + this.createdOn}); + + ListHISGetContactLensPrescription.fromJson(Map json) { + setupId = json['SetupId']; + projectId = json['ProjectId']; + patientType = json['PatientType']; + patientId = json['PatientId']; + encounterType = json['EncounterType']; + encounterNo = json['EncounterNo']; + oDOS = json['OD_OS']; + brand = json['Brand']; + baseCurve = json['BaseCurve']; + power = json['Power']; + diameter = json['Diameter']; + oZ = json['OZ']; + cT = json['CT']; + blend = json['Blend']; + remarks = json['Remarks']; + status = json['Status']; + isActive = json['IsActive']; + createdOn = json['CreatedOn']; + } + + Map toJson() { + final Map data = new Map(); + data['SetupId'] = this.setupId; + data['ProjectId'] = this.projectId; + data['PatientType'] = this.patientType; + data['PatientId'] = this.patientId; + data['EncounterType'] = this.encounterType; + data['EncounterNo'] = this.encounterNo; + data['OD_OS'] = this.oDOS; + data['Brand'] = this.brand; + data['BaseCurve'] = this.baseCurve; + data['Power'] = this.power; + data['Diameter'] = this.diameter; + data['OZ'] = this.oZ; + data['CT'] = this.cT; + data['Blend'] = this.blend; + data['Remarks'] = this.remarks; + data['Status'] = this.status; + data['IsActive'] = this.isActive; + data['CreatedOn'] = this.createdOn; + return data; + } +} + +class ListHISGetGlassPrescription { + dynamic projectID; + String? setupID; + dynamic patientId; + dynamic encounterType; + dynamic encounterNo; + String? visionType; + double? rightEyeSpherical; + dynamic rightEyeCylinder; + dynamic rightEyeAxis; + dynamic rightEyePrism; + dynamic rightEyeVA; + String? rightEyeRemarks; + dynamic leftEyeSpherical; + dynamic leftEyeCylinder; + dynamic leftEyeAxis; + dynamic leftEyePrism; + dynamic leftEyeVA; + String? leftEyeRemarks; + dynamic pD; + dynamic bVD; + dynamic status; + bool? isActive; + String? createdOn; + + ListHISGetGlassPrescription( + {this.projectID, + this.setupID, + this.patientId, + this.encounterType, + this.encounterNo, + this.visionType, + this.rightEyeSpherical, + this.rightEyeCylinder, + this.rightEyeAxis, + this.rightEyePrism, + this.rightEyeVA, + this.rightEyeRemarks, + this.leftEyeSpherical, + this.leftEyeCylinder, + this.leftEyeAxis, + this.leftEyePrism, + this.leftEyeVA, + this.leftEyeRemarks, + this.pD, + this.bVD, + this.status, + this.isActive, + this.createdOn}); + + ListHISGetGlassPrescription.fromJson(Map json) { + projectID = json['ProjectID']; + setupID = json['SetupID']; + patientId = json['PatientId']; + encounterType = json['EncounterType']; + encounterNo = json['EncounterNo']; + visionType = json['VisionType']; + rightEyeSpherical = json['RightEyeSpherical']; + rightEyeCylinder = json['RightEyeCylinder']; + rightEyeAxis = json['RightEyeAxis']; + rightEyePrism = json['RightEyePrism']; + rightEyeVA = json['RightEyeVA']; + rightEyeRemarks = json['RightEyeRemarks']; + leftEyeSpherical = json['LeftEyeSpherical']; + leftEyeCylinder = json['LeftEyeCylinder']; + leftEyeAxis = json['LeftEyeAxis']; + leftEyePrism = json['LeftEyePrism']; + leftEyeVA = json['LeftEyeVA']; + leftEyeRemarks = json['LeftEyeRemarks']; + pD = json['PD']; + bVD = json['BVD']; + status = json['Status']; + isActive = json['IsActive']; + createdOn = json['CreatedOn']; + } + + Map toJson() { + final Map data = new Map(); + data['ProjectID'] = this.projectID; + data['SetupID'] = this.setupID; + data['PatientId'] = this.patientId; + data['EncounterType'] = this.encounterType; + data['EncounterNo'] = this.encounterNo; + data['VisionType'] = this.visionType; + data['RightEyeSpherical'] = this.rightEyeSpherical; + data['RightEyeCylinder'] = this.rightEyeCylinder; + data['RightEyeAxis'] = this.rightEyeAxis; + data['RightEyePrism'] = this.rightEyePrism; + data['RightEyeVA'] = this.rightEyeVA; + data['RightEyeRemarks'] = this.rightEyeRemarks; + data['LeftEyeSpherical'] = this.leftEyeSpherical; + data['LeftEyeCylinder'] = this.leftEyeCylinder; + data['LeftEyeAxis'] = this.leftEyeAxis; + data['LeftEyePrism'] = this.leftEyePrism; + data['LeftEyeVA'] = this.leftEyeVA; + data['LeftEyeRemarks'] = this.leftEyeRemarks; + data['PD'] = this.pD; + data['BVD'] = this.bVD; + data['Status'] = this.status; + data['IsActive'] = this.isActive; + data['CreatedOn'] = this.createdOn; + return data; + } +} + class PatientAppointmentList { String? filterName = ""; List? patientDoctorAppointmentList = []; diff --git a/lib/features/my_appointments/my_appointments_repo.dart b/lib/features/my_appointments/my_appointments_repo.dart index 5fd7c12..87ec10d 100644 --- a/lib/features/my_appointments/my_appointments_repo.dart +++ b/lib/features/my_appointments/my_appointments_repo.dart @@ -16,7 +16,7 @@ import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/ import 'package:hmg_patient_app_new/services/logger_service.dart'; abstract class MyAppointmentsRepo { - Future>>> getPatientAppointments({required bool isActiveAppointment, required bool isArrivedAppointments}); + Future>>> getPatientAppointments({required bool isActiveAppointment, required bool isArrivedAppointments, bool isForEyeMeasurement = false}); Future>> getPatientShareAppointment( {required int projectID, required int clinicID, required String appointmentNo, required bool isLiveCareAppointment}); @@ -67,13 +67,14 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo { MyAppointmentsRepoImp({required this.loggerService, required this.apiClient}); @override - Future>>> getPatientAppointments({required bool isActiveAppointment, required bool isArrivedAppointments}) async { + Future>>> getPatientAppointments({required bool isActiveAppointment, required bool isArrivedAppointments, bool isForEyeMeasurement = false}) async { Map mapDevice = { "IsActiveAppointment": isActiveAppointment, "IsComingFromCOC": false, "isForUpcomming": false, "IsForMedicalReport": false, "IsForArrived": isArrivedAppointments, + "IsIrisPrescription": isForEyeMeasurement }; try { diff --git a/lib/features/my_appointments/my_appointments_view_model.dart b/lib/features/my_appointments/my_appointments_view_model.dart index 371f838..fa1ad22 100644 --- a/lib/features/my_appointments/my_appointments_view_model.dart +++ b/lib/features/my_appointments/my_appointments_view_model.dart @@ -14,6 +14,8 @@ class MyAppointmentsViewModel extends ChangeNotifier { int selectedTabIndex = 0; int previouslySelectedTab = -1; + int eyeMeasurementsTabSelectedIndex = 0; + MyAppointmentsRepo myAppointmentsRepo; ErrorHandlerService errorHandlerService; AppState appState; @@ -27,6 +29,8 @@ class MyAppointmentsViewModel extends ChangeNotifier { bool isAppointmentDataToBeLoaded = true; + bool isEyeMeasurementsAppointmentsLoading = false; + List availableFilters = []; List? selectedFilter = []; bool isDateFilterSelected = false; @@ -43,6 +47,8 @@ class MyAppointmentsViewModel extends ChangeNotifier { List patientMyDoctorsList = []; + List patientEyeMeasurementsAppointmentsHistoryList = []; + List askDoctorRequestTypeList = []; PatientAppointmentShareResponseModel? patientAppointmentShareResponseModel; @@ -60,18 +66,25 @@ class MyAppointmentsViewModel extends ChangeNotifier { notifyListeners(); } + void onEyeMeasurementsTabChanged(int index) { + eyeMeasurementsTabSelectedIndex = index; + notifyListeners(); + } + initAppointmentsViewModel() { if (isAppointmentDataToBeLoaded) { patientAppointmentsHistoryList.clear(); patientUpcomingAppointmentsHistoryList.clear(); patientArrivedAppointmentsHistoryList.clear(); patientTimelineAppointmentsList.clear(); + patientEyeMeasurementsAppointmentsHistoryList.clear(); isMyAppointmentsLoading = true; isTimeLineAppointmentsLoading = true; patientMyDoctorsList.clear(); } isTamaraDetailsLoading = true; isAppointmentPatientShareLoading = true; + isEyeMeasurementsAppointmentsLoading = true; notifyListeners(); } @@ -108,6 +121,11 @@ class MyAppointmentsViewModel extends ChangeNotifier { notifyListeners(); } + setIsEyeMeasurementsAppointmentsLoading(bool val) { + isEyeMeasurementsAppointmentsLoading = val; + notifyListeners(); + } + setAppointmentReminder(bool value, PatientAppointmentHistoryResponseModel item) { int index = patientAppointmentsHistoryList.indexOf(item); if (index != -1) { @@ -116,6 +134,29 @@ class MyAppointmentsViewModel extends ChangeNotifier { } } + Future getPatientEyeMeasurementAppointments({Function(dynamic)? onSuccess, Function(String)? onError}) async { + patientEyeMeasurementsAppointmentsHistoryList.clear(); + notifyListeners(); + + final result = await myAppointmentsRepo.getPatientAppointments(isActiveAppointment: false, isArrivedAppointments: true, isForEyeMeasurement: true); + + 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) { + patientEyeMeasurementsAppointmentsHistoryList = apiResponse.data!; + isEyeMeasurementsAppointmentsLoading = false; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } + Future getPatientAppointments(bool isActiveAppointment, bool isArrivedAppointments, {Function(dynamic)? onSuccess, Function(String)? onError}) async { if (!isAppointmentDataToBeLoaded) return; diff --git a/lib/presentation/appointments/widgets/appointment_card.dart b/lib/presentation/appointments/widgets/appointment_card.dart index f20d0b6..e451088 100644 --- a/lib/presentation/appointments/widgets/appointment_card.dart +++ b/lib/presentation/appointments/widgets/appointment_card.dart @@ -18,6 +18,7 @@ import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/appointments/appointment_details_page.dart'; import 'package:hmg_patient_app_new/presentation/appointments/widgets/ask_doctor_request_type_select.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/appointment_calendar.dart'; +import 'package:hmg_patient_app_new/presentation/medical_file/eye_measurement_details_page.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; @@ -31,6 +32,7 @@ class AppointmentCard extends StatelessWidget { final bool isLoading; final bool isFromHomePage; final bool isFromMedicalReport; + final bool isForEyeMeasurements; final MedicalFileViewModel? medicalFileViewModel; final BookAppointmentsViewModel bookAppointmentsViewModel; @@ -42,6 +44,7 @@ class AppointmentCard extends StatelessWidget { this.isLoading = false, this.isFromHomePage = false, this.isFromMedicalReport = false, + this.isForEyeMeasurements = false, this.medicalFileViewModel, }); @@ -170,24 +173,28 @@ class AppointmentCard extends StatelessWidget { Widget _buildActionArea(BuildContext context, AppState appState) { if (isFromMedicalReport) { - return CustomButton( - text: 'Select appointment'.needTranslation, - onPressed: () { - medicalFileViewModel!.setSelectedMedicalReportAppointment(patientAppointmentHistoryResponseModel); - Navigator.pop(context, false); - }, - backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, - textColor: AppColors.primaryRedColor, - fontSize: 14.f, - fontWeight: FontWeight.w500, - borderRadius: 12.r, - padding: EdgeInsets.symmetric(horizontal: 10.w), - height: isTablet || isFoldable ? 46.h : 40.h, - icon: AppAssets.checkmark_icon, - iconColor: AppColors.primaryRedColor, - iconSize: 16.h, - ); + if (isForEyeMeasurements) { + return SizedBox.shrink(); + } else { + return CustomButton( + text: 'Select appointment'.needTranslation, + onPressed: () { + medicalFileViewModel!.setSelectedMedicalReportAppointment(patientAppointmentHistoryResponseModel); + Navigator.pop(context, false); + }, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: 14.f, + fontWeight: FontWeight.w500, + borderRadius: 12.r, + padding: EdgeInsets.symmetric(horizontal: 10.w), + height: isTablet || isFoldable ? 46.h : 40.h, + icon: AppAssets.checkmark_icon, + iconColor: AppColors.primaryRedColor, + iconSize: 16.h, + ); + } } return Row( @@ -306,16 +313,24 @@ class AppointmentCard extends StatelessWidget { } void _goToDetails(BuildContext context) { - Navigator.of(context) - .push( - CustomPageRoute( - page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel), - ), - ) - .then((_) { - myAppointmentsViewModel.initAppointmentsViewModel(); - myAppointmentsViewModel.getPatientAppointments(true, false); - }); + if (isForEyeMeasurements) { + Navigator.of(context).push( + CustomPageRoute( + page: EyeMeasurementDetailsPage(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel), + ), + ); + } else { + Navigator.of(context) + .push( + CustomPageRoute( + page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel), + ), + ) + .then((_) { + myAppointmentsViewModel.initAppointmentsViewModel(); + myAppointmentsViewModel.getPatientAppointments(true, false); + }); + } } void openDoctorScheduleCalendar(BuildContext context) async { diff --git a/lib/presentation/medical_file/eye_measurement_details_page.dart b/lib/presentation/medical_file/eye_measurement_details_page.dart new file mode 100644 index 0000000..e69766a --- /dev/null +++ b/lib/presentation/medical_file/eye_measurement_details_page.dart @@ -0,0 +1,198 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/main.dart'; +import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_card.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; +import 'package:provider/provider.dart'; + +class EyeMeasurementDetailsPage extends StatelessWidget { + EyeMeasurementDetailsPage({super.key, required this.patientAppointmentHistoryResponseModel}); + + final PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel; + late BookAppointmentsViewModel bookAppointmentsViewModel; + + @override + Widget build(BuildContext context) { + bookAppointmentsViewModel = Provider.of(context, listen: false); + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: CollapsingListView( + title: LocaleKeys.eyeMeasurements.tr(), + child: SingleChildScrollView( + child: Consumer(builder: (context, myAppointmentsVM, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 16.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), + child: AppointmentCard( + patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel, + myAppointmentsViewModel: myAppointmentsVM, + bookAppointmentsViewModel: bookAppointmentsViewModel, + isLoading: false, + isFromHomePage: false, + isFromMedicalReport: true, + isForEyeMeasurements: true, + ), + ), + SizedBox(height: 16.h), + CustomTabBar( + activeTextColor: AppColors.primaryRedColor, + activeBackgroundColor: AppColors.primaryRedColor.withValues(alpha: .1), + tabs: [ + CustomTabBarModel(null, LocaleKeys.classes.tr()), + CustomTabBarModel(null, LocaleKeys.contactLens.tr()), + ], + onTabChange: (index) { + myAppointmentsVM.onEyeMeasurementsTabChanged(index); + }, + ), + SizedBox(height: 12.h), + getSelectedTabContent(myAppointmentsVM), + ], + ).paddingSymmetrical(24.w, 0); + }), + ), + ), + ); + } + + Widget getSelectedTabContent(MyAppointmentsViewModel myAppointmentsVM) { + switch (myAppointmentsVM.eyeMeasurementsTabSelectedIndex) { + case 0: + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.all(16.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.r, hasShadow: true), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.rightEye.tr().toText14(isBold: true), + SizedBox(height: 16.h), + getRow(LocaleKeys.sphere.tr(), '${patientAppointmentHistoryResponseModel.listHISGetGlassPrescription![0].rightEyeSpherical}', '-'), + getRow(LocaleKeys.cylinder.tr(), '${patientAppointmentHistoryResponseModel.listHISGetGlassPrescription![0].rightEyeCylinder}', '-'), + getRow(LocaleKeys.axis.tr(), '${patientAppointmentHistoryResponseModel.listHISGetGlassPrescription![0].rightEyeAxis}', '-'), + getRow(LocaleKeys.prism.tr(), '${patientAppointmentHistoryResponseModel.listHISGetGlassPrescription![0].rightEyePrism}', '-'), + getRow(LocaleKeys.va.tr(), '${patientAppointmentHistoryResponseModel.listHISGetGlassPrescription![0].rightEyeVA}', '-'), + getRow(LocaleKeys.remarks.tr(), '${patientAppointmentHistoryResponseModel.listHISGetGlassPrescription![0].rightEyeRemarks}', '-', isLast: true), + ], + ), + ), + SizedBox(height: 16.h), + Container( + padding: EdgeInsets.all(16.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.r, hasShadow: true), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.leftEye.tr().needTranslation.toText14(isBold: true), + SizedBox(height: 16.h), + getRow(LocaleKeys.sphere.tr(), '${patientAppointmentHistoryResponseModel.listHISGetGlassPrescription![0].leftEyeSpherical}', '-'), + getRow(LocaleKeys.cylinder.tr(), '${patientAppointmentHistoryResponseModel.listHISGetGlassPrescription![0].leftEyeCylinder}', '-'), + getRow(LocaleKeys.axis.tr(), '${patientAppointmentHistoryResponseModel.listHISGetGlassPrescription![0].leftEyeAxis}', '-'), + getRow(LocaleKeys.prism.tr(), '${patientAppointmentHistoryResponseModel.listHISGetGlassPrescription![0].leftEyePrism}', '-'), + getRow(LocaleKeys.va.tr(), '${patientAppointmentHistoryResponseModel.listHISGetGlassPrescription![0].leftEyeVA}', '-'), + getRow(LocaleKeys.remarks.tr(), '${patientAppointmentHistoryResponseModel.listHISGetGlassPrescription![0].leftEyeRemarks}', '-', isLast: true), + ], + ), + ), + SizedBox(height: 24.h), + ], + ); + case 1: + return Column( + children: [ + Container( + padding: EdgeInsets.all(16.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.r, hasShadow: true), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.rightEye.tr().toText14(isBold: true), + SizedBox(height: 16.h), + getRow(LocaleKeys.brand.tr(), '${patientAppointmentHistoryResponseModel.listHISGetContactLensPrescription![0].brand}', ''), + getRow('B.C', '${patientAppointmentHistoryResponseModel.listHISGetContactLensPrescription![0].baseCurve}', ''), + getRow(LocaleKeys.power.tr(), '${patientAppointmentHistoryResponseModel.listHISGetContactLensPrescription![0].power}', ''), + getRow(LocaleKeys.diameter.tr(), '${patientAppointmentHistoryResponseModel.listHISGetContactLensPrescription![0].diameter}', ''), + getRow('OZ', '${patientAppointmentHistoryResponseModel.listHISGetContactLensPrescription![0].oZ}', ''), + getRow('CT', '${patientAppointmentHistoryResponseModel.listHISGetContactLensPrescription![0].cT}', ''), + getRow('Blend', '${patientAppointmentHistoryResponseModel.listHISGetContactLensPrescription![0].blend}', ''), + getRow(LocaleKeys.remarks.tr(), '${patientAppointmentHistoryResponseModel.listHISGetContactLensPrescription![0].remarks}', '', isLast: true), + ], + ), + ), + SizedBox(height: 16.h), + Container( + padding: EdgeInsets.all(16.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.r, hasShadow: true), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.leftEye.tr().needTranslation.toText14(isBold: true), + SizedBox(height: 16.h), + getRow(LocaleKeys.brand.tr(), '${patientAppointmentHistoryResponseModel.listHISGetContactLensPrescription![1].brand}', ''), + getRow('B.C', '${patientAppointmentHistoryResponseModel.listHISGetContactLensPrescription![1].baseCurve}', ''), + getRow(LocaleKeys.power.tr(), '${patientAppointmentHistoryResponseModel.listHISGetContactLensPrescription![1].power}', ''), + getRow(LocaleKeys.diameter.tr(), '${patientAppointmentHistoryResponseModel.listHISGetContactLensPrescription![1].diameter}', ''), + getRow('OZ', '${patientAppointmentHistoryResponseModel.listHISGetContactLensPrescription![1].oZ}', ''), + getRow('CT', '${patientAppointmentHistoryResponseModel.listHISGetContactLensPrescription![1].cT}', ''), + getRow('Blend', '${patientAppointmentHistoryResponseModel.listHISGetContactLensPrescription![1].blend}', ''), + getRow(LocaleKeys.remarks.tr(), '${patientAppointmentHistoryResponseModel.listHISGetContactLensPrescription![1].remarks}', '', isLast: true), + ], + ), + ), + SizedBox(height: 24.h), + ], + ); + default: + return Container(); + } + } + + Widget getRow(String title, String val1, String val2, {bool isLast = false}) => Padding( + padding: EdgeInsets.only(left: 8.w, right: 8.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.start, + children: [ + Padding( + padding: EdgeInsets.all(8.h), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Expanded(flex: 2, child: title.toText11(weight: FontWeight.w500)), + Expanded( + flex: 2, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceAround, + children: [ + SizedBox(width: 120.w, child: (val1 == 'null' ? '-' : val1).toText10(isBold: true, textOverflow: TextOverflow.clip)), + (val2 == 'null' ? '-' : val2).toText10(isBold: true, textOverflow: TextOverflow.ellipsis), + ], + ), + ) + ], + ), + ), + isLast + ? Container( + height: 4, + ) + : Divider(color: AppColors.borderOnlyColor.withValues(alpha: 0.1), height: 2.h) + ], + ), + ); +} diff --git a/lib/presentation/medical_file/eye_measurements_appointments_page.dart b/lib/presentation/medical_file/eye_measurements_appointments_page.dart new file mode 100644 index 0000000..3b82ad4 --- /dev/null +++ b/lib/presentation/medical_file/eye_measurements_appointments_page.dart @@ -0,0 +1,90 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_card.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; +import 'package:provider/provider.dart'; + +class EyeMeasurementsAppointmentsPage extends StatelessWidget { + EyeMeasurementsAppointmentsPage({super.key}); + + late BookAppointmentsViewModel bookAppointmentsViewModel; + + @override + Widget build(BuildContext context) { + bookAppointmentsViewModel = Provider.of(context, listen: false); + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: CollapsingListView( + title: "Eye Measurements", + child: SingleChildScrollView( + child: Consumer(builder: (context, myAppointmentsVM, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 16.h), + ListView.separated( + scrollDirection: Axis.vertical, + itemCount: myAppointmentsVM.isEyeMeasurementsAppointmentsLoading + ? 5 + : myAppointmentsVM.patientEyeMeasurementsAppointmentsHistoryList.isNotEmpty + ? myAppointmentsVM.patientEyeMeasurementsAppointmentsHistoryList.length + : 1, + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + padding: EdgeInsets.only(left: 24.h, right: 24.h), + itemBuilder: (context, index) { + return myAppointmentsVM.isEyeMeasurementsAppointmentsLoading + ? Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), + child: AppointmentCard( + patientAppointmentHistoryResponseModel: PatientAppointmentHistoryResponseModel(), + myAppointmentsViewModel: myAppointmentsVM, + bookAppointmentsViewModel: bookAppointmentsViewModel, + isLoading: true, + isFromHomePage: false, + ), + ) + : myAppointmentsVM.patientEyeMeasurementsAppointmentsHistoryList.isNotEmpty + ? AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 1000), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: AnimatedContainer( + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), + child: AppointmentCard( + patientAppointmentHistoryResponseModel: myAppointmentsVM.patientEyeMeasurementsAppointmentsHistoryList[index], + myAppointmentsViewModel: myAppointmentsVM, + bookAppointmentsViewModel: bookAppointmentsViewModel, + isLoading: false, + isFromHomePage: false, + isForEyeMeasurements: true, + ), + ), + ), + ), + ) + : Utils.getNoDataWidget(context, noDataText: "No Ophthalmology appointments found...".needTranslation); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + ), + SizedBox(height: 60.h), + ], + ); + }), + ), + ), + ); + } +} diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index 1be683b..828cbb4 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -35,6 +35,7 @@ import 'package:hmg_patient_app_new/presentation/insurance/widgets/insurance_upd import 'package:hmg_patient_app_new/presentation/insurance/widgets/patient_insurance_card.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_orders_page.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; +import 'package:hmg_patient_app_new/presentation/medical_file/eye_measurements_appointments_page.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/patient_sickleaves_list_page.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/vaccine_list_page.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/widgets/lab_rad_card.dart'; @@ -751,7 +752,16 @@ class _MedicalFilePageState extends State { svgIcon: AppAssets.eye_result_icon, isLargeText: true, iconSize: 36.w, - ), + ).onPress(() { + myAppointmentsViewModel.setIsEyeMeasurementsAppointmentsLoading(true); + myAppointmentsViewModel.onEyeMeasurementsTabChanged(0); + myAppointmentsViewModel.getPatientEyeMeasurementAppointments(); + Navigator.of(context).push( + CustomPageRoute( + page: EyeMeasurementsAppointmentsPage(), + ), + ); + }), MedicalFileCard( label: "Allergy Info".needTranslation, textColor: AppColors.blackColor, From 2a6c7fc0a1b277598f0eefcdd76329b496e4e068 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Thu, 13 Nov 2025 15:09:42 +0300 Subject: [PATCH 4/5] Find us implemented --- lib/core/api/api_client.dart | 4 +- lib/core/api_consts.dart | 2 +- lib/core/dependencies.dart | 11 ++ lib/features/contact_us/contact_us_repo.dart | 55 ++++++ .../contact_us/contact_us_view_model.dart | 65 +++++++ .../models/resp_models/get_hmg_locations.dart | 80 +++++++++ .../resp_models/get_patientI_cprojects.dart | 48 +++++ lib/main.dart | 4 + lib/presentation/contact_us/contact_us.dart | 104 +++++++++++ lib/presentation/contact_us/find_us_page.dart | 165 ++++++++++++++++++ .../contact_us/widgets/find_us_item_card.dart | 106 +++++++++++ .../widgets/hospital_list_item.dart | 4 +- lib/presentation/home/landing_page.dart | 12 +- lib/widgets/appbar/collapsing_list_view.dart | 2 +- 14 files changed, 651 insertions(+), 11 deletions(-) create mode 100644 lib/features/contact_us/contact_us_repo.dart create mode 100644 lib/features/contact_us/contact_us_view_model.dart create mode 100644 lib/features/contact_us/models/resp_models/get_hmg_locations.dart create mode 100644 lib/features/contact_us/models/resp_models/get_patientI_cprojects.dart create mode 100644 lib/presentation/contact_us/contact_us.dart create mode 100644 lib/presentation/contact_us/find_us_page.dart create mode 100644 lib/presentation/contact_us/widgets/find_us_item_card.dart diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index b2cc819..3d5f337 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -173,8 +173,8 @@ class ApiClientImp implements ApiClient { body[_appState.isAuthenticated ? 'TokenID' : 'LogInTokenID'] = _appState.appAuthToken; } - body['TokenID'] = "@dm!n"; - body['PatientID'] = 1231755; + // body['TokenID'] = "@dm!n"; + // body['PatientID'] = 1231755; // body['PatientTypeID'] = 1; // // body['PatientOutSA'] = 0; diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 41ef922..87a2d0e 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -730,7 +730,7 @@ var GET_PRESCRIPTION_INSTRUCTIONS_PDF = 'Services/ChatBot_Service.svc/REST/Chatb class ApiConsts { static const maxSmallScreen = 660; - static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.uat; + static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.prod; // static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index a82a9ad..fd9bb87 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -8,6 +8,8 @@ import 'package:hmg_patient_app_new/features/authentication/authentication_view_ import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_repo.dart'; import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/common/common_repo.dart'; +import 'package:hmg_patient_app_new/features/contact_us/contact_us_repo.dart'; +import 'package:hmg_patient_app_new/features/contact_us/contact_us_view_model.dart'; import 'package:hmg_patient_app_new/features/doctor_filter/doctor_filter_view_model.dart'; import 'package:hmg_patient_app_new/features/emergency_services/emergency_services_repo.dart'; import 'package:hmg_patient_app_new/features/emergency_services/emergency_services_view_model.dart'; @@ -103,6 +105,7 @@ class AppDependencies { getIt.registerLazySingleton(() => MedicalFileRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => ImmediateLiveCareRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => EmergencyServicesRepoImp(loggerService: getIt(), apiClient: getIt())); + getIt.registerLazySingleton(() => ContactUsRepoImp(loggerService: getIt(), apiClient: getIt())); // ViewModels // Global/shared VMs → LazySingleton @@ -202,6 +205,14 @@ class AppDependencies { ), ); + getIt.registerLazySingleton( + () => ContactUsViewModel( + contactUsRepo: getIt(), + appState: getIt(), + errorHandlerService: getIt(), + ), + ); + // Screen-specific VMs → Factory // getIt.registerFactory( // () => BookAppointmentsViewModel( diff --git a/lib/features/contact_us/contact_us_repo.dart b/lib/features/contact_us/contact_us_repo.dart new file mode 100644 index 0000000..9834150 --- /dev/null +++ b/lib/features/contact_us/contact_us_repo.dart @@ -0,0 +1,55 @@ +import 'package:dartz/dartz.dart'; +import 'package:hmg_patient_app_new/core/api/api_client.dart'; +import 'package:hmg_patient_app_new/core/api_consts.dart'; +import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; +import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; +import 'package:hmg_patient_app_new/features/contact_us/models/resp_models/get_hmg_locations.dart'; +import 'package:hmg_patient_app_new/services/logger_service.dart'; + +abstract class ContactUsRepo { + Future>>> getHMGLocations(); +} + +class ContactUsRepoImp implements ContactUsRepo { + final ApiClient apiClient; + final LoggerService loggerService; + + ContactUsRepoImp({required this.apiClient, required this.loggerService}); + + @override + Future>>> getHMGLocations() async { + Map mapDevice = {}; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + GET_FINDUS_REQUEST, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['ListHMGLocation']; + final hmgLocations = list.map((item) => GetHMGLocationsModel.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: hmgLocations, + ); + } 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/contact_us/contact_us_view_model.dart b/lib/features/contact_us/contact_us_view_model.dart new file mode 100644 index 0000000..7826bd1 --- /dev/null +++ b/lib/features/contact_us/contact_us_view_model.dart @@ -0,0 +1,65 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/features/contact_us/contact_us_repo.dart'; +import 'package:hmg_patient_app_new/features/contact_us/models/resp_models/get_hmg_locations.dart'; +import 'package:hmg_patient_app_new/services/error_handler_service.dart'; + +class ContactUsViewModel extends ChangeNotifier { + ContactUsRepo contactUsRepo; + ErrorHandlerService errorHandlerService; + AppState appState; + + bool isHMGLocationsListLoading = false; + bool isHMGHospitalsListSelected = true; + + List hmgHospitalsLocationsList = []; + List hmgPharmacyLocationsList = []; + + ContactUsViewModel({required this.contactUsRepo, required this.errorHandlerService, required this.appState}); + + initContactUsViewModel() { + isHMGLocationsListLoading = true; + isHMGHospitalsListSelected = true; + hmgHospitalsLocationsList.clear(); + hmgPharmacyLocationsList.clear(); + getHMGLocations(); + notifyListeners(); + } + + setHMGHospitalsListSelected(bool isSelected) { + isHMGHospitalsListSelected = isSelected; + notifyListeners(); + } + + Future getHMGLocations({Function(dynamic)? onSuccess, Function(String)? onError}) async { + isHMGLocationsListLoading = true; + hmgHospitalsLocationsList.clear(); + hmgPharmacyLocationsList.clear(); + notifyListeners(); + + final result = await contactUsRepo.getHMGLocations(); + + 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) { + // hmgLocationsList = apiResponse.data!; + for (var location in apiResponse.data!) { + if (location.locationType == 1) { + hmgHospitalsLocationsList.add(location); + } else if (location.locationType == 2) { + hmgPharmacyLocationsList.add(location); + } + } + isHMGLocationsListLoading = false; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } +} diff --git a/lib/features/contact_us/models/resp_models/get_hmg_locations.dart b/lib/features/contact_us/models/resp_models/get_hmg_locations.dart new file mode 100644 index 0000000..d15b853 --- /dev/null +++ b/lib/features/contact_us/models/resp_models/get_hmg_locations.dart @@ -0,0 +1,80 @@ +class GetHMGLocationsModel { + dynamic cityID; + String? cityName; + dynamic cityNameN; + dynamic distanceInKilometers; + bool? isActive; + String? latitude; + int? locationID; + String? locationName; + dynamic locationNameN; + dynamic locationType; + String? longitude; + int? pharmacyLocationID; + String? phoneNumber; + int? projectID; + String? projectImageURL; + int? setupID; + dynamic sortOrder; + + GetHMGLocationsModel( + {this.cityID, + this.cityName, + this.cityNameN, + this.distanceInKilometers, + this.isActive, + this.latitude, + this.locationID, + this.locationName, + this.locationNameN, + this.locationType, + this.longitude, + this.pharmacyLocationID, + this.phoneNumber, + this.projectID, + this.projectImageURL, + this.setupID, + this.sortOrder}); + + GetHMGLocationsModel.fromJson(Map json) { + cityID = json['CityID']; + cityName = json['CityName']; + cityNameN = json['CityNameN']; + distanceInKilometers = json['DistanceInKilometers']; + isActive = json['IsActive']; + latitude = json['Latitude']; + locationID = json['LocationID']; + locationName = json['LocationName']; + locationNameN = json['LocationNameN']; + locationType = json['LocationType']; + longitude = json['Longitude']; + pharmacyLocationID = json['PharmacyLocationID']; + phoneNumber = json['PhoneNumber']; + projectID = json['ProjectID']; + projectImageURL = json['ProjectImageURL']; + setupID = json['SetupID']; + sortOrder = json['SortOrder']; + } + + Map toJson() { + final Map data = new Map(); + data['CityID'] = this.cityID; + data['CityName'] = this.cityName; + data['CityNameN'] = this.cityNameN; + data['DistanceInKilometers'] = this.distanceInKilometers; + data['IsActive'] = this.isActive; + data['Latitude'] = this.latitude; + data['LocationID'] = this.locationID; + data['LocationName'] = this.locationName; + data['LocationNameN'] = this.locationNameN; + data['LocationType'] = this.locationType; + data['Longitude'] = this.longitude; + data['PharmacyLocationID'] = this.pharmacyLocationID; + data['PhoneNumber'] = this.phoneNumber; + data['ProjectID'] = this.projectID; + data['ProjectImageURL'] = this.projectImageURL; + data['SetupID'] = this.setupID; + data['SortOrder'] = this.sortOrder; + return data; + } +} \ No newline at end of file diff --git a/lib/features/contact_us/models/resp_models/get_patientI_cprojects.dart b/lib/features/contact_us/models/resp_models/get_patientI_cprojects.dart new file mode 100644 index 0000000..ba7463f --- /dev/null +++ b/lib/features/contact_us/models/resp_models/get_patientI_cprojects.dart @@ -0,0 +1,48 @@ +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; + +class GetPatientICProjectsModel { + int? id; + String? projectName; + String? projectNameN; + String? value; + dynamic languageId; + DateTime? createdOn; + String? createdBy; + dynamic editedOn; + dynamic editedBy; + bool? isActive; + dynamic distanceInKilometers; + + GetPatientICProjectsModel( + {this.id, this.projectName, this.projectNameN, this.value, this.languageId, this.createdOn, this.createdBy, this.editedOn, this.editedBy, this.distanceInKilometers, this.isActive}); + + GetPatientICProjectsModel.fromJson(Map json) { + id = json['id']; + projectName = json['ProjectName']; + projectNameN = json['ProjectNameN']; + value = json['Value']; + languageId = json['LanguageId']; + createdOn = DateUtil.convertStringToDate(json['CreatedOn']); + createdBy = json['CreatedBy']; + editedOn = json['EditedOn']; + editedBy = json['EditedBy']; + isActive = json['IsActive']; + distanceInKilometers = json['DistanceInKilometers']; + } + + Map toJson() { + final Map data = new Map(); + data['id'] = this.id; + data['ProjectName'] = this.projectName; + data['ProjectNameN'] = this.projectNameN; + data['Value'] = this.value; + data['LanguageId'] = this.languageId; + data['CreatedOn'] = this.createdOn; + data['CreatedBy'] = this.createdBy; + data['EditedOn'] = this.editedOn; + data['EditedBy'] = this.editedBy; + data['IsActive'] = this.isActive; + data['DistanceInKilometers'] = this.distanceInKilometers; + return data; + } +} diff --git a/lib/main.dart b/lib/main.dart index 259ce3b..7de1be4 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -10,6 +10,7 @@ import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/features/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/features/contact_us/contact_us_view_model.dart'; import 'package:hmg_patient_app_new/features/doctor_filter/doctor_filter_view_model.dart'; import 'package:hmg_patient_app_new/features/emergency_services/emergency_services_view_model.dart'; import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart'; @@ -129,6 +130,9 @@ void main() async { ), ChangeNotifierProvider( create: (_) => getIt.get(), + ), + ChangeNotifierProvider( + create: (_) => getIt.get(), ) ], child: MyApp()), ), diff --git a/lib/presentation/contact_us/contact_us.dart b/lib/presentation/contact_us/contact_us.dart new file mode 100644 index 0000000..970e7eb --- /dev/null +++ b/lib/presentation/contact_us/contact_us.dart @@ -0,0 +1,104 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/location_util.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/contact_us/contact_us_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/contact_us/find_us_page.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; +import 'package:provider/provider.dart'; + +class ContactUs extends StatelessWidget { + ContactUs({super.key}); + + late AppState appState; + late ContactUsViewModel contactUsViewModel; + late LocationUtils locationUtils; + + @override + Widget build(BuildContext context) { + appState = getIt.get(); + locationUtils = getIt.get(); + locationUtils!.isShowConfirmDialog = true; + contactUsViewModel = Provider.of(context); + return Column( + children: [ + checkInOptionCard( + AppAssets.checkin_location_icon, + LocaleKeys.findUs.tr(), + "View your nearest HMG locations".needTranslation, + ).onPress(() { + locationUtils.getCurrentLocation(onSuccess: (value) { + contactUsViewModel.initContactUsViewModel(); + Navigator.pop(context); + Navigator.of(context).push( + CustomPageRoute( + page: FindUsPage(), + ), + ); + }); + }), + SizedBox(height: 16.h), + checkInOptionCard( + AppAssets.checkin_location_icon, + LocaleKeys.feedback.tr(), + "Provide your feedback on our services".needTranslation, + ), + SizedBox(height: 16.h), + checkInOptionCard( + AppAssets.checkin_location_icon, + LocaleKeys.liveChat.tr(), + "Live chat option with HMG".needTranslation, + ), + ], + ); + } + + Widget checkInOptionCard(String icon, String title, String subTitle) { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.r, + hasShadow: false, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Utils.buildSvgWithAssets(icon: icon, width: 40.h, height: 40.h, fit: BoxFit.fill), + SizedBox(height: 16.h), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + title.toText16(isBold: true, color: AppColors.textColor), + subTitle.toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor), + ], + ), + ), + Transform.flip( + flipX: appState.isArabic(), + child: Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon_small, + iconColor: AppColors.blackColor, + width: 18.h, + height: 13.h, + fit: BoxFit.contain, + ), + ), + ], + ), + ], + ).paddingAll(16.h), + ); + } +} diff --git a/lib/presentation/contact_us/find_us_page.dart b/lib/presentation/contact_us/find_us_page.dart new file mode 100644 index 0000000..a4e4258 --- /dev/null +++ b/lib/presentation/contact_us/find_us_page.dart @@ -0,0 +1,165 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/contact_us/contact_us_view_model.dart'; +import 'package:hmg_patient_app_new/features/medical_file/models/patient_sickleave_response_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/contact_us/widgets/find_us_item_card.dart'; +import 'package:hmg_patient_app_new/presentation/medical_file/widgets/patient_sick_leave_card.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; +import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; +import 'package:provider/provider.dart'; + +class FindUsPage extends StatelessWidget { + FindUsPage({super.key}); + + late AppState appState; + late ContactUsViewModel contactUsViewModel; + + @override + Widget build(BuildContext context) { + contactUsViewModel = Provider.of(context); + appState = getIt.get(); + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: CollapsingListView( + title: LocaleKeys.location.tr(), + child: Consumer(builder: (context, contactUsVM, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 16.h), + contactUsVM.isHMGLocationsListLoading + ? SizedBox.shrink() + : CustomTabBar( + activeTextColor: AppColors.primaryRedColor, + activeBackgroundColor: AppColors.primaryRedColor.withValues(alpha: .1), + tabs: [ + CustomTabBarModel(null, LocaleKeys.hmgHospitals.tr()), + CustomTabBarModel(null, LocaleKeys.pharmaciesList.tr()), + ], + onTabChange: (index) { + contactUsVM.setHMGHospitalsListSelected(index == 0); + }, + ).paddingSymmetrical(24.h, 0.h), + ListView.separated( + padding: EdgeInsets.only(top: 16.h), + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: contactUsVM.isHMGLocationsListLoading + ? 5 + : contactUsVM.isHMGHospitalsListSelected + ? contactUsVM.hmgHospitalsLocationsList.length + : contactUsVM.hmgPharmacyLocationsList.length, + itemBuilder: (context, index) { + return contactUsVM.isHMGLocationsListLoading + ? Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), + child: Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: true, + ), + child: Padding( + padding: EdgeInsets.all(14.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Image.network( + "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png", + width: 63.h, + height: 63.h, + fit: BoxFit.cover, + ).circle(100).toShimmer2(isShow: true), + SizedBox(width: 16.h), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Dr John Smith".toText16(isBold: true).toShimmer2(isShow: true), + SizedBox(height: 8.h), + Wrap( + direction: Axis.horizontal, + spacing: 3.h, + runSpacing: 4.h, + children: [ + AppCustomChipWidget(labelText: "").toShimmer2(isShow: true, width: 16.h), + AppCustomChipWidget(labelText: "").toShimmer2(isShow: true, width: 16.h), + ], + ), + ], + ), + ), + ], + ), + ], + ), + ), + ), + ).paddingSymmetrical(24.h, 0.h) + : contactUsVM.isHMGHospitalsListSelected + // ? contactUsVM.hmgHospitalsLocationsList.isNotEmpty + ? AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: AnimatedContainer( + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), + child: FindUsItemCard( + getHMGLocationsModel: contactUsVM.hmgHospitalsLocationsList[index], + ), + ).paddingSymmetrical(24.h, 0.h), + ), + ), + ) + : AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: AnimatedContainer( + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true), + child: FindUsItemCard( + getHMGLocationsModel: contactUsVM.hmgPharmacyLocationsList[index], + ), + ).paddingSymmetrical(24.h, 0.h), + ), + ), + ); + // : Utils.getNoDataWidget( + // context, + // noDataText: "No any locations yet.".needTranslation, + // ); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + ), + SizedBox(height: 24.h), + // FindUsItemCard(), + // FindUsItemCard(), + // FindUsItemCard(), + ], + ); + }), + ), + ); + } +} diff --git a/lib/presentation/contact_us/widgets/find_us_item_card.dart b/lib/presentation/contact_us/widgets/find_us_item_card.dart new file mode 100644 index 0000000..a59959f --- /dev/null +++ b/lib/presentation/contact_us/widgets/find_us_item_card.dart @@ -0,0 +1,106 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/contact_us/models/resp_models/get_hmg_locations.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; +import 'package:maps_launcher/maps_launcher.dart'; +import 'package:url_launcher/url_launcher.dart'; + +class FindUsItemCard extends StatelessWidget { + FindUsItemCard({super.key, required this.getHMGLocationsModel}); + + late AppState appState; + GetHMGLocationsModel getHMGLocationsModel; + + @override + Widget build(BuildContext context) { + appState = getIt.get(); + return DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.r, + hasShadow: false, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 8.h, + children: [hospitalName, distanceInfo], + ), + ), + ], + ).paddingSymmetrical(16.h, 16.h), + ); + } + + Widget get hospitalName => Row( + children: [ + Image.network( + getHMGLocationsModel.projectImageURL ?? "https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png", + width: 40.h, + height: 40.h, + fit: BoxFit.cover, + ).circle(100).toShimmer2(isShow: false).paddingOnly(right: 10), + Expanded( + child: Text( + getHMGLocationsModel.locationName!, + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 16, + color: AppColors.blackColor, + ), + ), + ) + ], + ); + + Widget get distanceInfo => Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + AppCustomChipWidget( + labelText: "${getHMGLocationsModel.distanceInKilometers ?? ""} km".needTranslation, + icon: AppAssets.location_red, + iconColor: AppColors.primaryRedColor, + backgroundColor: AppColors.secondaryLightRedColor, + textColor: AppColors.errorColor, + ), + Row( + children: [ + AppCustomChipWidget( + labelText: "Get Directions".needTranslation, + icon: AppAssets.directions_icon, + iconColor: AppColors.whiteColor, + backgroundColor: AppColors.textColor.withValues(alpha: 0.8), + textColor: AppColors.whiteColor, + onChipTap: () { + MapsLauncher.launchCoordinates(double.parse(getHMGLocationsModel.latitude ?? "0.0"), double.parse(getHMGLocationsModel.longitude ?? "0.0"), getHMGLocationsModel.locationName!); + }, + ), + SizedBox(width: 4.w), + AppCustomChipWidget( + labelText: LocaleKeys.callNow.tr(), + icon: AppAssets.call_fill, + iconColor: AppColors.whiteColor, + backgroundColor: AppColors.primaryRedColor.withValues(alpha: 1.0), + textColor: AppColors.whiteColor, + onChipTap: () { + launchUrl(Uri.parse("tel://" + "${getHMGLocationsModel.phoneNumber}")); + }, + ), + ], + ), + ], + ); +} diff --git a/lib/presentation/habib_wallet/widgets/hospital_list_item.dart b/lib/presentation/habib_wallet/widgets/hospital_list_item.dart index 8e10e28..ae47326 100644 --- a/lib/presentation/habib_wallet/widgets/hospital_list_item.dart +++ b/lib/presentation/habib_wallet/widgets/hospital_list_item.dart @@ -40,8 +40,8 @@ class HospitalListItemAdvancePayment extends StatelessWidget { child: Utils.buildSvgWithAssets( icon: AppAssets.forward_arrow_icon, iconColor: AppColors.blackColor, - width: 18, - height: 13, + width: 40.h, + height: 40.h, fit: BoxFit.contain, ), ), diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index 51bd872..3347902 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -29,6 +29,7 @@ import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointmen import 'package:hmg_patient_app_new/presentation/authentication/quick_login.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/book_appointment_page.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/livecare/immediate_livecare_pending_request_page.dart'; +import 'package:hmg_patient_app_new/presentation/contact_us/contact_us.dart'; import 'package:hmg_patient_app_new/presentation/emergency_services/er_online_checkin/er_online_checkin_home.dart'; import 'package:hmg_patient_app_new/presentation/home/data/landing_page_data.dart'; import 'package:hmg_patient_app_new/presentation/home/widgets/habib_wallet_card.dart'; @@ -167,11 +168,12 @@ class _LandingPageState extends State { ); }), Utils.buildSvgWithAssets(icon: AppAssets.contact_icon, height: 18.h, width: 18.h).onPress(() { - Navigator.of(context).push( - CustomPageRoute( - page: MedicalFilePage(), - // page: LoginScreen(), - ), + showCommonBottomSheetWithoutHeight( + context, + title: LocaleKeys.contactUs.tr(), + child: ContactUs(), + callBackFunc: () {}, + isFullScreen: false, ); }), ], diff --git a/lib/widgets/appbar/collapsing_list_view.dart b/lib/widgets/appbar/collapsing_list_view.dart index f6db2f4..8e07631 100644 --- a/lib/widgets/appbar/collapsing_list_view.dart +++ b/lib/widgets/appbar/collapsing_list_view.dart @@ -54,7 +54,7 @@ class CollapsingListView extends StatelessWidget { SliverAppBar( automaticallyImplyLeading: false, pinned: true, - expandedHeight: 100.h, + expandedHeight: MediaQuery.of(context).size.height * 0.12.h, stretch: true, systemOverlayStyle: SystemUiOverlayStyle(statusBarBrightness: Brightness.light), surfaceTintColor: Colors.transparent, From 096b2b7cbf7c116153a5fe1dedbcdcb3437bf3d0 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Thu, 13 Nov 2025 15:48:12 +0300 Subject: [PATCH 5/5] livechat implementation contd. --- lib/features/contact_us/contact_us_repo.dart | 40 +++++++++++++++++++ ...ects.dart => get_patient_ic_projects.dart} | 0 lib/presentation/contact_us/contact_us.dart | 14 ++++++- .../contact_us/live_chat_page.dart | 27 +++++++++++++ 4 files changed, 79 insertions(+), 2 deletions(-) rename lib/features/contact_us/models/resp_models/{get_patientI_cprojects.dart => get_patient_ic_projects.dart} (100%) create mode 100644 lib/presentation/contact_us/live_chat_page.dart diff --git a/lib/features/contact_us/contact_us_repo.dart b/lib/features/contact_us/contact_us_repo.dart index 9834150..f2b1169 100644 --- a/lib/features/contact_us/contact_us_repo.dart +++ b/lib/features/contact_us/contact_us_repo.dart @@ -4,10 +4,13 @@ import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; import 'package:hmg_patient_app_new/features/contact_us/models/resp_models/get_hmg_locations.dart'; +import 'package:hmg_patient_app_new/features/contact_us/models/resp_models/get_patient_ic_projects.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; abstract class ContactUsRepo { Future>>> getHMGLocations(); + + Future>>> getLiveChatProjectsList(); } class ContactUsRepoImp implements ContactUsRepo { @@ -52,4 +55,41 @@ class ContactUsRepoImp implements ContactUsRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future>>> getLiveChatProjectsList() async { + Map mapDevice = {}; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + GET_LIVECHAT_REQUEST, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['List_PatientICProjects']; + final hmgLocations = list.map((item) => GetPatientICProjectsModel.fromJson(item as Map)).toList().cast(); + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: hmgLocations, + ); + } 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/contact_us/models/resp_models/get_patientI_cprojects.dart b/lib/features/contact_us/models/resp_models/get_patient_ic_projects.dart similarity index 100% rename from lib/features/contact_us/models/resp_models/get_patientI_cprojects.dart rename to lib/features/contact_us/models/resp_models/get_patient_ic_projects.dart diff --git a/lib/presentation/contact_us/contact_us.dart b/lib/presentation/contact_us/contact_us.dart index 970e7eb..6890fb4 100644 --- a/lib/presentation/contact_us/contact_us.dart +++ b/lib/presentation/contact_us/contact_us.dart @@ -11,6 +11,7 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/contact_us/contact_us_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/contact_us/find_us_page.dart'; +import 'package:hmg_patient_app_new/presentation/contact_us/live_chat_page.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:provider/provider.dart'; @@ -26,7 +27,7 @@ class ContactUs extends StatelessWidget { Widget build(BuildContext context) { appState = getIt.get(); locationUtils = getIt.get(); - locationUtils!.isShowConfirmDialog = true; + locationUtils.isShowConfirmDialog = true; contactUsViewModel = Provider.of(context); return Column( children: [ @@ -56,7 +57,16 @@ class ContactUs extends StatelessWidget { AppAssets.checkin_location_icon, LocaleKeys.liveChat.tr(), "Live chat option with HMG".needTranslation, - ), + ).onPress(() { + locationUtils.getCurrentLocation(onSuccess: (value) { + Navigator.pop(context); + Navigator.of(context).push( + CustomPageRoute( + page: LiveChatPage(), + ), + ); + }); + }), ], ); } diff --git a/lib/presentation/contact_us/live_chat_page.dart b/lib/presentation/contact_us/live_chat_page.dart new file mode 100644 index 0000000..aced678 --- /dev/null +++ b/lib/presentation/contact_us/live_chat_page.dart @@ -0,0 +1,27 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; + +class LiveChatPage extends StatelessWidget { + const LiveChatPage({super.key}); + + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: Column( + children: [ + Expanded( + child: CollapsingListView( + title: LocaleKeys.liveChat.tr(), + child: SingleChildScrollView(), + ), + ), + Container() + ], + ), + ); + } +}