From f9f17f3a76514d311065a3b1e129348e8a75adb4 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Mon, 17 Nov 2025 15:43:27 +0300 Subject: [PATCH 01/12] LiveChat implemented --- lib/core/api/api_client.dart | 2 +- lib/core/api_consts.dart | 2 +- lib/features/contact_us/contact_us_repo.dart | 4 +- .../contact_us/contact_us_view_model.dart | 39 ++ .../appointment_details_page.dart | 335 +++++++++--------- .../authentication/quick_login.dart | 206 ++++++----- .../book_appointment/widgets/doctor_card.dart | 27 +- lib/presentation/contact_us/contact_us.dart | 1 + .../contact_us/live_chat_page.dart | 173 ++++++++- lib/presentation/home/landing_page.dart | 9 +- .../lab_order_result_item.dart | 15 +- .../medical_file/medical_file_page.dart | 8 +- .../prescriptions_list_page.dart | 20 +- lib/presentation/services/services_page.dart | 23 -- lib/widgets/common_bottom_sheet.dart | 2 +- 15 files changed, 510 insertions(+), 356 deletions(-) delete mode 100644 lib/presentation/services/services_page.dart diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index 162f0fd..74a7158 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -175,7 +175,7 @@ class ApiClientImp implements ApiClient { } // body['TokenID'] = "@dm!n"; - // body['PatientID'] = 4772429; + // body['PatientID'] = 763103; // body['PatientID'] = 1231755; // body['PatientTypeID'] = 1; // diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index bdaba66..032206e 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -719,7 +719,7 @@ var GET_PRESCRIPTION_INSTRUCTIONS_PDF = 'Services/ChatBot_Service.svc/REST/Chatb class ApiConsts { static const maxSmallScreen = 660; - static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.prod; + static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.uat; // static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT diff --git a/lib/features/contact_us/contact_us_repo.dart b/lib/features/contact_us/contact_us_repo.dart index f2b1169..b3e42c3 100644 --- a/lib/features/contact_us/contact_us_repo.dart +++ b/lib/features/contact_us/contact_us_repo.dart @@ -72,13 +72,13 @@ class ContactUsRepoImp implements ContactUsRepo { onSuccess: (response, statusCode, {messageStatus, errorMessage}) { try { final list = response['List_PatientICProjects']; - final hmgLocations = list.map((item) => GetPatientICProjectsModel.fromJson(item as Map)).toList().cast(); + final liveChatProjectsList = list.map((item) => GetPatientICProjectsModel.fromJson(item as Map)).toList().cast(); apiResponse = GenericApiModel>( messageStatus: messageStatus, statusCode: statusCode, errorMessage: null, - data: hmgLocations, + data: liveChatProjectsList, ); } catch (e) { failure = DataParsingFailure(e.toString()); diff --git a/lib/features/contact_us/contact_us_view_model.dart b/lib/features/contact_us/contact_us_view_model.dart index 7826bd1..2ad5737 100644 --- a/lib/features/contact_us/contact_us_view_model.dart +++ b/lib/features/contact_us/contact_us_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/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/features/contact_us/models/resp_models/get_patient_ic_projects.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; class ContactUsViewModel extends ChangeNotifier { @@ -11,17 +12,24 @@ class ContactUsViewModel extends ChangeNotifier { bool isHMGLocationsListLoading = false; bool isHMGHospitalsListSelected = true; + bool isLiveChatProjectsListLoading = false; List hmgHospitalsLocationsList = []; List hmgPharmacyLocationsList = []; + List liveChatProjectsList = []; + + int selectedLiveChatProjectIndex = -1; + ContactUsViewModel({required this.contactUsRepo, required this.errorHandlerService, required this.appState}); initContactUsViewModel() { isHMGLocationsListLoading = true; isHMGHospitalsListSelected = true; + isLiveChatProjectsListLoading = true; hmgHospitalsLocationsList.clear(); hmgPharmacyLocationsList.clear(); + liveChatProjectsList.clear(); getHMGLocations(); notifyListeners(); } @@ -31,6 +39,11 @@ class ContactUsViewModel extends ChangeNotifier { notifyListeners(); } + setSelectedLiveChatProjectIndex(int index) { + selectedLiveChatProjectIndex = index; + notifyListeners(); + } + Future getHMGLocations({Function(dynamic)? onSuccess, Function(String)? onError}) async { isHMGLocationsListLoading = true; hmgHospitalsLocationsList.clear(); @@ -62,4 +75,30 @@ class ContactUsViewModel extends ChangeNotifier { }, ); } + + Future getLiveChatProjectsList({Function(dynamic)? onSuccess, Function(String)? onError}) async { + isLiveChatProjectsListLoading = true; + liveChatProjectsList.clear(); + + notifyListeners(); + + final result = await contactUsRepo.getLiveChatProjectsList(); + + 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) { + liveChatProjectsList = apiResponse.data!; + liveChatProjectsList.sort((a, b) => b.distanceInKilometers.compareTo(a.distanceInKilometers)); + isLiveChatProjectsListLoading = false; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } } diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart index 79084c9..8bf74a0 100644 --- a/lib/presentation/appointments/appointment_details_page.dart +++ b/lib/presentation/appointments/appointment_details_page.dart @@ -263,6 +263,7 @@ class _AppointmentDetailsPageState extends State { SizedBox(height: 16.h), ], ) + // : SizedBox.shrink() : Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ @@ -294,186 +295,170 @@ class _AppointmentDetailsPageState extends State { iconSize: 40.w, isLargeText: true, ), - MedicalFileCard( - label: LocaleKeys.labResults.tr(context: context), - textColor: AppColors.blackColor, - backgroundColor: AppColors.whiteColor, - svgIcon: AppAssets.lab_result_icon, - iconSize: 40.w, - isLargeText: true, - ), - MedicalFileCard( - label: "Radiology Results".needTranslation, - textColor: AppColors.blackColor, - backgroundColor: AppColors.whiteColor, - svgIcon: AppAssets.radiology_icon, - iconSize: 40.w, - isLargeText: true, - ), ], ), SizedBox(height: 16.h), LocaleKeys.prescriptions.tr().toText18(isBold: true), SizedBox(height: 16.h), - Consumer(builder: (context, prescriptionVM, child) { - return prescriptionVM.isPrescriptionsDetailsLoading - ? const MoviesShimmerWidget() - : Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: Colors.white, - borderRadius: 20.r, - ), - padding: EdgeInsets.all(16.w), - child: Column( - children: [ - ListView.separated( - itemCount: prescriptionVM.prescriptionDetailsList.length, - shrinkWrap: true, - padding: EdgeInsets.only(right: 8.w), - physics: NeverScrollableScrollPhysics(), - itemBuilder: (context, index) { - return AnimationConfiguration.staggeredList( - position: index, - duration: const Duration(milliseconds: 500), - child: SlideAnimation( - verticalOffset: 100.0, - child: FadeInAnimation( - child: Row( - children: [ - Utils.buildSvgWithAssets( - icon: AppAssets.prescription_item_icon, - width: 40.h, - height: 40.h, - ), - SizedBox(width: 8.h), - Row( - mainAxisSize: MainAxisSize.max, - children: [ - Column( - children: [ - prescriptionVM.prescriptionDetailsList[index].itemDescription! - .toText12(isBold: true, maxLine: 1), - "Prescribed By: ${widget.patientAppointmentHistoryResponseModel.doctorTitle} ${widget.patientAppointmentHistoryResponseModel.doctorNameObj}" - .needTranslation - .toText10( - weight: FontWeight.w500, - color: AppColors.greyTextColor, - letterSpacing: -0.4), - ], - ), - SizedBox(width: 68.w), - Transform.flip( - flipX: appState.isArabic(), - child: Utils.buildSvgWithAssets( - icon: AppAssets.forward_arrow_icon, - iconColor: AppColors.blackColor, - width: 18.w, - height: 13.h, - fit: BoxFit.contain, - ), - ), - ], - ), - ], - ), - ), - ), - ); - }, - separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), - ).onPress(() { - prescriptionVM.setPrescriptionsDetailsLoading(); - Navigator.of(context).push( - CustomPageRoute( - page: PrescriptionDetailPage(prescriptionsResponseModel: getPrescriptionRequestModel()), - ), - ); - }), - SizedBox(height: 16.h), - const Divider(color: AppColors.dividerColor), - SizedBox(height: 16.h), - Wrap( - runSpacing: 6.w, - children: [ - // Expanded( - // child: CustomButton( - // text: widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported! ? LocaleKeys.resendOrder.tr(context: context) : LocaleKeys.prescriptionDeliveryError.tr(context: context), - // onPressed: () {}, - // backgroundColor: AppColors.secondaryLightRedColor, - // borderColor: AppColors.secondaryLightRedColor, - // textColor: AppColors.primaryRedColor, - // fontSize: 14, - // fontWeight: FontWeight.w500, - // borderRadius: 12.h, - // height: 40.h, - // icon: AppAssets.appointment_calendar_icon, - // iconColor: AppColors.primaryRedColor, - // iconSize: 16.h, - // ), - // ), - // SizedBox(width: 16.h), - Expanded( - child: CustomButton( - text: "Refill & Delivery".needTranslation, - onPressed: () { - Navigator.of(context) - .push( - CustomPageRoute( - page: PrescriptionsListPage(), - ), - ) - .then((val) { - prescriptionsViewModel.setPrescriptionsDetailsLoading(); - prescriptionsViewModel.getPrescriptionDetails(getPrescriptionRequestModel()); - }); - }, - backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, - textColor: AppColors.primaryRedColor, - fontSize: 14.f, - fontWeight: FontWeight.w500, - borderRadius: 12.r, - height: 40.h, - icon: AppAssets.requests, - iconColor: AppColors.primaryRedColor, - iconSize: 16.h, - ), - ), - - SizedBox(width: 16.w), - Expanded( - child: CustomButton( - text: "All Prescriptions".needTranslation, - onPressed: () { - Navigator.of(context) - .push( - CustomPageRoute( - page: PrescriptionsListPage(), - ), - ) - .then((val) { - prescriptionsViewModel.setPrescriptionsDetailsLoading(); - prescriptionsViewModel.getPrescriptionDetails(getPrescriptionRequestModel()); - }); - }, - backgroundColor: AppColors.secondaryLightRedColor, - borderColor: AppColors.secondaryLightRedColor, - textColor: AppColors.primaryRedColor, - fontSize: 14.f, - fontWeight: FontWeight.w500, - borderRadius: 12.r, - height: 40.h, - icon: AppAssets.requests, - iconColor: AppColors.primaryRedColor, - iconSize: 16.h, - ), - ), - ], - ), - ], - ), - ); - }), + // Consumer(builder: (context, prescriptionVM, child) { + // return prescriptionVM.isPrescriptionsDetailsLoading + // ? const MoviesShimmerWidget() + // : Container( + // decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + // color: Colors.white, + // borderRadius: 20.r, + // ), + // padding: EdgeInsets.all(16.w), + // child: Column( + // children: [ + // ListView.separated( + // itemCount: prescriptionVM.prescriptionDetailsList.length, + // shrinkWrap: true, + // padding: EdgeInsets.only(right: 8.w), + // physics: NeverScrollableScrollPhysics(), + // itemBuilder: (context, index) { + // return AnimationConfiguration.staggeredList( + // position: index, + // duration: const Duration(milliseconds: 500), + // child: SlideAnimation( + // verticalOffset: 100.0, + // child: FadeInAnimation( + // child: Row( + // children: [ + // Utils.buildSvgWithAssets( + // icon: AppAssets.prescription_item_icon, + // width: 40.h, + // height: 40.h, + // ), + // SizedBox(width: 8.h), + // Row( + // mainAxisSize: MainAxisSize.max, + // children: [ + // Column( + // children: [ + // prescriptionVM.prescriptionDetailsList[index].itemDescription! + // .toText12(isBold: true, maxLine: 1), + // "Prescribed By: ${widget.patientAppointmentHistoryResponseModel.doctorTitle} ${widget.patientAppointmentHistoryResponseModel.doctorNameObj}" + // .needTranslation + // .toText10( + // weight: FontWeight.w500, + // color: AppColors.greyTextColor, + // letterSpacing: -0.4), + // ], + // ), + // SizedBox(width: 68.w), + // Transform.flip( + // flipX: appState.isArabic(), + // child: Utils.buildSvgWithAssets( + // icon: AppAssets.forward_arrow_icon, + // iconColor: AppColors.blackColor, + // width: 18.w, + // height: 13.h, + // fit: BoxFit.contain, + // ), + // ), + // ], + // ), + // ], + // ), + // ), + // ), + // ); + // }, + // separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + // ).onPress(() { + // prescriptionVM.setPrescriptionsDetailsLoading(); + // Navigator.of(context).push( + // CustomPageRoute( + // page: PrescriptionDetailPage(prescriptionsResponseModel: getPrescriptionRequestModel()), + // ), + // ); + // }), + // SizedBox(height: 16.h), + // const Divider(color: AppColors.dividerColor), + // SizedBox(height: 16.h), + // Wrap( + // runSpacing: 6.w, + // children: [ + // // Expanded( + // // child: CustomButton( + // // text: widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported! ? LocaleKeys.resendOrder.tr(context: context) : LocaleKeys.prescriptionDeliveryError.tr(context: context), + // // onPressed: () {}, + // // backgroundColor: AppColors.secondaryLightRedColor, + // // borderColor: AppColors.secondaryLightRedColor, + // // textColor: AppColors.primaryRedColor, + // // fontSize: 14, + // // fontWeight: FontWeight.w500, + // // borderRadius: 12.h, + // // height: 40.h, + // // icon: AppAssets.appointment_calendar_icon, + // // iconColor: AppColors.primaryRedColor, + // // iconSize: 16.h, + // // ), + // // ), + // // SizedBox(width: 16.h), + // Expanded( + // child: CustomButton( + // text: "Refill & Delivery".needTranslation, + // onPressed: () { + // Navigator.of(context) + // .push( + // CustomPageRoute( + // page: PrescriptionsListPage(), + // ), + // ) + // .then((val) { + // prescriptionsViewModel.setPrescriptionsDetailsLoading(); + // prescriptionsViewModel.getPrescriptionDetails(getPrescriptionRequestModel()); + // }); + // }, + // backgroundColor: AppColors.secondaryLightRedColor, + // borderColor: AppColors.secondaryLightRedColor, + // textColor: AppColors.primaryRedColor, + // fontSize: 14.f, + // fontWeight: FontWeight.w500, + // borderRadius: 12.r, + // height: 40.h, + // icon: AppAssets.requests, + // iconColor: AppColors.primaryRedColor, + // iconSize: 16.h, + // ), + // ), + // + // SizedBox(width: 16.w), + // Expanded( + // child: CustomButton( + // text: "All Prescriptions".needTranslation, + // onPressed: () { + // Navigator.of(context) + // .push( + // CustomPageRoute( + // page: PrescriptionsListPage(), + // ), + // ) + // .then((val) { + // prescriptionsViewModel.setPrescriptionsDetailsLoading(); + // prescriptionsViewModel.getPrescriptionDetails(getPrescriptionRequestModel()); + // }); + // }, + // backgroundColor: AppColors.secondaryLightRedColor, + // borderColor: AppColors.secondaryLightRedColor, + // textColor: AppColors.primaryRedColor, + // fontSize: 14.f, + // fontWeight: FontWeight.w500, + // borderRadius: 12.r, + // height: 40.h, + // icon: AppAssets.requests, + // iconColor: AppColors.primaryRedColor, + // iconSize: 16.h, + // ), + // ), + // ], + // ), + // ], + // ), + // ); + // }), ], ), ], diff --git a/lib/presentation/authentication/quick_login.dart b/lib/presentation/authentication/quick_login.dart index f10d84f..e03efc3 100644 --- a/lib/presentation/authentication/quick_login.dart +++ b/lib/presentation/authentication/quick_login.dart @@ -1,11 +1,14 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:get_it/get_it.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/core/cache_consts.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/services/cache_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; @@ -21,115 +24,108 @@ class QuickLogin extends StatefulWidget { } class QuickLoginState extends State { + final CacheService cacheService = GetIt.instance(); + @override Widget build(BuildContext context) { NavigationService navigationService = getIt.get(); - return Container( - decoration: const BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.only( - topLeft: Radius.circular(24), - topRight: Radius.circular(24), - ), - ), - padding: const EdgeInsets.all(24), - child: Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - widget.isDone - ? Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - InkWell( - onTap: () { - navigationService.pop(); - }, - child: Utils.buildSvgWithAssets(icon: AppAssets.cross_circle)), - ], - ), - Utils.showLottie(context: context, assetPath: AppAnimations.checkmark, width: 120, height: 120, repeat: true), - LocaleKeys.allSet.tr().toText16(textAlign: TextAlign.center, weight: FontWeight.w500) - // Text( - // ' TranslationBase.of(context).allSet', - // textAlign: TextAlign.center, - // style: context.dynamicTextStyle( - // fontSize: 16, - // fontWeight: FontWeight.w500, - // color: Colors.black, - // ), - // ), - ], - ) - : Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Image.asset(AppAssets.lockIcon, height: 100), - SizedBox(height: 10.h), - LocaleKeys.enableQuickLogin.tr().toText26(isBold: true), - // Text( - // ' TranslationBase.of(context).enableQuickLogin', - // style: context.dynamicTextStyle( - // fontSize: 26, - // fontWeight: FontWeight.bold, - // color: Colors.black, - // ), - // ), - SizedBox(height: 5.h), - LocaleKeys.enableQuickLogin.tr().toText16(color: AppColors.quickLoginColor), - // Description - // Text( - // 'TranslationBase.of(context).enableMsg', - // style: context.dynamicTextStyle( - // fontSize: 16, - // color: Color(0xFF666666), - // height: 1.5, - // ), - //), - const SizedBox(height: 24), - // Buttons - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Expanded( - child: CustomButton( - text: LocaleKeys.enableQuickLogin.tr(), - onPressed: () { - widget.onPressed(); - }, - backgroundColor: Color(0xffED1C2B), - borderColor: Color(0xffED1C2B), - textColor: Colors.white, - icon: AppAssets.apple_finder, - )), - ], - ), - SizedBox( - height: 16, - ), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Expanded( - child: CustomButton( - text: LocaleKeys.notNow.tr(), - onPressed: () { - Navigator.pop(context, "true"); + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + widget.isDone + ? Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + InkWell( + onTap: () { + navigationService.pop(); }, - backgroundColor: Color(0xffFEE9EA), - borderColor: Color(0xffFEE9EA), - textColor: Colors.red, - // icon: "assets/images/svg/apple-finder.svg", - )), - ], - ), - ], - ) - ], - ), + child: Utils.buildSvgWithAssets(icon: AppAssets.cross_circle)), + ], + ), + Utils.showLottie(context: context, assetPath: AppAnimations.checkmark, width: 120, height: 120, repeat: true), + LocaleKeys.allSet.tr().toText16(textAlign: TextAlign.center, weight: FontWeight.w500) + // Text( + // ' TranslationBase.of(context).allSet', + // textAlign: TextAlign.center, + // style: context.dynamicTextStyle( + // fontSize: 16, + // fontWeight: FontWeight.w500, + // color: Colors.black, + // ), + // ), + ], + ) + : Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Image.asset(AppAssets.lockIcon, height: 100), + SizedBox(height: 10.h), + LocaleKeys.enableQuickLogin.tr().toText26(isBold: true), + // Text( + // ' TranslationBase.of(context).enableQuickLogin', + // style: context.dynamicTextStyle( + // fontSize: 26, + // fontWeight: FontWeight.bold, + // color: Colors.black, + // ), + // ), + SizedBox(height: 5.h), + LocaleKeys.enableQuickLogin.tr().toText16(color: AppColors.quickLoginColor), + // Description + // Text( + // 'TranslationBase.of(context).enableMsg', + // style: context.dynamicTextStyle( + // fontSize: 16, + // color: Color(0xFF666666), + // height: 1.5, + // ), + //), + const SizedBox(height: 24), + // Buttons + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Expanded( + child: CustomButton( + text: LocaleKeys.enableQuickLogin.tr(), + onPressed: () { + widget.onPressed(); + }, + backgroundColor: Color(0xffED1C2B), + borderColor: Color(0xffED1C2B), + textColor: Colors.white, + icon: AppAssets.apple_finder, + )), + ], + ), + SizedBox( + height: 16, + ), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Expanded( + child: CustomButton( + text: LocaleKeys.notNow.tr(), + onPressed: () { + cacheService.saveBool(key: CacheConst.quickLoginEnabled, value: false); + Navigator.pop(context, "true"); + }, + backgroundColor: Color(0xffFEE9EA), + borderColor: Color(0xffFEE9EA), + textColor: Colors.red, + // icon: "assets/images/svg/apple-finder.svg", + )), + ], + ), + ], + ) + ], ); } } diff --git a/lib/presentation/book_appointment/widgets/doctor_card.dart b/lib/presentation/book_appointment/widgets/doctor_card.dart index 1a91f01..4cd147a 100644 --- a/lib/presentation/book_appointment/widgets/doctor_card.dart +++ b/lib/presentation/book_appointment/widgets/doctor_card.dart @@ -65,25 +65,28 @@ class DoctorCard extends StatelessWidget { .toString() .toText16(isBold: true, maxlines: 1), ).toShimmer2(isShow: isLoading), + ], + ), + SizedBox(height: 2.h), + Row( + children: [ + (isLoading + ? "Consultant Cardiologist" + : doctorsListResponseModel.speciality!.isNotEmpty + ? doctorsListResponseModel.speciality!.first + : "") + .toString() + .toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor, maxLine: 1) + .toShimmer2(isShow: isLoading), + SizedBox(width: 6.w), Image.network( - isLoading - ? "https://hmgwebservices.com/Images/flag/SYR.png" - : doctorsListResponseModel.nationalityFlagURL ?? "https://hmgwebservices.com/Images/flag/SYR.png", + isLoading ? "https://hmgwebservices.com/Images/flag/SYR.png" : doctorsListResponseModel.nationalityFlagURL ?? "https://hmgwebservices.com/Images/flag/SYR.png", width: 20.h, height: 15.h, fit: BoxFit.fill, ).toShimmer2(isShow: isLoading), ], ), - SizedBox(height: 2.h), - (isLoading - ? "Consultant Cardiologist" - : doctorsListResponseModel.speciality!.isNotEmpty - ? doctorsListResponseModel.speciality!.first - : "") - .toString() - .toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor, maxLine: 1) - .toShimmer2(isShow: isLoading), ], ), ), diff --git a/lib/presentation/contact_us/contact_us.dart b/lib/presentation/contact_us/contact_us.dart index 6890fb4..0322bfb 100644 --- a/lib/presentation/contact_us/contact_us.dart +++ b/lib/presentation/contact_us/contact_us.dart @@ -59,6 +59,7 @@ class ContactUs extends StatelessWidget { "Live chat option with HMG".needTranslation, ).onPress(() { locationUtils.getCurrentLocation(onSuccess: (value) { + contactUsViewModel.getLiveChatProjectsList(); Navigator.pop(context); Navigator.of(context).push( CustomPageRoute( diff --git a/lib/presentation/contact_us/live_chat_page.dart b/lib/presentation/contact_us/live_chat_page.dart index aced678..3b973f6 100644 --- a/lib/presentation/contact_us/live_chat_page.dart +++ b/lib/presentation/contact_us/live_chat_page.dart @@ -1,27 +1,178 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/utils/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/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.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:provider/provider.dart'; +import 'package:url_launcher/url_launcher.dart'; class LiveChatPage extends StatelessWidget { - const LiveChatPage({super.key}); + LiveChatPage({super.key}); + + String chatURL = ""; + + late AppState appState; @override Widget build(BuildContext context) { + appState = getIt.get(); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, - body: Column( - children: [ - Expanded( - child: CollapsingListView( - title: LocaleKeys.liveChat.tr(), - child: SingleChildScrollView(), + body: Consumer(builder: (context, contactUsVM, child) { + return Column( + children: [ + Expanded( + child: CollapsingListView( + title: LocaleKeys.liveChat.tr(), + child: Consumer(builder: (context, contactUsVM, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 16.h), + ListView.separated( + padding: EdgeInsets.only(top: 16.h), + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: contactUsVM.isLiveChatProjectsListLoading ? 5 : contactUsVM.liveChatProjectsList.length, + itemBuilder: (context, index) { + return contactUsVM.isLiveChatProjectsListLoading + ? 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) + : 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: DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: contactUsVM.selectedLiveChatProjectIndex == index ? AppColors.primaryRedColor : AppColors.whiteColor, + borderRadius: 16.r, + hasShadow: false, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ("${appState.isArabic() ? contactUsVM.liveChatProjectsList[index].projectNameN! : contactUsVM.liveChatProjectsList[index].projectName!}\n${contactUsVM.liveChatProjectsList[index].distanceInKilometers!} KM") + .needTranslation + .toText14(isBold: true, color: contactUsVM.selectedLiveChatProjectIndex == index ? AppColors.whiteColor : AppColors.textColor), + Transform.flip( + flipX: getIt.get().isArabic(), + child: Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon_small, + iconColor: contactUsVM.selectedLiveChatProjectIndex == index ? AppColors.whiteColor : AppColors.textColor, + width: 18.h, + height: 13.h, + fit: BoxFit.contain, + ), + ), + ], + ).paddingSymmetrical(16.h, 16.h), + ).onPress(() { + contactUsVM.setSelectedLiveChatProjectIndex(index); + chatURL = + "https://chat.hmg.com/Index.aspx?Name=${appState.getAuthenticatedUser()!.firstName}&PatientID=${appState.getAuthenticatedUser()!.patientId}&MobileNo=${appState.getAuthenticatedUser()!.mobileNumber}&Language=${appState.isArabic() ? 'ar' : 'en'}&WorkGroup=${contactUsVM.liveChatProjectsList[index].value}"; + debugPrint("Chat URL: $chatURL"); + }), + ).paddingSymmetrical(24.h, 0.h), + ), + ), + ); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + ), + SizedBox(height: 24.h), + ], + ); + }), + ), + ), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: true, + ), + child: CustomButton( + text: LocaleKeys.liveChat.tr(context: context), + onPressed: () async { + Uri uri = Uri.parse(chatURL); + launchUrl(uri, mode: LaunchMode.platformDefault, webOnlyWindowName: ""); + }, + backgroundColor: contactUsVM.selectedLiveChatProjectIndex == -1 ? AppColors.greyColor : AppColors.primaryRedColor, + borderColor: contactUsVM.selectedLiveChatProjectIndex == -1 ? AppColors.greyColor : AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 16, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 50.h, + ).paddingSymmetrical(24.h, 24.h), ), - ), - Container() - ], - ), + ], + ); + }), ); } } diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index 3347902..320d8d9 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -245,7 +245,7 @@ class _LandingPageState extends State { indicatorLayout: PageIndicatorLayout.COLOR, axisDirection: AxisDirection.right, controller: _controller, - itemHeight: 210 + 25, + itemHeight: 200.h, pagination: const SwiperPagination( alignment: Alignment.bottomCenter, margin: EdgeInsets.only(top: 210 + 8 + 24), @@ -439,7 +439,7 @@ class _LandingPageState extends State { }), SizedBox(height: 16.h), Container( - height: 120.h, + height: 121.h, decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r), child: Column( children: [ @@ -530,7 +530,7 @@ class _LandingPageState extends State { ], ).paddingSymmetrical(24.h, 0.h), SizedBox( - height: 280.h, + height: 340.h, child: ListView.separated( scrollDirection: Axis.horizontal, itemCount: LandingPageData.getServiceCardsList.length, @@ -567,9 +567,8 @@ class _LandingPageState extends State { void showQuickLogin(BuildContext context) { showCommonBottomSheetWithoutHeight( context, - title: "", + // title: "", isCloseButtonVisible: false, - child: StatefulBuilder( builder: (context, setState) { return QuickLogin( diff --git a/lib/presentation/lab/lab_result_via_clinic/lab_order_result_item.dart b/lib/presentation/lab/lab_result_via_clinic/lab_order_result_item.dart index 36cdc2b..bc1d6b1 100644 --- a/lib/presentation/lab/lab_result_via_clinic/lab_order_result_item.dart +++ b/lib/presentation/lab/lab_result_via_clinic/lab_order_result_item.dart @@ -46,7 +46,7 @@ class LabOrderResultItem extends StatelessWidget { padding: EdgeInsets.only(bottom: 8.h), child: '${tests!.description}'.toText14(weight: FontWeight.w500), ), - '${tests!.packageShortDescription}'.toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), + (tests!.packageShortDescription ?? "").toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight), SizedBox(height: 12.h), Row( mainAxisSize: MainAxisSize.max, @@ -58,22 +58,25 @@ class LabOrderResultItem extends StatelessWidget { fontSize: 24.f, fontWeight: FontWeight.w600, fontFamily: 'Poppins', - color: context.read().getColor( - tests?.calculatedResultFlag ?? "", - ), + color: tests!.checkIfGraphShouldBeDisplayed() + ? context.read().getColor( + tests?.calculatedResultFlag ?? "", + ) + : Colors.grey.shade700, letterSpacing: -2, ), ), ), SizedBox(width: 4.h,), Visibility( - visible: tests?.checkIfGraphShouldBeDisplayed() == true, + // visible: tests?.checkIfGraphShouldBeDisplayed() == true, + visible: true, child: Expanded( flex: 2, child: Visibility( visible: tests?.referanceRange != null, child: Text( - "(Reference range ${tests?.referanceRange})".needTranslation, + "(Reference range: ${tests?.referanceRange})".needTranslation, style: TextStyle( fontSize: 12.f, fontWeight: FontWeight.w500, diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index 84efb9c..0561b27 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -344,7 +344,7 @@ class _MedicalFilePageState extends State { Consumer(builder: (context, myAppointmentsVM, child) { // Provide an explicit height so the horizontal ListView has a bounded height return SizedBox( - height: 190.h, + height: 192.h, child: myAppointmentsVM.isMyAppointmentsLoading ? MedicalFileAppointmentCard( patientAppointmentHistoryResponseModel: PatientAppointmentHistoryResponseModel(), @@ -472,7 +472,7 @@ class _MedicalFilePageState extends State { child: Column( children: [ ListView.separated( - itemCount: prescriptionVM.patientPrescriptionOrders.length, + itemCount: prescriptionVM.patientPrescriptionOrders.length <= 2 ? prescriptionVM.patientPrescriptionOrders.length : 2, shrinkWrap: true, padding: EdgeInsets.only(left: 0, right: 8.w), physics: NeverScrollableScrollPhysics(), @@ -564,7 +564,7 @@ class _MedicalFilePageState extends State { fontSize: 12.f, fontWeight: FontWeight.w500, borderRadius: 12.r, - height: 56.h, + height: 40.h, icon: AppAssets.requests, iconColor: AppColors.primaryRedColor, iconSize: 16.w, @@ -581,7 +581,7 @@ class _MedicalFilePageState extends State { fontSize: 12.f, fontWeight: FontWeight.w500, borderRadius: 12.h, - height: 56.h, + height: 40.h, icon: AppAssets.all_medications_icon, iconColor: AppColors.primaryRedColor, iconSize: 16.h, diff --git a/lib/presentation/prescriptions/prescriptions_list_page.dart b/lib/presentation/prescriptions/prescriptions_list_page.dart index 1293c9c..725b06c 100644 --- a/lib/presentation/prescriptions/prescriptions_list_page.dart +++ b/lib/presentation/prescriptions/prescriptions_list_page.dart @@ -255,22 +255,22 @@ class _PrescriptionsListPageState extends State { Expanded( flex: 1, child: Container( - height: 40.h, - width: 40.h, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + height: 48.h, + width: 40.w, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.textColor, - borderRadius: 10.h, - ), + borderRadius: 12, + ), child: Padding( - padding: EdgeInsets.all(8.h), - child: Transform.flip( + padding: EdgeInsets.all(12.h), + child: Transform.flip( flipX: appState.isArabic(), child: Utils.buildSvgWithAssets( icon: AppAssets.forward_arrow_icon_small, iconColor: AppColors.whiteColor, - width: 10.h, - height: 10.h, - fit: BoxFit.contain, + // width: 8.w, + // height: 2, + fit: BoxFit.contain, ), ), ), diff --git a/lib/presentation/services/services_page.dart b/lib/presentation/services/services_page.dart deleted file mode 100644 index 24a259b..0000000 --- a/lib/presentation/services/services_page.dart +++ /dev/null @@ -1,23 +0,0 @@ -import 'package:flutter/material.dart'; -import 'package:hmg_patient_app_new/theme/colors.dart'; - -class ServicesPage extends StatelessWidget { - const ServicesPage({super.key}); - - @override - Widget build(BuildContext context) { - return Scaffold( - backgroundColor: AppColors.bgScaffoldColor, - appBar: AppBar( - title: const Text('Appointments'), - backgroundColor: AppColors.bgScaffoldColor, - ), - body: const Center( - child: Text( - 'Appointments Page', - style: TextStyle(fontSize: 24), - ), - ), - ); - } -} \ No newline at end of file diff --git a/lib/widgets/common_bottom_sheet.dart b/lib/widgets/common_bottom_sheet.dart index 1d857b5..7eeda9e 100644 --- a/lib/widgets/common_bottom_sheet.dart +++ b/lib/widgets/common_bottom_sheet.dart @@ -176,7 +176,7 @@ void showCommonBottomSheetWithoutHeight( ], ], ), - SizedBox(height: 16.h), + isCloseButtonVisible ? SizedBox(height: 16.h) : SizedBox.shrink(), child, ], ), From c88bff8cf22bf4cd4650982a1bf714c5f3be0255 Mon Sep 17 00:00:00 2001 From: Haroon Amjad <> Date: Mon, 17 Nov 2025 20:03:40 +0300 Subject: [PATCH 02/12] todo update --- lib/presentation/todo_section/todo_page.dart | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/lib/presentation/todo_section/todo_page.dart b/lib/presentation/todo_section/todo_page.dart index f2eb50c..11e258f 100644 --- a/lib/presentation/todo_section/todo_page.dart +++ b/lib/presentation/todo_section/todo_page.dart @@ -2,6 +2,8 @@ import 'dart:async'; import 'dart:developer'; import 'package:flutter/material.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/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; @@ -21,11 +23,16 @@ class ToDoPage extends StatefulWidget { } class _ToDoPageState extends State { + + late AppState appState; + @override void initState() { final TodoSectionViewModel todoSectionViewModel = context.read(); scheduleMicrotask(() async { - await todoSectionViewModel.initializeTodoSectionViewModel(); + if (appState.isAuthenticated) { + await todoSectionViewModel.initializeTodoSectionViewModel(); + } }); super.initState(); } @@ -51,6 +58,7 @@ class _ToDoPageState extends State { @override Widget build(BuildContext context) { + appState = getIt.get(); return CollapsingListView( title: "ToDo List".needTranslation, isLeading: false, From 73b05276f6658450edf1da393585bb3c5d9d0b99 Mon Sep 17 00:00:00 2001 From: Haroon Amjad <> Date: Mon, 17 Nov 2025 23:28:30 +0300 Subject: [PATCH 03/12] Feedback page implementation contd. --- .../contact_us/contact_us_view_model.dart | 6 + .../authentication/quick_login.dart | 197 ++++++++-------- .../doctor_filter/clinic_item.dart | 2 +- .../widgets/appointment_calendar.dart | 3 +- lib/presentation/contact_us/contact_us.dart | 10 +- .../contact_us/feedback_page.dart | 146 ++++++++++++ .../contact_us/live_chat_page.dart | 210 +++++++++--------- lib/theme/colors.dart | 2 +- lib/widgets/appbar/collapsing_list_view.dart | 22 +- lib/widgets/input_widget.dart | 4 +- 10 files changed, 378 insertions(+), 224 deletions(-) create mode 100644 lib/presentation/contact_us/feedback_page.dart diff --git a/lib/features/contact_us/contact_us_view_model.dart b/lib/features/contact_us/contact_us_view_model.dart index 2ad5737..bcb63c9 100644 --- a/lib/features/contact_us/contact_us_view_model.dart +++ b/lib/features/contact_us/contact_us_view_model.dart @@ -13,6 +13,7 @@ class ContactUsViewModel extends ChangeNotifier { bool isHMGLocationsListLoading = false; bool isHMGHospitalsListSelected = true; bool isLiveChatProjectsListLoading = false; + bool isSendFeedbackTabSelected = true; List hmgHospitalsLocationsList = []; List hmgPharmacyLocationsList = []; @@ -44,6 +45,11 @@ class ContactUsViewModel extends ChangeNotifier { notifyListeners(); } + setIsSendFeedbackTabSelected(bool isSelected) { + isSendFeedbackTabSelected = isSelected; + notifyListeners(); + } + Future getHMGLocations({Function(dynamic)? onSuccess, Function(String)? onError}) async { isHMGLocationsListLoading = true; hmgHospitalsLocationsList.clear(); diff --git a/lib/presentation/authentication/quick_login.dart b/lib/presentation/authentication/quick_login.dart index e03efc3..bdeb0ff 100644 --- a/lib/presentation/authentication/quick_login.dart +++ b/lib/presentation/authentication/quick_login.dart @@ -29,103 +29,108 @@ class QuickLoginState extends State { @override Widget build(BuildContext context) { NavigationService navigationService = getIt.get(); - return Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - widget.isDone - ? Column( - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - InkWell( - onTap: () { - navigationService.pop(); + return Padding( + padding: EdgeInsets.all(24.h), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + widget.isDone + ? Column( + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + InkWell( + onTap: () { + navigationService.pop(); + }, + child: Utils.buildSvgWithAssets(icon: AppAssets.cross_circle)), + ], + ), + Utils.showLottie(context: context, assetPath: AppAnimations.checkmark, width: 120, height: 120, repeat: true), + LocaleKeys.allSet.tr().toText16(textAlign: TextAlign.center, weight: FontWeight.w500) + // Text( + // ' TranslationBase.of(context).allSet', + // textAlign: TextAlign.center, + // style: context.dynamicTextStyle( + // fontSize: 16, + // fontWeight: FontWeight.w500, + // color: Colors.black, + // ), + // ), + ], + ) + : Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Image.asset(AppAssets.lockIcon, height: 100), + SizedBox(height: 10.h), + LocaleKeys.enableQuickLogin.tr().toText26(isBold: true), + // Text( + // ' TranslationBase.of(context).enableQuickLogin', + // style: context.dynamicTextStyle( + // fontSize: 26, + // fontWeight: FontWeight.bold, + // color: Colors.black, + // ), + // ), + SizedBox(height: 5.h), + LocaleKeys.enableQuickLogin.tr().toText16(color: AppColors.quickLoginColor), + // Description + // Text( + // 'TranslationBase.of(context).enableMsg', + // style: context.dynamicTextStyle( + // fontSize: 16, + // color: Color(0xFF666666), + // height: 1.5, + // ), + //), + const SizedBox(height: 24), + // Buttons + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Expanded( + child: CustomButton( + text: LocaleKeys.enableQuickLogin.tr(), + onPressed: () { + widget.onPressed(); }, - child: Utils.buildSvgWithAssets(icon: AppAssets.cross_circle)), - ], - ), - Utils.showLottie(context: context, assetPath: AppAnimations.checkmark, width: 120, height: 120, repeat: true), - LocaleKeys.allSet.tr().toText16(textAlign: TextAlign.center, weight: FontWeight.w500) - // Text( - // ' TranslationBase.of(context).allSet', - // textAlign: TextAlign.center, - // style: context.dynamicTextStyle( - // fontSize: 16, - // fontWeight: FontWeight.w500, - // color: Colors.black, - // ), - // ), - ], - ) - : Column( - mainAxisSize: MainAxisSize.min, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Image.asset(AppAssets.lockIcon, height: 100), - SizedBox(height: 10.h), - LocaleKeys.enableQuickLogin.tr().toText26(isBold: true), - // Text( - // ' TranslationBase.of(context).enableQuickLogin', - // style: context.dynamicTextStyle( - // fontSize: 26, - // fontWeight: FontWeight.bold, - // color: Colors.black, - // ), - // ), - SizedBox(height: 5.h), - LocaleKeys.enableQuickLogin.tr().toText16(color: AppColors.quickLoginColor), - // Description - // Text( - // 'TranslationBase.of(context).enableMsg', - // style: context.dynamicTextStyle( - // fontSize: 16, - // color: Color(0xFF666666), - // height: 1.5, - // ), - //), - const SizedBox(height: 24), - // Buttons - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Expanded( - child: CustomButton( - text: LocaleKeys.enableQuickLogin.tr(), - onPressed: () { - widget.onPressed(); - }, - backgroundColor: Color(0xffED1C2B), - borderColor: Color(0xffED1C2B), - textColor: Colors.white, - icon: AppAssets.apple_finder, - )), - ], - ), - SizedBox( - height: 16, - ), - Row( - mainAxisAlignment: MainAxisAlignment.end, - children: [ - Expanded( - child: CustomButton( - text: LocaleKeys.notNow.tr(), - onPressed: () { - cacheService.saveBool(key: CacheConst.quickLoginEnabled, value: false); - Navigator.pop(context, "true"); - }, - backgroundColor: Color(0xffFEE9EA), - borderColor: Color(0xffFEE9EA), - textColor: Colors.red, - // icon: "assets/images/svg/apple-finder.svg", - )), - ], - ), - ], - ) - ], + backgroundColor: Color(0xffED1C2B), + borderColor: Color(0xffED1C2B), + textColor: Colors.white, + icon: AppAssets.apple_finder, + height: 56.h, + )), + ], + ), + SizedBox( + height: 16.h, + ), + Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Expanded( + child: CustomButton( + text: LocaleKeys.notNow.tr(), + onPressed: () { + cacheService.saveBool(key: CacheConst.quickLoginEnabled, value: false); + Navigator.pop(context, "true"); + }, + backgroundColor: Color(0xffFEE9EA), + borderColor: Color(0xffFEE9EA), + textColor: Colors.red, + height: 56.h, + // icon: "assets/images/svg/apple-finder.svg", + )), + ], + ), + ], + ) + ], + ), ); } } diff --git a/lib/presentation/book_appointment/doctor_filter/clinic_item.dart b/lib/presentation/book_appointment/doctor_filter/clinic_item.dart index 0d5ba76..0771d1b 100644 --- a/lib/presentation/book_appointment/doctor_filter/clinic_item.dart +++ b/lib/presentation/book_appointment/doctor_filter/clinic_item.dart @@ -37,7 +37,7 @@ class ClinicItem extends StatelessWidget { Transform.flip( flipX: isArabic, child: Utils.buildSvgWithAssets( - icon: AppAssets.forward_arrow_icon, + icon: AppAssets.forward_arrow_icon_small, width: 15.h, height: 15.h, fit: BoxFit.contain, diff --git a/lib/presentation/book_appointment/widgets/appointment_calendar.dart b/lib/presentation/book_appointment/widgets/appointment_calendar.dart index d695ea6..54ff282 100644 --- a/lib/presentation/book_appointment/widgets/appointment_calendar.dart +++ b/lib/presentation/book_appointment/widgets/appointment_calendar.dart @@ -140,8 +140,9 @@ class _AppointmentCalendarState extends State { }, ), ), + SizedBox(height: 10.h), Transform.translate( - offset: const Offset(0.0, -20.0), + offset: const Offset(0.0, -10.0), child: selectedDateDisplay.toText16(weight: FontWeight.w500), ), //TODO: Add Next Day Span here diff --git a/lib/presentation/contact_us/contact_us.dart b/lib/presentation/contact_us/contact_us.dart index 0322bfb..f9ed0c3 100644 --- a/lib/presentation/contact_us/contact_us.dart +++ b/lib/presentation/contact_us/contact_us.dart @@ -10,6 +10,7 @@ import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/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/feedback_page.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'; @@ -51,7 +52,14 @@ class ContactUs extends StatelessWidget { AppAssets.checkin_location_icon, LocaleKeys.feedback.tr(), "Provide your feedback on our services".needTranslation, - ), + ).onPress(() { + Navigator.pop(context); + Navigator.of(context).push( + CustomPageRoute( + page: FeedbackPage(), + ), + ); + }), SizedBox(height: 16.h), checkInOptionCard( AppAssets.checkin_location_icon, diff --git a/lib/presentation/contact_us/feedback_page.dart b/lib/presentation/contact_us/feedback_page.dart new file mode 100644 index 0000000..19b248d --- /dev/null +++ b/lib/presentation/contact_us/feedback_page.dart @@ -0,0 +1,146 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/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/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.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/custom_tab_bar.dart'; +import 'package:hmg_patient_app_new/widgets/input_widget.dart'; +import 'package:provider/provider.dart'; + +class FeedbackPage extends StatelessWidget { + FeedbackPage({super.key}); + + late ContactUsViewModel contactUsViewModel; + + @override + Widget build(BuildContext context) { + contactUsViewModel = Provider.of(context); + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: Column( + children: [ + Expanded( + child: CollapsingListView( + title: LocaleKeys.feedback.tr(), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 16.h), + CustomTabBar( + activeTextColor: AppColors.primaryRedColor, + activeBackgroundColor: AppColors.primaryRedColor.withValues(alpha: .1), + tabs: [ + CustomTabBarModel(null, "Send".needTranslation), + CustomTabBarModel(null, "Status".needTranslation), + ], + onTabChange: (index) { + contactUsViewModel.setIsSendFeedbackTabSelected(index == 0); + }, + ).paddingSymmetrical(24.h, 0.h), + getSelectedTabWidget(context).paddingSymmetrical(24.h, 16.w), + ], + ), + ), + ), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: true, + ), + child: CustomButton( + text: LocaleKeys.submit.tr(context: context), + onPressed: () async {}, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 16, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 50.h, + icon: AppAssets.feedback, + iconColor: AppColors.whiteColor, + iconSize: 20.h, + ).paddingSymmetrical(24.h, 24.h), + ), + ], + ), + ); + } + + Widget getSelectedTabWidget(BuildContext context) { + if (contactUsViewModel.isSendFeedbackTabSelected) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.likeToHear.tr().toText14(weight: FontWeight.w500), + SizedBox(height: 16.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: false, + ), + child: Padding( + padding: EdgeInsets.all(16.h), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.ask_doctor_icon, width: 24.w, height: 24.h, iconColor: AppColors.greyTextColor), + SizedBox(width: 12.w), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + LocaleKeys.feedbackType.tr().toText16(color: AppColors.textColor, weight: FontWeight.w500), + LocaleKeys.select.tr().toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + ], + ), + ], + ), + Utils.buildSvgWithAssets(icon: AppAssets.arrow_down, width: 25.h, height: 25.h), + ], + ).onPress(() { + showCommonBottomSheetWithoutHeight(context, + title: "Select Feedback Type".needTranslation, child: Container(), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true); + }), + ], + ), + ), + ), + SizedBox(height: 16.h), + TextInputWidget( + labelText: "Subject".needTranslation, + hintText: "Type subject here".needTranslation, + // controller: searchEditingController, + isEnable: true, + prefix: null, + autoFocus: false, + isBorderAllowed: false, + keyboardType: TextInputType.text, + padding: EdgeInsets.symmetric( + vertical: ResponsiveExtension(10).h, + horizontal: ResponsiveExtension(15).h, + ), + ), + SizedBox(height: 16.h), + ], + ); + } else { + return Container(); + } + } +} diff --git a/lib/presentation/contact_us/live_chat_page.dart b/lib/presentation/contact_us/live_chat_page.dart index 3b973f6..7cbdee3 100644 --- a/lib/presentation/contact_us/live_chat_page.dart +++ b/lib/presentation/contact_us/live_chat_page.dart @@ -35,117 +35,115 @@ class LiveChatPage extends StatelessWidget { Expanded( child: CollapsingListView( title: LocaleKeys.liveChat.tr(), - child: Consumer(builder: (context, contactUsVM, child) { - return Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox(height: 16.h), - ListView.separated( - padding: EdgeInsets.only(top: 16.h), - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - itemCount: contactUsVM.isLiveChatProjectsListLoading ? 5 : contactUsVM.liveChatProjectsList.length, - itemBuilder: (context, index) { - return contactUsVM.isLiveChatProjectsListLoading - ? 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), - ], - ), - ], - ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 16.h), + ListView.separated( + padding: EdgeInsets.only(top: 16.h), + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: contactUsVM.isLiveChatProjectsListLoading ? 5 : contactUsVM.liveChatProjectsList.length, + itemBuilder: (context, index) { + return contactUsVM.isLiveChatProjectsListLoading + ? 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) - : 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: DecoratedBox( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: contactUsVM.selectedLiveChatProjectIndex == index ? AppColors.primaryRedColor : AppColors.whiteColor, - borderRadius: 16.r, - hasShadow: false, - ), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - ("${appState.isArabic() ? contactUsVM.liveChatProjectsList[index].projectNameN! : contactUsVM.liveChatProjectsList[index].projectName!}\n${contactUsVM.liveChatProjectsList[index].distanceInKilometers!} KM") - .needTranslation - .toText14(isBold: true, color: contactUsVM.selectedLiveChatProjectIndex == index ? AppColors.whiteColor : AppColors.textColor), - Transform.flip( - flipX: getIt.get().isArabic(), - child: Utils.buildSvgWithAssets( - icon: AppAssets.forward_arrow_icon_small, - iconColor: contactUsVM.selectedLiveChatProjectIndex == index ? AppColors.whiteColor : AppColors.textColor, - width: 18.h, - height: 13.h, - fit: BoxFit.contain, - ), + ), + ).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: DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: contactUsVM.selectedLiveChatProjectIndex == index ? AppColors.primaryRedColor : AppColors.whiteColor, + borderRadius: 16.r, + hasShadow: false, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + ("${appState.isArabic() ? contactUsVM.liveChatProjectsList[index].projectNameN! : contactUsVM.liveChatProjectsList[index].projectName!}\n${contactUsVM.liveChatProjectsList[index].distanceInKilometers!} KM") + .needTranslation + .toText14(isBold: true, color: contactUsVM.selectedLiveChatProjectIndex == index ? AppColors.whiteColor : AppColors.textColor), + Transform.flip( + flipX: getIt.get().isArabic(), + child: Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon_small, + iconColor: contactUsVM.selectedLiveChatProjectIndex == index ? AppColors.whiteColor : AppColors.textColor, + width: 18.h, + height: 13.h, + fit: BoxFit.contain, ), - ], - ).paddingSymmetrical(16.h, 16.h), - ).onPress(() { - contactUsVM.setSelectedLiveChatProjectIndex(index); - chatURL = - "https://chat.hmg.com/Index.aspx?Name=${appState.getAuthenticatedUser()!.firstName}&PatientID=${appState.getAuthenticatedUser()!.patientId}&MobileNo=${appState.getAuthenticatedUser()!.mobileNumber}&Language=${appState.isArabic() ? 'ar' : 'en'}&WorkGroup=${contactUsVM.liveChatProjectsList[index].value}"; - debugPrint("Chat URL: $chatURL"); - }), - ).paddingSymmetrical(24.h, 0.h), - ), + ), + ], + ).paddingSymmetrical(16.h, 16.h), + ).onPress(() { + contactUsVM.setSelectedLiveChatProjectIndex(index); + chatURL = + "https://chat.hmg.com/Index.aspx?Name=${appState.getAuthenticatedUser()!.firstName}&PatientID=${appState.getAuthenticatedUser()!.patientId}&MobileNo=${appState.getAuthenticatedUser()!.mobileNumber}&Language=${appState.isArabic() ? 'ar' : 'en'}&WorkGroup=${contactUsVM.liveChatProjectsList[index].value}"; + debugPrint("Chat URL: $chatURL"); + }), + ).paddingSymmetrical(24.h, 0.h), ), - ); - }, - separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), - ), - SizedBox(height: 24.h), - ], - ); - }), + ), + ); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + ), + SizedBox(height: 24.h), + ], + ), ), ), Container( diff --git a/lib/theme/colors.dart b/lib/theme/colors.dart index 6ccf8b1..0dc75c2 100644 --- a/lib/theme/colors.dart +++ b/lib/theme/colors.dart @@ -37,7 +37,7 @@ class AppColors { static const Color warningColorYellow = Color(0xFFF4A308); static const Color blackBgColor = Color(0xFF2E3039); static const blackColor = textColor; - static const Color inputLabelTextColor = Color(0xff898A8D); + static const Color inputLabelTextColor = Color(0xff898A8D); static const Color greyTextColor = Color(0xFF8F9AA3); static const Color lightGrayBGColor = Color(0x142E3039); diff --git a/lib/widgets/appbar/collapsing_list_view.dart b/lib/widgets/appbar/collapsing_list_view.dart index 8e07631..8fa5f99 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: MediaQuery.of(context).size.height * 0.12.h, + expandedHeight: MediaQuery.of(context).size.height * 0.11.h, stretch: true, systemOverlayStyle: SystemUiOverlayStyle(statusBarBrightness: Brightness.light), surfaceTintColor: Colors.transparent, @@ -92,8 +92,7 @@ class CollapsingListView extends StatelessWidget { t, )!, child: Padding( - padding: EdgeInsets.only( - left: appState.isArabic() ? 0 : leftPadding, right: appState.isArabic() ? leftPadding : 0, bottom: bottomPadding), + padding: EdgeInsets.only(left: appState.isArabic() ? 0 : leftPadding, right: appState.isArabic() ? leftPadding : 0, bottom: bottomPadding), child: Row( spacing: 4.h, children: [ @@ -110,18 +109,11 @@ class CollapsingListView extends StatelessWidget { color: AppColors.blackColor, letterSpacing: -0.5), ).expanded, - if (logout != null) - actionButton(context, t, title: "Logout".needTranslation, icon: AppAssets.logout).onPress(logout!), - if (report != null) - actionButton(context, t, title: "Report".needTranslation, icon: AppAssets.report_icon).onPress(report!), - if (history != null) - actionButton(context, t, title: "History".needTranslation, icon: AppAssets.insurance_history_icon) - .onPress(history!), - if (instructions != null) - actionButton(context, t, title: "Instructions".needTranslation, icon: AppAssets.requests).onPress(instructions!), - if (requests != null) - actionButton(context, t, title: "Requests".needTranslation, icon: AppAssets.insurance_history_icon) - .onPress(requests!), + if (logout != null) actionButton(context, t, title: "Logout".needTranslation, icon: AppAssets.logout).onPress(logout!), + if (report != null) actionButton(context, t, title: "Report".needTranslation, icon: AppAssets.report_icon).onPress(report!), + if (history != null) actionButton(context, t, title: "History".needTranslation, icon: AppAssets.insurance_history_icon).onPress(history!), + if (instructions != null) actionButton(context, t, title: "Instructions".needTranslation, icon: AppAssets.requests).onPress(instructions!), + if (requests != null) actionButton(context, t, title: "Requests".needTranslation, icon: AppAssets.insurance_history_icon).onPress(requests!), if (search != null) Utils.buildSvgWithAssets(icon: AppAssets.search_icon).onPress(search!).paddingOnly(right: 24), if (trailing != null) trailing!, ], diff --git a/lib/widgets/input_widget.dart b/lib/widgets/input_widget.dart index d943731..f69dcb6 100644 --- a/lib/widgets/input_widget.dart +++ b/lib/widgets/input_widget.dart @@ -205,9 +205,7 @@ class TextInputWidget extends StatelessWidget { initialDate: DateTime.now(), fontFamily: appState.getLanguageCode() == "ar" ? "GESSTwo" : "Poppins", okWidget: Padding(padding: EdgeInsets.only(right: 8.h), child: Utils.buildSvgWithAssets(icon: AppAssets.confirm, width: 24.h, height: 24.h)), - cancelWidget: Padding( - padding: EdgeInsets.only(right: 8.h), - child: Utils.buildSvgWithAssets(icon: AppAssets.cancel, iconColor: Colors.white, width: 24.h, height: 24.h)), + cancelWidget: Padding(padding: EdgeInsets.only(right: 8.h), child: Utils.buildSvgWithAssets(icon: AppAssets.cancel, iconColor: Colors.white, width: 24.h, height: 24.h)), onCalendarTypeChanged: (bool value) { isGregorian = value; }); From 9f845f1de847ec5ee3f308d09d299f556d445805 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Tue, 18 Nov 2025 15:31:17 +0300 Subject: [PATCH 04/12] Feedback page implementation contd. --- lib/core/utils/date_util.dart | 2 - .../contact_us/contact_us_view_model.dart | 3 + .../contact_us/feedback_page.dart | 148 ++++++++++++------ lib/presentation/home/navigation_screen.dart | 3 +- lib/widgets/input_widget.dart | 31 +++- 5 files changed, 129 insertions(+), 58 deletions(-) diff --git a/lib/core/utils/date_util.dart b/lib/core/utils/date_util.dart index d58aef6..cdd49cf 100644 --- a/lib/core/utils/date_util.dart +++ b/lib/core/utils/date_util.dart @@ -6,8 +6,6 @@ class DateUtil { /// convert String To Date function /// [date] String we want to convert static DateTime convertStringToDate(String? date) { - print("the date is $date"); - if (date == null) return DateTime.now(); if (date.isEmpty) return DateTime.now(); diff --git a/lib/features/contact_us/contact_us_view_model.dart b/lib/features/contact_us/contact_us_view_model.dart index bcb63c9..2901e7c 100644 --- a/lib/features/contact_us/contact_us_view_model.dart +++ b/lib/features/contact_us/contact_us_view_model.dart @@ -22,6 +22,8 @@ class ContactUsViewModel extends ChangeNotifier { int selectedLiveChatProjectIndex = -1; + List feedbackAttachmentList = []; + ContactUsViewModel({required this.contactUsRepo, required this.errorHandlerService, required this.appState}); initContactUsViewModel() { @@ -31,6 +33,7 @@ class ContactUsViewModel extends ChangeNotifier { hmgHospitalsLocationsList.clear(); hmgPharmacyLocationsList.clear(); liveChatProjectsList.clear(); + feedbackAttachmentList.clear(); getHMGLocations(); notifyListeners(); } diff --git a/lib/presentation/contact_us/feedback_page.dart b/lib/presentation/contact_us/feedback_page.dart index 19b248d..fa84f49 100644 --- a/lib/presentation/contact_us/feedback_page.dart +++ b/lib/presentation/contact_us/feedback_page.dart @@ -12,6 +12,7 @@ import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.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/custom_tab_bar.dart'; +import 'package:hmg_patient_app_new/widgets/image_picker.dart'; import 'package:hmg_patient_app_new/widgets/input_widget.dart'; import 'package:provider/provider.dart'; @@ -25,55 +26,61 @@ class FeedbackPage extends StatelessWidget { contactUsViewModel = Provider.of(context); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, - body: Column( - children: [ - Expanded( - child: CollapsingListView( - title: LocaleKeys.feedback.tr(), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox(height: 16.h), - CustomTabBar( - activeTextColor: AppColors.primaryRedColor, - activeBackgroundColor: AppColors.primaryRedColor.withValues(alpha: .1), - tabs: [ - CustomTabBarModel(null, "Send".needTranslation), - CustomTabBarModel(null, "Status".needTranslation), - ], - onTabChange: (index) { - contactUsViewModel.setIsSendFeedbackTabSelected(index == 0); - }, - ).paddingSymmetrical(24.h, 0.h), - getSelectedTabWidget(context).paddingSymmetrical(24.h, 16.w), - ], + body: Consumer(builder: (context, contactUsVM, child) { + return Column( + children: [ + Expanded( + child: CollapsingListView( + isLeading: Navigator.canPop(context), + title: LocaleKeys.feedback.tr(), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 16.h), + CustomTabBar( + activeTextColor: AppColors.primaryRedColor, + activeBackgroundColor: AppColors.primaryRedColor.withValues(alpha: .1), + tabs: [ + CustomTabBarModel(null, "Send".needTranslation), + CustomTabBarModel(null, "Status".needTranslation), + ], + onTabChange: (index) { + contactUsViewModel.setIsSendFeedbackTabSelected(index == 0); + }, + ).paddingSymmetrical(24.h, 0.h), + getSelectedTabWidget(context).paddingSymmetrical(24.h, 16.w), + ], + ), ), ), - ), - Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.h, - hasShadow: true, + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + customBorder: BorderRadius.only( + topLeft: Radius.circular(24.h), + topRight: Radius.circular(24.h), + ), + hasShadow: true, + ), + child: CustomButton( + text: LocaleKeys.submit.tr(context: context), + onPressed: () async {}, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 16, + fontWeight: FontWeight.w500, + borderRadius: 12, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 50.h, + icon: AppAssets.feedback, + iconColor: AppColors.whiteColor, + iconSize: 20.h, + ).paddingSymmetrical(24.h, 24.h), ), - child: CustomButton( - text: LocaleKeys.submit.tr(context: context), - onPressed: () async {}, - backgroundColor: AppColors.primaryRedColor, - borderColor: AppColors.primaryRedColor, - textColor: AppColors.whiteColor, - fontSize: 16, - fontWeight: FontWeight.w500, - borderRadius: 12, - padding: EdgeInsets.fromLTRB(10, 0, 10, 0), - height: 50.h, - icon: AppAssets.feedback, - iconColor: AppColors.whiteColor, - iconSize: 20.h, - ).paddingSymmetrical(24.h, 24.h), - ), - ], - ), + ], + ); + }), ); } @@ -87,7 +94,7 @@ class FeedbackPage extends StatelessWidget { Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.whiteColor, - borderRadius: 24.h, + borderRadius: 16.r, hasShadow: false, ), child: Padding( @@ -124,12 +131,28 @@ class FeedbackPage extends StatelessWidget { SizedBox(height: 16.h), TextInputWidget( labelText: "Subject".needTranslation, - hintText: "Type subject here".needTranslation, + hintText: "Enter subject here".needTranslation, + // controller: searchEditingController, + isEnable: true, + prefix: null, + autoFocus: false, + isBorderAllowed: false, + keyboardType: TextInputType.text, + padding: EdgeInsets.symmetric( + vertical: ResponsiveExtension(10).h, + horizontal: ResponsiveExtension(15).h, + ), + ), + SizedBox(height: 16.h), + TextInputWidget( + labelText: "Message".needTranslation, + hintText: "Enter message here".needTranslation, // controller: searchEditingController, isEnable: true, prefix: null, autoFocus: false, isBorderAllowed: false, + isMultiline: true, keyboardType: TextInputType.text, padding: EdgeInsets.symmetric( vertical: ResponsiveExtension(10).h, @@ -137,6 +160,35 @@ class FeedbackPage extends StatelessWidget { ), ), SizedBox(height: 16.h), + CustomButton( + text: LocaleKeys.selectAttachment.tr(context: context), + onPressed: () async { + ImageOptions.showImageOptionsNew( + context, + true, + (String image, file) { + print(image); + print(file); + Navigator.pop(context); + // setState(() { + // EReferralAttachment eReferralAttachment = new EReferralAttachment(fileName: 'image ${medicalReportImages.length + 1}.png', base64String: image); + // medicalReportImages.add(eReferralAttachment); + // }); + }, + ); + }, + backgroundColor: AppColors.secondaryLightRedColor, + borderColor: AppColors.secondaryLightRedColor, + textColor: AppColors.primaryRedColor, + fontSize: 14.f, + fontWeight: FontWeight.w500, + borderRadius: 10.r, + padding: EdgeInsets.symmetric(horizontal: 10.w), + height: isTablet || isFoldable ? 46.h : 40.h, + icon: AppAssets.file_icon, + iconColor: AppColors.primaryRedColor, + iconSize: 16.h, + ) ], ); } else { diff --git a/lib/presentation/home/navigation_screen.dart b/lib/presentation/home/navigation_screen.dart index 0599969..18803cc 100644 --- a/lib/presentation/home/navigation_screen.dart +++ b/lib/presentation/home/navigation_screen.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/dependencies.dart'; import 'package:hmg_patient_app_new/presentation/book_appointment/book_appointment_page.dart'; +import 'package:hmg_patient_app_new/presentation/contact_us/feedback_page.dart'; import 'package:hmg_patient_app_new/presentation/hmg_services/services_page.dart'; import 'package:hmg_patient_app_new/presentation/home/landing_page.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart'; @@ -29,7 +30,7 @@ class _LandingNavigationState extends State { physics: const NeverScrollableScrollPhysics(), children: [ const LandingPage(), - appState.isAuthenticated ? MedicalFilePage() : /* need add feedback page */ const LandingPage(), + appState.isAuthenticated ? MedicalFilePage() : /* need add feedback page */ FeedbackPage(), BookAppointmentPage(), const ToDoPage(), appState.isAuthenticated ? /* need add news page */ ServicesPage() : const LandingPage(), diff --git a/lib/widgets/input_widget.dart b/lib/widgets/input_widget.dart index f69dcb6..4b3f091 100644 --- a/lib/widgets/input_widget.dart +++ b/lib/widgets/input_widget.dart @@ -43,6 +43,11 @@ class TextInputWidget extends StatelessWidget { final Color? labelColor; final Function(String)? onSubmitted; + // new multiline options + final bool isMultiline; + final int minLines; + final int maxLines; + // final List countryList; // final Function(Country)? onCountryChange; @@ -73,10 +78,14 @@ class TextInputWidget extends StatelessWidget { this.isWalletAmountInput = false, this.suffix, this.labelColor, - this.onSubmitted - // this.countryList = const [], - // this.onCountryChange, - }); + this.onSubmitted, + // multiline defaults + this.isMultiline = false, + this.minLines = 3, + this.maxLines = 6, + // this.countryList = const [], + // this.onCountryChange, + }); final FocusNode _focusNode = FocusNode(); @@ -113,7 +122,7 @@ class TextInputWidget extends StatelessWidget { children: [ Container( padding: padding, - height: 64.h, + height: isMultiline ? null : 64.h, alignment: Alignment.center, decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: Colors.white, @@ -238,7 +247,7 @@ class TextInputWidget extends StatelessWidget { return TextField( enabled: isEnable, scrollPadding: EdgeInsets.zero, - keyboardType: keyboardType, + keyboardType: isMultiline ? TextInputType.multiline : keyboardType, controller: controller, readOnly: isReadOnly, textAlignVertical: TextAlignVertical.top, @@ -253,7 +262,15 @@ class TextInputWidget extends StatelessWidget { FocusManager.instance.primaryFocus?.unfocus(); }, onSubmitted: onSubmitted, - style: TextStyle(fontSize: fontS, height: isWalletAmountInput! ? 1 / 4 : 0, fontWeight: FontWeight.w500, color: AppColors.textColor, letterSpacing: -1), + minLines: isMultiline ? minLines : 1, + maxLines: isMultiline ? maxLines : 1, + style: TextStyle( + fontSize: fontS, + height: isMultiline ? 1.2 : (isWalletAmountInput! ? 1 / 4 : 0), + fontWeight: FontWeight.w500, + color: AppColors.textColor, + letterSpacing: -1, + ), decoration: InputDecoration( isDense: true, hintText: hintText, From a4e55cb6df67f22c67221a2905d4f98f571c2a33 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Tue, 18 Nov 2025 15:53:52 +0300 Subject: [PATCH 05/12] fixes --- .../history/widget/ambulance_history_item.dart | 2 ++ .../emergency_services/history/widget/rrt_item.dart | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/presentation/emergency_services/history/widget/ambulance_history_item.dart b/lib/presentation/emergency_services/history/widget/ambulance_history_item.dart index 42bf257..180ca48 100644 --- a/lib/presentation/emergency_services/history/widget/ambulance_history_item.dart +++ b/lib/presentation/emergency_services/history/widget/ambulance_history_item.dart @@ -60,6 +60,8 @@ class AmbulanceHistoryItem extends StatelessWidget { borderColor: AppColors.primaryRedColor, textColor: Colors.white, icon: AppAssets.cancel, + iconSize: 20.h, + height: 40.h, ), ], ).paddingAll(16.h), diff --git a/lib/presentation/emergency_services/history/widget/rrt_item.dart b/lib/presentation/emergency_services/history/widget/rrt_item.dart index 84108fb..61ecced 100644 --- a/lib/presentation/emergency_services/history/widget/rrt_item.dart +++ b/lib/presentation/emergency_services/history/widget/rrt_item.dart @@ -44,7 +44,7 @@ class RRTItem extends StatelessWidget { chip("Rapid Response Team(RRT)".needTranslation, AppAssets.ic_rrt_vehicle, AppColors.blackBgColor), ], ), - + SizedBox(height: 4.h), if (order.statusId == 1 || order.statusId == 2) CustomButton( text: "Cancel Request".needTranslation, @@ -55,6 +55,8 @@ class RRTItem extends StatelessWidget { borderColor: AppColors.primaryRedColor, textColor: Colors.white, icon: AppAssets.cancel, + iconSize: 20.h, + height: 40.h, ), ], ).paddingAll(16.h), From b13adde9d2478f8688b2dddbfe12a5c15c6b94cd Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Wed, 19 Nov 2025 13:42:24 +0300 Subject: [PATCH 06/12] send feedback implementation done --- lib/core/api_consts.dart | 2 +- lib/features/contact_us/contact_us_repo.dart | 48 ++++ .../contact_us/contact_us_view_model.dart | 85 ++++++ .../contact_us/models/feedback_type.dart | 11 + .../req_models/request_insert_coc_item.dart | 137 ++++++++++ .../widgets/appointment_card.dart | 12 +- lib/presentation/contact_us/contact_us.dart | 4 + .../contact_us/feedback_page.dart | 242 ++++++++++++++++-- .../feedback_appointment_selection.dart | 70 +++++ .../wallet_payment_confirm_page.dart | 4 +- .../hmg_services/services_page.dart | 2 +- lib/presentation/home/landing_page.dart | 5 +- 12 files changed, 596 insertions(+), 26 deletions(-) create mode 100644 lib/features/contact_us/models/feedback_type.dart create mode 100644 lib/features/contact_us/models/req_models/request_insert_coc_item.dart create mode 100644 lib/presentation/contact_us/widgets/feedback_appointment_selection.dart diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 982e1ca..6a4a64a 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -847,7 +847,7 @@ class ApiConsts { static final String addAdvanceNumberRequest = 'Services/PayFort_Serv.svc/REST/AddAdvancedNumberRequest'; // ************ static values for Api **************** - static final double appVersionID = 18.7; + static final double appVersionID = 50.0; static final int appChannelId = 3; static final String appIpAddress = "10.20.10.20"; static final String appGeneralId = "Cs2020@2016\$2958"; diff --git a/lib/features/contact_us/contact_us_repo.dart b/lib/features/contact_us/contact_us_repo.dart index b3e42c3..3e96f91 100644 --- a/lib/features/contact_us/contact_us_repo.dart +++ b/lib/features/contact_us/contact_us_repo.dart @@ -3,14 +3,18 @@ import 'package:hmg_patient_app_new/core/api/api_client.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; +import 'package:hmg_patient_app_new/features/contact_us/models/req_models/request_insert_coc_item.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/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; abstract class ContactUsRepo { Future>>> getHMGLocations(); Future>>> getLiveChatProjectsList(); + + Future>> insertCOCItem({required RequestInsertCOCItem requestInsertCOCItem, PatientAppointmentHistoryResponseModel? patientSelectedAppointment}); } class ContactUsRepoImp implements ContactUsRepo { @@ -92,4 +96,48 @@ class ContactUsRepoImp implements ContactUsRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future>> insertCOCItem({required RequestInsertCOCItem requestInsertCOCItem, PatientAppointmentHistoryResponseModel? patientSelectedAppointment}) async { + final Map body = requestInsertCOCItem.toJson(); + + if (patientSelectedAppointment != null) { + body['AppoinmentNo'] = patientSelectedAppointment.appointmentNo; + body['AppointmentDate'] = patientSelectedAppointment.appointmentDate; + body['ClinicID'] = patientSelectedAppointment.clinicID; + body['ClinicName'] = patientSelectedAppointment.clinicName; + body['DoctorID'] = patientSelectedAppointment.doctorID; + body['DoctorName'] = patientSelectedAppointment.doctorNameObj; + body['ProjectName'] = patientSelectedAppointment.projectName; + } + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + SEND_FEEDBACK, + body: body, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: response, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } } diff --git a/lib/features/contact_us/contact_us_view_model.dart b/lib/features/contact_us/contact_us_view_model.dart index 2901e7c..1185700 100644 --- a/lib/features/contact_us/contact_us_view_model.dart +++ b/lib/features/contact_us/contact_us_view_model.dart @@ -1,8 +1,15 @@ +import 'dart:io'; + import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/features/contact_us/contact_us_repo.dart'; +import 'package:hmg_patient_app_new/features/contact_us/models/feedback_type.dart'; +import 'package:hmg_patient_app_new/features/contact_us/models/req_models/request_insert_coc_item.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/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; class ContactUsViewModel extends ChangeNotifier { @@ -24,6 +31,19 @@ class ContactUsViewModel extends ChangeNotifier { List feedbackAttachmentList = []; + PatientAppointmentHistoryResponseModel? patientFeedbackSelectedAppointment; + + List feedbackTypeList = [ + FeedbackType(id: 1, nameEN: "Complaint for appointment", nameAR: 'شكوى على موعد'), + FeedbackType(id: 2, nameEN: "Complaint without appointment", nameAR: 'شكوى بدون موعد'), + FeedbackType(id: 3, nameEN: "Question", nameAR: 'سؤال'), + FeedbackType(id: 4, nameEN: "Appreciation", nameAR: 'تقدير'), + FeedbackType(id: 6, nameEN: "Suggestion", nameAR: 'إقتراح'), + FeedbackType(id: 5, nameEN: "Not classified", nameAR: 'غير محدد'), + ]; + + FeedbackType selectedFeedbackType = FeedbackType(id: 5, nameEN: "Not classified", nameAR: 'غير محدد'); + ContactUsViewModel({required this.contactUsRepo, required this.errorHandlerService, required this.appState}); initContactUsViewModel() { @@ -34,6 +54,8 @@ class ContactUsViewModel extends ChangeNotifier { hmgPharmacyLocationsList.clear(); liveChatProjectsList.clear(); feedbackAttachmentList.clear(); + selectedFeedbackType = FeedbackType(id: 5, nameEN: "Not classified", nameAR: 'غير محدد'); + setPatientFeedbackSelectedAppointment(null); getHMGLocations(); notifyListeners(); } @@ -53,6 +75,26 @@ class ContactUsViewModel extends ChangeNotifier { notifyListeners(); } + setSelectedFeedbackType(FeedbackType feedbackType) { + selectedFeedbackType = feedbackType; + notifyListeners(); + } + + addFeedbackAttachment(String attachmentPath) { + feedbackAttachmentList.add(attachmentPath); + notifyListeners(); + } + + removeFeedbackAttachment(String attachmentPath) { + feedbackAttachmentList.remove(attachmentPath); + notifyListeners(); + } + + setPatientFeedbackSelectedAppointment(PatientAppointmentHistoryResponseModel? appointment) { + patientFeedbackSelectedAppointment = appointment; + notifyListeners(); + } + Future getHMGLocations({Function(dynamic)? onSuccess, Function(String)? onError}) async { isHMGLocationsListLoading = true; hmgHospitalsLocationsList.clear(); @@ -110,4 +152,47 @@ class ContactUsViewModel extends ChangeNotifier { }, ); } + + Future insertCOCItem({required String subject, required String message, Function(dynamic)? onSuccess, Function(String)? onError}) async { + RequestInsertCOCItem requestInsertCOCItem = RequestInsertCOCItem(); + requestInsertCOCItem.attachment = feedbackAttachmentList.isNotEmpty ? feedbackAttachmentList.first : ""; + requestInsertCOCItem.title = subject; + requestInsertCOCItem.details = message; + requestInsertCOCItem.cOCTypeName = selectedFeedbackType.id.toString(); + requestInsertCOCItem.formTypeID = selectedFeedbackType.id.toString(); + requestInsertCOCItem.mobileNo = "966${Utils.getPhoneNumberWithoutZero(appState.getAuthenticatedUser()!.mobileNumber!)}"; + requestInsertCOCItem.isUserLoggedIn = true; + requestInsertCOCItem.projectID = 0; + requestInsertCOCItem.patientName = "${appState.getAuthenticatedUser()!.firstName!} ${appState.getAuthenticatedUser()!.lastName!}"; + requestInsertCOCItem.fileName = ""; + requestInsertCOCItem.appVersion = ApiConsts.appVersionID; + requestInsertCOCItem.uILanguage = appState.isArabic() ? "ar" : "en"; //TODO Change it to be dynamic + requestInsertCOCItem.browserInfo = Platform.localHostname; + requestInsertCOCItem.deviceInfo = Platform.localHostname; + requestInsertCOCItem.resolution = "400x847"; + requestInsertCOCItem.projectID = 0; + requestInsertCOCItem.tokenID = "C0c@@dm!n?T&A&A@Barcha202029582948"; + requestInsertCOCItem.identificationNo = int.parse(appState.getAuthenticatedUser()!.patientIdentificationNo!); + if (BASE_URL.contains('uat')) { + requestInsertCOCItem.forDemo = true; + } else { + requestInsertCOCItem.forDemo = false; + } + + final result = await contactUsRepo.insertCOCItem(requestInsertCOCItem: requestInsertCOCItem, patientSelectedAppointment: patientFeedbackSelectedAppointment); + + 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) { + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } + }, + ); + } } diff --git a/lib/features/contact_us/models/feedback_type.dart b/lib/features/contact_us/models/feedback_type.dart new file mode 100644 index 0000000..ff1025a --- /dev/null +++ b/lib/features/contact_us/models/feedback_type.dart @@ -0,0 +1,11 @@ +class FeedbackType { + final int id; + final String nameEN; + final String nameAR; + + FeedbackType({ + required this.id, + required this.nameEN, + required this.nameAR, + }); +} diff --git a/lib/features/contact_us/models/req_models/request_insert_coc_item.dart b/lib/features/contact_us/models/req_models/request_insert_coc_item.dart new file mode 100644 index 0000000..e285999 --- /dev/null +++ b/lib/features/contact_us/models/req_models/request_insert_coc_item.dart @@ -0,0 +1,137 @@ +class RequestInsertCOCItem { + bool? isUserLoggedIn; + String? mobileNo; + int? identificationNo; + int? patientID; + int? patientOutSA; + int? patientTypeID; + String? tokenID; + String? patientName; + int? projectID; + String? fileName; + String? attachment; + String? uILanguage; + String? browserInfo; + String? cOCTypeName; + String? formTypeID; + String? details; + String? deviceInfo; + String? deviceType; + String? title; + String? resolution; + double? versionID; + int? channel; + int? languageID; + String? iPAdress; + String? generalid; + String? sessionID; + bool? isDentalAllowedBackend; + int? deviceTypeID; + int? patientType; + double? appVersion; + bool? forDemo; + + RequestInsertCOCItem( + {this.isUserLoggedIn, + this.mobileNo, + this.identificationNo, + this.patientID, + this.patientOutSA, + this.patientTypeID, + this.tokenID, + this.patientName, + this.projectID, + this.fileName, + this.attachment, + this.uILanguage, + this.browserInfo, + this.cOCTypeName, + this.formTypeID, + this.details, + this.deviceInfo, + this.deviceType, + this.title, + this.resolution, + this.versionID, + this.channel, + this.languageID, + this.iPAdress, + this.generalid, + this.sessionID, + this.isDentalAllowedBackend, + this.deviceTypeID, + this.patientType, + this.appVersion, + this.forDemo}); + + RequestInsertCOCItem.fromJson(Map json) { + isUserLoggedIn = json['IsUserLoggedIn']; + mobileNo = json['MobileNo']; + identificationNo = json['IdentificationNo']; + patientID = json['PatientID']; + patientOutSA = json['PatientOutSA']; + patientTypeID = json['PatientTypeID']; + tokenID = json['TokenID']; + patientName = json['PatientName']; + projectID = json['ProjectID']; + fileName = json['FileName']; + attachment = json['Attachment']; + uILanguage = json['UILanguage']; + browserInfo = json['BrowserInfo']; + cOCTypeName = json['COCTypeName']; + formTypeID = json['FormTypeID']; + details = json['Details']; + deviceInfo = json['DeviceInfo']; + deviceType = json['DeviceType']; + title = json['Title']; + resolution = json['Resolution']; + versionID = json['VersionID']; + channel = json['Channel']; + languageID = json['LanguageID']; + iPAdress = json['IPAdress']; + generalid = json['generalid']; + sessionID = json['SessionID']; + isDentalAllowedBackend = json['isDentalAllowedBackend']; + deviceTypeID = json['DeviceTypeID']; + patientType = json['PatientType']; + appVersion = json['AppVersion']; + forDemo = json['ForDemo']; + } + + Map toJson() { + final Map data = new Map(); + data['IsUserLoggedIn'] = this.isUserLoggedIn; + data['MobileNo'] = this.mobileNo; + data['IdentificationNo'] = this.identificationNo; + data['PatientID'] = this.patientID; + data['PatientOutSA'] = this.patientOutSA; + data['PatientTypeID'] = this.patientTypeID; + data['TokenID'] = this.tokenID; + data['PatientName'] = this.patientName; + data['ProjectID'] = this.projectID; + data['FileName'] = this.fileName; + data['Attachment'] = this.attachment; + data['UILanguage'] = this.uILanguage; + data['BrowserInfo'] = this.browserInfo; + data['COCTypeName'] = this.cOCTypeName; + data['FormTypeID'] = this.formTypeID; + data['Details'] = this.details; + data['DeviceInfo'] = this.deviceInfo; + data['DeviceType'] = this.deviceType; + data['Title'] = this.title; + data['Resolution'] = this.resolution; + data['VersionID'] = this.versionID; + data['Channel'] = this.channel; + data['LanguageID'] = this.languageID; + data['IPAdress'] = this.iPAdress; + data['generalid'] = this.generalid; + data['SessionID'] = this.sessionID; + data['isDentalAllowedBackend'] = this.isDentalAllowedBackend; + data['DeviceTypeID'] = this.deviceTypeID; + data['PatientType'] = this.patientType; + data['AppVersion'] = this.appVersion; + data['ForDemo'] = this.forDemo; + + return data; + } +} diff --git a/lib/presentation/appointments/widgets/appointment_card.dart b/lib/presentation/appointments/widgets/appointment_card.dart index e451088..3cf56a6 100644 --- a/lib/presentation/appointments/widgets/appointment_card.dart +++ b/lib/presentation/appointments/widgets/appointment_card.dart @@ -10,6 +10,7 @@ import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctors_list_response_model.dart'; +import 'package:hmg_patient_app_new/features/contact_us/contact_us_view_model.dart'; import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; @@ -33,7 +34,9 @@ class AppointmentCard extends StatelessWidget { final bool isFromHomePage; final bool isFromMedicalReport; final bool isForEyeMeasurements; + final bool isForFeedback; final MedicalFileViewModel? medicalFileViewModel; + final ContactUsViewModel? contactUsViewModel; final BookAppointmentsViewModel bookAppointmentsViewModel; const AppointmentCard({ @@ -45,7 +48,9 @@ class AppointmentCard extends StatelessWidget { this.isFromHomePage = false, this.isFromMedicalReport = false, this.isForEyeMeasurements = false, + this.isForFeedback = false, this.medicalFileViewModel, + this.contactUsViewModel, }); @override @@ -179,7 +184,11 @@ class AppointmentCard extends StatelessWidget { return CustomButton( text: 'Select appointment'.needTranslation, onPressed: () { - medicalFileViewModel!.setSelectedMedicalReportAppointment(patientAppointmentHistoryResponseModel); + if (isForFeedback) { + contactUsViewModel!.setPatientFeedbackSelectedAppointment(patientAppointmentHistoryResponseModel); + } else { + medicalFileViewModel!.setSelectedMedicalReportAppointment(patientAppointmentHistoryResponseModel); + } Navigator.pop(context, false); }, backgroundColor: AppColors.secondaryLightRedColor, @@ -313,6 +322,7 @@ class AppointmentCard extends StatelessWidget { } void _goToDetails(BuildContext context) { + if (isFromMedicalReport) return; if (isForEyeMeasurements) { Navigator.of(context).push( CustomPageRoute( diff --git a/lib/presentation/contact_us/contact_us.dart b/lib/presentation/contact_us/contact_us.dart index f9ed0c3..d7ea9c5 100644 --- a/lib/presentation/contact_us/contact_us.dart +++ b/lib/presentation/contact_us/contact_us.dart @@ -9,6 +9,7 @@ import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/contact_us/contact_us_view_model.dart'; +import 'package:hmg_patient_app_new/features/contact_us/models/feedback_type.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/contact_us/feedback_page.dart'; import 'package:hmg_patient_app_new/presentation/contact_us/find_us_page.dart'; @@ -53,6 +54,9 @@ class ContactUs extends StatelessWidget { LocaleKeys.feedback.tr(), "Provide your feedback on our services".needTranslation, ).onPress(() { + contactUsViewModel.setSelectedFeedbackType( + FeedbackType(id: 5, nameEN: "Not classified", nameAR: 'غير محدد'), + ); Navigator.pop(context); Navigator.of(context).push( CustomPageRoute( diff --git a/lib/presentation/contact_us/feedback_page.dart b/lib/presentation/contact_us/feedback_page.dart index fa84f49..db7c218 100644 --- a/lib/presentation/contact_us/feedback_page.dart +++ b/lib/presentation/contact_us/feedback_page.dart @@ -1,29 +1,46 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/utils/date_util.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/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/contact_us/models/feedback_type.dart'; +import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_card.dart'; +import 'package:hmg_patient_app_new/presentation/contact_us/widgets/feedback_appointment_selection.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/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; import 'package:hmg_patient_app_new/widgets/image_picker.dart'; import 'package:hmg_patient_app_new/widgets/input_widget.dart'; +import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; +import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:provider/provider.dart'; class FeedbackPage extends StatelessWidget { FeedbackPage({super.key}); late ContactUsViewModel contactUsViewModel; + late MedicalFileViewModel medicalFileViewModel; + + final TextEditingController subjectTextController = TextEditingController(); + final TextEditingController messageTextController = TextEditingController(); @override Widget build(BuildContext context) { - contactUsViewModel = Provider.of(context); + contactUsViewModel = Provider.of(context, listen: false); + medicalFileViewModel = Provider.of(context, listen: false); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: Consumer(builder: (context, contactUsVM, child) { @@ -64,7 +81,42 @@ class FeedbackPage extends StatelessWidget { ), child: CustomButton( text: LocaleKeys.submit.tr(context: context), - onPressed: () async {}, + onPressed: () async { + if (subjectTextController.text.isEmpty) { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: LocaleKeys.emptySubject.tr(context: context)), + ); + return; + } + if (messageTextController.text.isEmpty) { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: LocaleKeys.emptyMessage.tr(context: context)), + ); + return; + } + LoaderBottomSheet.showLoader(loadingText: "Sending Feedback...".needTranslation); + contactUsViewModel.insertCOCItem( + subject: subjectTextController.text, + message: messageTextController.text, + onSuccess: (val) { + LoaderBottomSheet.hideLoader(); + subjectTextController.clear(); + messageTextController.clear(); + contactUsViewModel.setPatientFeedbackSelectedAppointment(null); + showCommonBottomSheetWithoutHeight(context, child: Utils.getSuccessWidget(loadingText: LocaleKeys.success.tr(context: context)), callBackFunc: () { + Navigator.pop(context); + }); + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getSuccessWidget(loadingText: err), + ); + }); + }, backgroundColor: AppColors.primaryRedColor, borderColor: AppColors.primaryRedColor, textColor: AppColors.whiteColor, @@ -113,26 +165,142 @@ class FeedbackPage extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ LocaleKeys.feedbackType.tr().toText16(color: AppColors.textColor, weight: FontWeight.w500), - LocaleKeys.select.tr().toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), - ], - ), - ], + (getIt.get().isArabic() ? contactUsViewModel.selectedFeedbackType.nameAR : contactUsViewModel.selectedFeedbackType.nameEN) + .toText14(color: AppColors.greyTextColor, weight: FontWeight.w500), + ], + ), + ], + ), + Utils.buildSvgWithAssets(icon: AppAssets.arrow_down, width: 25.h, height: 25.h), + ], + ).onPress(() { + showCommonBottomSheetWithoutHeight(context, + title: "Select Feedback Type".needTranslation, + child: Container( + width: double.infinity, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24), + child: ListView.builder( + itemCount: contactUsViewModel.feedbackTypeList.length, + physics: NeverScrollableScrollPhysics(), + padding: EdgeInsets.only(top: 8, bottom: 8), + shrinkWrap: true, + itemBuilder: (innerContext, index) { + return Theme( + data: Theme.of(context).copyWith( + listTileTheme: ListTileThemeData(horizontalTitleGap: 4), + ), + child: RadioListTile( + title: Text( + getIt.get().isArabic() ? contactUsViewModel.feedbackTypeList[index].nameAR : contactUsViewModel.feedbackTypeList[index].nameEN, + style: TextStyle( + fontSize: 16.h, + fontWeight: FontWeight.w500, + ), + ), + value: contactUsViewModel.feedbackTypeList[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: contactUsViewModel.selectedFeedbackType, + onChanged: (FeedbackType? newValue) async { + Navigator.pop(context); + contactUsViewModel.setSelectedFeedbackType(newValue!); + if (contactUsViewModel.selectedFeedbackType.id == 1) { + LoaderBottomSheet.showLoader(loadingText: "Loading appointments list...".needTranslation); + await medicalFileViewModel.getPatientMedicalReportAppointmentsList(onSuccess: (val) async { + LoaderBottomSheet.hideLoader(); + bool? value = await Navigator.of(context).push( + CustomPageRoute( + page: FeedbackAppointmentSelection(), + fullScreenDialog: true, + direction: AxisDirection.down, + ), + ); + if (value != null) { + // showConfirmRequestMedicalReportBottomSheet(); + } + }, onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: "You do not have any appointments to submit a feedback.".needTranslation), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }); + } else { + contactUsViewModel.setPatientFeedbackSelectedAppointment(null); + } + }, + ), + ); + }, + ), ), - Utils.buildSvgWithAssets(icon: AppAssets.arrow_down, width: 25.h, height: 25.h), - ], - ).onPress(() { - showCommonBottomSheetWithoutHeight(context, - title: "Select Feedback Type".needTranslation, child: Container(), callBackFunc: () {}, isFullScreen: false, isCloseButtonVisible: true); - }), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true); + }), + ]), + ), + ), + if (contactUsViewModel.patientFeedbackSelectedAppointment != null) ...[ + SizedBox(height: 16.h), + "Selected Appointment:".needTranslation.toText16(isBold: true), + SizedBox(height: 8.h), + Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.r, + hasShadow: false, + ), + padding: EdgeInsets.all(16.h), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Image.network( + contactUsViewModel.patientFeedbackSelectedAppointment!.doctorImageURL!, + width: 63.h, + height: 63.h, + fit: BoxFit.cover, + ).circle(100).toShimmer2(isShow: false), + SizedBox(width: 16.h), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + (contactUsViewModel.patientFeedbackSelectedAppointment!.doctorNameObj!).toText16(isBold: true, maxlines: 1).toShimmer2(isShow: false), + SizedBox(height: 8.h), + Wrap( + direction: Axis.horizontal, + spacing: 3.h, + runSpacing: 4.h, + children: [ + AppCustomChipWidget(labelText: contactUsViewModel.patientFeedbackSelectedAppointment!.clinicName!).toShimmer2(isShow: false), + AppCustomChipWidget(labelText: contactUsViewModel.patientFeedbackSelectedAppointment!.projectName!).toShimmer2(isShow: false), + AppCustomChipWidget( + icon: AppAssets.appointment_calendar_icon, + labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(contactUsViewModel.patientFeedbackSelectedAppointment!.appointmentDate), false), + ).toShimmer2(isShow: false), + ], + ), + ], + ), + ), ], ), ), - ), + ], SizedBox(height: 16.h), TextInputWidget( labelText: "Subject".needTranslation, hintText: "Enter subject here".needTranslation, - // controller: searchEditingController, + controller: subjectTextController, isEnable: true, prefix: null, autoFocus: false, @@ -147,7 +315,7 @@ class FeedbackPage extends StatelessWidget { TextInputWidget( labelText: "Message".needTranslation, hintText: "Enter message here".needTranslation, - // controller: searchEditingController, + controller: messageTextController, isEnable: true, prefix: null, autoFocus: false, @@ -170,10 +338,7 @@ class FeedbackPage extends StatelessWidget { print(image); print(file); Navigator.pop(context); - // setState(() { - // EReferralAttachment eReferralAttachment = new EReferralAttachment(fileName: 'image ${medicalReportImages.length + 1}.png', base64String: image); - // medicalReportImages.add(eReferralAttachment); - // }); + contactUsViewModel.addFeedbackAttachment(image); }, ); }, @@ -188,7 +353,44 @@ class FeedbackPage extends StatelessWidget { icon: AppAssets.file_icon, iconColor: AppColors.primaryRedColor, iconSize: 16.h, - ) + ), + SizedBox(height: 16.h), + contactUsViewModel.feedbackAttachmentList.isNotEmpty + ? Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 12.r, + hasShadow: false, + ), + child: ListView.builder( + padding: EdgeInsets.all(16.h), + shrinkWrap: true, + itemCount: contactUsViewModel.feedbackAttachmentList.length, + itemBuilder: (BuildContext context, int index) { + return Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Icon( + Icons.attach_file, + color: Color(0xff2B353E), + ), + SizedBox(width: 8.w), + "Image ${index + 1}".toText14().paddingOnly(bottom: 8.h), + ], + ), + Utils.buildSvgWithAssets(icon: AppAssets.cancel_circle_icon).onPress(() { + contactUsViewModel.removeFeedbackAttachment(contactUsViewModel.feedbackAttachmentList[index]); + }), + ], + ); + }, + ), + ) + : SizedBox.shrink(), ], ); } else { diff --git a/lib/presentation/contact_us/widgets/feedback_appointment_selection.dart b/lib/presentation/contact_us/widgets/feedback_appointment_selection.dart new file mode 100644 index 0000000..ca04069 --- /dev/null +++ b/lib/presentation/contact_us/widgets/feedback_appointment_selection.dart @@ -0,0 +1,70 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/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/medical_file/medical_file_view_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/appointments/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 FeedbackAppointmentSelection extends StatelessWidget { + FeedbackAppointmentSelection({super.key}); + + late MedicalFileViewModel medicalFileViewModel; + late ContactUsViewModel contactUsViewModel; + + @override + Widget build(BuildContext context) { + medicalFileViewModel = Provider.of(context, listen: false); + contactUsViewModel = Provider.of(context, listen: false); + return CollapsingListView( + title: LocaleKeys.feedback.tr(), + isClose: true, + child: Column( + children: [ + ListView.separated( + padding: EdgeInsets.only(top: 24.h), + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount: medicalFileViewModel.patientMedicalReportAppointmentHistoryList.length, + itemBuilder: (context, index) { + return 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: AppointmentCard( + patientAppointmentHistoryResponseModel: medicalFileViewModel.patientMedicalReportAppointmentHistoryList[index], + myAppointmentsViewModel: Provider.of(context, listen: false), + bookAppointmentsViewModel: Provider.of(context, listen: false), + medicalFileViewModel: medicalFileViewModel, + contactUsViewModel: contactUsViewModel, + isLoading: false, + isFromHomePage: false, + isFromMedicalReport: true, + isForFeedback: true, + ), + ).paddingSymmetrical(24.h, 0.h), + ), + ), + ); + }, + separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + ), + SizedBox(height: 24.h), + ], + ), + ); + } +} diff --git a/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart b/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart index b414799..8be6ce2 100644 --- a/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart +++ b/lib/presentation/habib_wallet/wallet_payment_confirm_page.dart @@ -90,7 +90,7 @@ class _WalletPaymentConfirmPageState extends State { Transform.flip( flipX: appState.isArabic(), child: Utils.buildSvgWithAssets( - icon: AppAssets.forward_arrow_icon, + icon: AppAssets.forward_arrow_icon_small, iconColor: AppColors.blackColor, width: 18.h, height: 13.h, @@ -132,7 +132,7 @@ class _WalletPaymentConfirmPageState extends State { Transform.flip( flipX: appState.isArabic(), child: Utils.buildSvgWithAssets( - icon: AppAssets.forward_arrow_icon, + icon: AppAssets.forward_arrow_icon_small, iconColor: AppColors.blackColor, width: 18.h, height: 13.h, diff --git a/lib/presentation/hmg_services/services_page.dart b/lib/presentation/hmg_services/services_page.dart index f79aae0..a634803 100644 --- a/lib/presentation/hmg_services/services_page.dart +++ b/lib/presentation/hmg_services/services_page.dart @@ -10,7 +10,7 @@ class ServicesPage extends StatelessWidget { Widget build(BuildContext context) { return CollapsingListView( title: "Explore Services".needTranslation, - isLeading: false, + isLeading: Navigator.canPop(context), child: Padding( padding: EdgeInsets.all(24.h), child: Column( diff --git a/lib/presentation/home/landing_page.dart b/lib/presentation/home/landing_page.dart index 320d8d9..1007547 100644 --- a/lib/presentation/home/landing_page.dart +++ b/lib/presentation/home/landing_page.dart @@ -31,6 +31,7 @@ import 'package:hmg_patient_app_new/presentation/book_appointment/book_appointme 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/hmg_services/services_page.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'; import 'package:hmg_patient_app_new/presentation/home/widgets/large_service_card.dart'; @@ -526,7 +527,9 @@ class _LandingPageState extends State { SizedBox(width: 2.h), Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h), ], - ), + ).onPress(() { + Navigator.of(context).push(CustomPageRoute(page: ServicesPage())); + }), ], ).paddingSymmetrical(24.h, 0.h), SizedBox( From ffca4317aa446ab98ac63f62b92c60e2b38004a5 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Wed, 19 Nov 2025 16:50:16 +0300 Subject: [PATCH 07/12] updates --- lib/core/api/api_client.dart | 2 +- lib/core/api_consts.dart | 2 +- lib/features/lab/lab_view_model.dart | 8 +- .../appointment_details_page.dart | 473 ++++++++++-------- lib/presentation/lab/lab_orders_page.dart | 2 +- .../medical_file/widgets/lab_rad_card.dart | 4 +- 6 files changed, 283 insertions(+), 208 deletions(-) diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index e7410e1..a2ab5a2 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -161,7 +161,7 @@ class ApiClientImp implements ApiClient { // body['VersionID'] = ApiConsts.appVersionID.toString(); if (!isExternal) { - body['VersionID'] = "50.0"; + body['VersionID'] = ApiConsts.appVersionID.toString(); body['Channel'] = ApiConsts.appChannelId.toString(); body['IPAdress'] = ApiConsts.appIpAddress; body['generalid'] = ApiConsts.appGeneralId; diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 6a4a64a..7ba9f13 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -847,7 +847,7 @@ class ApiConsts { static final String addAdvanceNumberRequest = 'Services/PayFort_Serv.svc/REST/AddAdvancedNumberRequest'; // ************ static values for Api **************** - static final double appVersionID = 50.0; + static final double appVersionID = 19.3; static final int appChannelId = 3; static final String appIpAddress = "10.20.10.20"; static final String appGeneralId = "Cs2020@2016\$2958"; diff --git a/lib/features/lab/lab_view_model.dart b/lib/features/lab/lab_view_model.dart index 12f0f27..d30190e 100644 --- a/lib/features/lab/lab_view_model.dart +++ b/lib/features/lab/lab_view_model.dart @@ -75,14 +75,14 @@ class LabViewModel extends ChangeNotifier { required this.navigationService}); initLabProvider() { - if (isLabNeedToLoad) { + // if (isLabNeedToLoad) { patientLabOrders.clear(); filteredLabOrders.clear(); labOrderTests.clear(); isLabOrdersLoading = true; isLabResultsLoading = true; getPatientLabOrders(); - } + // } notifyListeners(); } @@ -92,7 +92,7 @@ class LabViewModel extends ChangeNotifier { } Future getPatientLabOrders({Function(dynamic)? onSuccess, Function(String)? onError}) async { - if (!isLabNeedToLoad) return; + // if (!isLabNeedToLoad) return; isLabOrdersLoading = true; patientLabOrders.clear(); @@ -158,7 +158,7 @@ class LabViewModel extends ChangeNotifier { filterSuggestions() { final List labels = patientLabOrders - .expand((order) => order.testDetails!) + .expand((order) => order.testDetails ?? []) .map((detail) => detail.description) .whereType() .toList(); diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart index 8bf74a0..24594aa 100644 --- a/lib/presentation/appointments/appointment_details_page.dart +++ b/lib/presentation/appointments/appointment_details_page.dart @@ -11,6 +11,7 @@ import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctors_list_response_model.dart'; +import 'package:hmg_patient_app_new/features/contact_us/contact_us_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/utils/appointment_type.dart'; @@ -22,8 +23,12 @@ import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointmen 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/contact_us/feedback_page.dart'; +import 'package:hmg_patient_app_new/presentation/lab/lab_orders_page.dart'; +import 'package:hmg_patient_app_new/presentation/medical_file/widgets/lab_rad_card.dart'; import 'package:hmg_patient_app_new/presentation/prescriptions/prescription_detail_page.dart'; import 'package:hmg_patient_app_new/presentation/prescriptions/prescriptions_list_page.dart'; +import 'package:hmg_patient_app_new/presentation/radiology/radiology_orders_page.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/buttons/custom_button.dart'; @@ -50,6 +55,7 @@ class _AppointmentDetailsPageState extends State { late MyAppointmentsViewModel myAppointmentsViewModel; late PrescriptionsViewModel prescriptionsViewModel; late BookAppointmentsViewModel bookAppointmentsViewModel; + late ContactUsViewModel contactUsViewModel; @override void initState() { @@ -68,6 +74,7 @@ class _AppointmentDetailsPageState extends State { myAppointmentsViewModel = Provider.of(context, listen: false); prescriptionsViewModel = Provider.of(context, listen: false); bookAppointmentsViewModel = Provider.of(context, listen: false); + contactUsViewModel = Provider.of(context, listen: false); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: Column( @@ -75,7 +82,16 @@ class _AppointmentDetailsPageState extends State { Expanded( child: CollapsingListView( title: "Appointment Details".needTranslation, - report: AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) ? () {} : null, + report: AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) + ? () { + contactUsViewModel.setPatientFeedbackSelectedAppointment(widget.patientAppointmentHistoryResponseModel); + Navigator.of(context).push( + CustomPageRoute( + page: FeedbackPage(), + ), + ); + } + : null, child: SingleChildScrollView( child: Column( crossAxisAlignment: CrossAxisAlignment.start, @@ -264,203 +280,264 @@ class _AppointmentDetailsPageState extends State { ], ) // : SizedBox.shrink() - : Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - "Lab & Radiology".needTranslation.toText18(isBold: true), - SizedBox(height: 16.h), - GridView( - padding: EdgeInsets.zero, - gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( - crossAxisCount: isTablet || isFoldable ? 3 : 2, - crossAxisSpacing: 13.w, - mainAxisSpacing: 13.w, - ), - physics: NeverScrollableScrollPhysics(), - shrinkWrap: true, - children: [ - MedicalFileCard( - label: LocaleKeys.labResults.tr(context: context), - textColor: AppColors.blackColor, - backgroundColor: AppColors.whiteColor, - svgIcon: AppAssets.lab_result_icon, - iconSize: 40.w, - isLargeText: true, - ), - MedicalFileCard( - label: "Radiology Results".needTranslation, - textColor: AppColors.blackColor, - backgroundColor: AppColors.whiteColor, - svgIcon: AppAssets.radiology_icon, - iconSize: 40.w, - isLargeText: true, - ), - ], - ), - SizedBox(height: 16.h), - LocaleKeys.prescriptions.tr().toText18(isBold: true), - SizedBox(height: 16.h), - // Consumer(builder: (context, prescriptionVM, child) { - // return prescriptionVM.isPrescriptionsDetailsLoading - // ? const MoviesShimmerWidget() - // : Container( - // decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - // color: Colors.white, - // borderRadius: 20.r, - // ), - // padding: EdgeInsets.all(16.w), - // child: Column( - // children: [ - // ListView.separated( - // itemCount: prescriptionVM.prescriptionDetailsList.length, - // shrinkWrap: true, - // padding: EdgeInsets.only(right: 8.w), - // physics: NeverScrollableScrollPhysics(), - // itemBuilder: (context, index) { - // return AnimationConfiguration.staggeredList( - // position: index, - // duration: const Duration(milliseconds: 500), - // child: SlideAnimation( - // verticalOffset: 100.0, - // child: FadeInAnimation( - // child: Row( - // children: [ - // Utils.buildSvgWithAssets( - // icon: AppAssets.prescription_item_icon, - // width: 40.h, - // height: 40.h, - // ), - // SizedBox(width: 8.h), - // Row( - // mainAxisSize: MainAxisSize.max, - // children: [ - // Column( - // children: [ - // prescriptionVM.prescriptionDetailsList[index].itemDescription! - // .toText12(isBold: true, maxLine: 1), - // "Prescribed By: ${widget.patientAppointmentHistoryResponseModel.doctorTitle} ${widget.patientAppointmentHistoryResponseModel.doctorNameObj}" - // .needTranslation - // .toText10( - // weight: FontWeight.w500, - // color: AppColors.greyTextColor, - // letterSpacing: -0.4), - // ], - // ), - // SizedBox(width: 68.w), - // Transform.flip( - // flipX: appState.isArabic(), - // child: Utils.buildSvgWithAssets( - // icon: AppAssets.forward_arrow_icon, - // iconColor: AppColors.blackColor, - // width: 18.w, - // height: 13.h, - // fit: BoxFit.contain, - // ), - // ), - // ], - // ), - // ], - // ), - // ), - // ), - // ); - // }, - // separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), - // ).onPress(() { - // prescriptionVM.setPrescriptionsDetailsLoading(); - // Navigator.of(context).push( - // CustomPageRoute( - // page: PrescriptionDetailPage(prescriptionsResponseModel: getPrescriptionRequestModel()), - // ), - // ); - // }), - // SizedBox(height: 16.h), - // const Divider(color: AppColors.dividerColor), - // SizedBox(height: 16.h), - // Wrap( - // runSpacing: 6.w, - // children: [ - // // Expanded( - // // child: CustomButton( - // // text: widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported! ? LocaleKeys.resendOrder.tr(context: context) : LocaleKeys.prescriptionDeliveryError.tr(context: context), - // // onPressed: () {}, - // // backgroundColor: AppColors.secondaryLightRedColor, - // // borderColor: AppColors.secondaryLightRedColor, - // // textColor: AppColors.primaryRedColor, - // // fontSize: 14, - // // fontWeight: FontWeight.w500, - // // borderRadius: 12.h, - // // height: 40.h, - // // icon: AppAssets.appointment_calendar_icon, - // // iconColor: AppColors.primaryRedColor, - // // iconSize: 16.h, - // // ), - // // ), - // // SizedBox(width: 16.h), - // Expanded( - // child: CustomButton( - // text: "Refill & Delivery".needTranslation, - // onPressed: () { - // Navigator.of(context) - // .push( - // CustomPageRoute( - // page: PrescriptionsListPage(), - // ), - // ) - // .then((val) { - // prescriptionsViewModel.setPrescriptionsDetailsLoading(); - // prescriptionsViewModel.getPrescriptionDetails(getPrescriptionRequestModel()); - // }); - // }, - // backgroundColor: AppColors.secondaryLightRedColor, - // borderColor: AppColors.secondaryLightRedColor, - // textColor: AppColors.primaryRedColor, - // fontSize: 14.f, - // fontWeight: FontWeight.w500, - // borderRadius: 12.r, - // height: 40.h, - // icon: AppAssets.requests, - // iconColor: AppColors.primaryRedColor, - // iconSize: 16.h, - // ), - // ), - // - // SizedBox(width: 16.w), - // Expanded( - // child: CustomButton( - // text: "All Prescriptions".needTranslation, - // onPressed: () { - // Navigator.of(context) - // .push( - // CustomPageRoute( - // page: PrescriptionsListPage(), - // ), - // ) - // .then((val) { - // prescriptionsViewModel.setPrescriptionsDetailsLoading(); - // prescriptionsViewModel.getPrescriptionDetails(getPrescriptionRequestModel()); - // }); - // }, - // backgroundColor: AppColors.secondaryLightRedColor, - // borderColor: AppColors.secondaryLightRedColor, - // textColor: AppColors.primaryRedColor, - // fontSize: 14.f, - // fontWeight: FontWeight.w500, - // borderRadius: 12.r, - // height: 40.h, - // icon: AppAssets.requests, - // iconColor: AppColors.primaryRedColor, - // iconSize: 16.h, - // ), - // ), - // ], - // ), - // ], - // ), - // ); - // }), - ], - ), + : GridView( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + crossAxisSpacing: 16.h, + mainAxisSpacing: 16.w, + mainAxisExtent: 115.h, + ), + physics: NeverScrollableScrollPhysics(), + padding: EdgeInsets.zero, + shrinkWrap: true, + children: [ + MedicalFileCard( + label: "Eye Test Results".needTranslation, + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.eye_result_icon, + isLargeText: true, + iconSize: 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, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.allergy_info_icon, + isLargeText: true, + iconSize: 36.w, + ), + MedicalFileCard( + label: "Vaccine Info".needTranslation, + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.vaccine_info_icon, + isLargeText: true, + iconSize: 36.w, + ).onPress(() { + // Navigator.of(context).push( + // CustomPageRoute( + // page: VaccineListPage(), + // ), + // ); + }), + ], + ), + // Column( + // crossAxisAlignment: CrossAxisAlignment.start, + // children: [ + // "Lab & Radiology".needTranslation.toText18(isBold: true), + // SizedBox(height: 16.h), + // Row( + // children: [ + // Expanded( + // child: LabRadCard( + // icon: AppAssets.lab_result_icon, + // labelText: LocaleKeys.labResults.tr(context: context), + // // labOrderTests: ["Complete blood count", "Creatinine", "Blood Sugar"], + // // labOrderTests: labViewModel.isLabOrdersLoading ? [] : labViewModel.labOrderTests, + // labOrderTests: [], + // // isLoading: labViewModel.isLabOrdersLoading, + // isLoading: false, + // ).onPress(() { + // Navigator.of(context).push( + // CustomPageRoute( + // page: LabOrdersPage(), + // ), + // ); + // }), + // ), + // SizedBox(width: 16.h), + // Expanded( + // child: LabRadCard( + // icon: AppAssets.radiology_icon, + // labelText: LocaleKeys.radiology.tr(context: context), + // // labOrderTests: ["Chest X-ray", "Abdominal Ultrasound", "Dental X-ray"], + // labOrderTests: [], + // isLoading: false, + // ).onPress(() { + // Navigator.of(context).push( + // CustomPageRoute( + // page: RadiologyOrdersPage(), + // ), + // ); + // }), + // ), + // ], + // ), + // SizedBox(height: 16.h), + // LocaleKeys.prescriptions.tr().toText18(isBold: true), + // SizedBox(height: 16.h), + // Consumer(builder: (context, prescriptionVM, child) { + // return prescriptionVM.isPrescriptionsDetailsLoading + // ? const MoviesShimmerWidget() + // : Container( + // decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + // color: Colors.white, + // borderRadius: 20.r, + // ), + // padding: EdgeInsets.all(16.w), + // child: Column( + // children: [ + // // ListView.separated( + // // itemCount: prescriptionVM.prescriptionDetailsList.length, + // // shrinkWrap: true, + // // padding: EdgeInsets.only(right: 8.w), + // // physics: NeverScrollableScrollPhysics(), + // // itemBuilder: (context, index) { + // // return AnimationConfiguration.staggeredList( + // // position: index, + // // duration: const Duration(milliseconds: 500), + // // child: SlideAnimation( + // // verticalOffset: 100.0, + // // child: FadeInAnimation( + // // child: Row( + // // children: [ + // // Utils.buildSvgWithAssets( + // // icon: AppAssets.prescription_item_icon, + // // width: 40.h, + // // height: 40.h, + // // ), + // // SizedBox(width: 8.h), + // // Row( + // // mainAxisSize: MainAxisSize.max, + // // children: [ + // // Column( + // // children: [ + // // prescriptionVM.prescriptionDetailsList[index].itemDescription! + // // .toText12(isBold: true, maxLine: 1), + // // "Prescribed By: ${widget.patientAppointmentHistoryResponseModel.doctorTitle} ${widget.patientAppointmentHistoryResponseModel.doctorNameObj}" + // // .needTranslation + // // .toText10( + // // weight: FontWeight.w500, + // // color: AppColors.greyTextColor, + // // letterSpacing: -0.4), + // // ], + // // ), + // // SizedBox(width: 68.w), + // // Transform.flip( + // // flipX: appState.isArabic(), + // // child: Utils.buildSvgWithAssets( + // // icon: AppAssets.forward_arrow_icon, + // // iconColor: AppColors.blackColor, + // // width: 18.w, + // // height: 13.h, + // // fit: BoxFit.contain, + // // ), + // // ), + // // ], + // // ), + // // ], + // // ), + // // ), + // // ), + // // ); + // // }, + // // separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h), + // // ).onPress(() { + // // prescriptionVM.setPrescriptionsDetailsLoading(); + // // Navigator.of(context).push( + // // CustomPageRoute( + // // page: PrescriptionDetailPage(prescriptionsResponseModel: getPrescriptionRequestModel()), + // // ), + // // ); + // // }), + // SizedBox(height: 16.h), + // const Divider(color: AppColors.dividerColor), + // SizedBox(height: 16.h), + // // Wrap( + // // runSpacing: 6.w, + // // children: [ + // // // Expanded( + // // // child: CustomButton( + // // // text: widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported! ? LocaleKeys.resendOrder.tr(context: context) : LocaleKeys.prescriptionDeliveryError.tr(context: context), + // // // onPressed: () {}, + // // // backgroundColor: AppColors.secondaryLightRedColor, + // // // borderColor: AppColors.secondaryLightRedColor, + // // // textColor: AppColors.primaryRedColor, + // // // fontSize: 14, + // // // fontWeight: FontWeight.w500, + // // // borderRadius: 12.h, + // // // height: 40.h, + // // // icon: AppAssets.appointment_calendar_icon, + // // // iconColor: AppColors.primaryRedColor, + // // // iconSize: 16.h, + // // // ), + // // // ), + // // // SizedBox(width: 16.h), + // // Expanded( + // // child: CustomButton( + // // text: "Refill & Delivery".needTranslation, + // // onPressed: () { + // // Navigator.of(context) + // // .push( + // // CustomPageRoute( + // // page: PrescriptionsListPage(), + // // ), + // // ) + // // .then((val) { + // // prescriptionsViewModel.setPrescriptionsDetailsLoading(); + // // prescriptionsViewModel.getPrescriptionDetails(getPrescriptionRequestModel()); + // // }); + // // }, + // // backgroundColor: AppColors.secondaryLightRedColor, + // // borderColor: AppColors.secondaryLightRedColor, + // // textColor: AppColors.primaryRedColor, + // // fontSize: 14.f, + // // fontWeight: FontWeight.w500, + // // borderRadius: 12.r, + // // height: 40.h, + // // icon: AppAssets.requests, + // // iconColor: AppColors.primaryRedColor, + // // iconSize: 16.h, + // // ), + // // ), + // // + // // SizedBox(width: 16.w), + // // Expanded( + // // child: CustomButton( + // // text: "All Prescriptions".needTranslation, + // // onPressed: () { + // // Navigator.of(context) + // // .push( + // // CustomPageRoute( + // // page: PrescriptionsListPage(), + // // ), + // // ) + // // .then((val) { + // // prescriptionsViewModel.setPrescriptionsDetailsLoading(); + // // prescriptionsViewModel.getPrescriptionDetails(getPrescriptionRequestModel()); + // // }); + // // }, + // // backgroundColor: AppColors.secondaryLightRedColor, + // // borderColor: AppColors.secondaryLightRedColor, + // // textColor: AppColors.primaryRedColor, + // // fontSize: 14.f, + // // fontWeight: FontWeight.w500, + // // borderRadius: 12.r, + // // height: 40.h, + // // icon: AppAssets.requests, + // // iconColor: AppColors.primaryRedColor, + // // iconSize: 16.h, + // // ), + // // ), + // // ], + // // ), + // ], + // ), + // ); + // }), + // ], + // ), ], ).paddingAll(24.w), ), diff --git a/lib/presentation/lab/lab_orders_page.dart b/lib/presentation/lab/lab_orders_page.dart index 0b9d093..4ffd979 100644 --- a/lib/presentation/lab/lab_orders_page.dart +++ b/lib/presentation/lab/lab_orders_page.dart @@ -1 +1 @@ -import 'dart:async'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_order_by_test.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart'; import 'package:hmg_patient_app_new/presentation/lab/search_lab_report.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/chip/custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; import 'package:provider/provider.dart'; import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; import '../../widgets/appbar/collapsing_list_view.dart'; class LabOrdersPage extends StatefulWidget { const LabOrdersPage({super.key}); @override State createState() => _LabOrdersPageState(); } class _LabOrdersPageState extends State { late LabViewModel labProvider; late DateRangeSelectorRangeViewModel rangeViewModel; late AppState _appState; List?> labSuggestions = []; int? expandedIndex; String? selectedFilterText = ''; int activeIndex = 0; @override void initState() { scheduleMicrotask(() { labProvider.initLabProvider(); }); super.initState(); } @override Widget build(BuildContext context) { labProvider = Provider.of(context, listen: false); rangeViewModel = Provider.of(context); _appState = getIt(); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( title: LocaleKeys.labResults.tr(), search: () async { final lavVM = Provider.of(context, listen: false); if (lavVM.isLabOrdersLoading) { return; } else { String? value = await Navigator.of(context).push( CustomPageRoute( page: SearchLabResultsContent(labSuggestionsList: lavVM.labSuggestions), fullScreenDialog: true, direction: AxisDirection.down, ), ); if (value != null) { selectedFilterText = value; lavVM.filterLabReports(value); } } }, child: SingleChildScrollView( padding: EdgeInsets.all(24.h), physics: NeverScrollableScrollPhysics(), child: Consumer( builder: (context, model, child) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ SizedBox(height: 16.h), CustomTabBar( activeTextColor: Color(0xffED1C2B), activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), tabs: [ CustomTabBarModel(null, "By Visit".needTranslation), CustomTabBarModel(null, "By Test".needTranslation), // CustomTabBarModel(null, "Completed".needTranslation), ], onTabChange: (index) { activeIndex = index; setState(() {}); }, ), SizedBox(height: 16.h), selectedFilterText!.isNotEmpty ? CustomChipWidget( chipText: selectedFilterText!, chipType: ChipTypeEnum.alert, isSelected: true, ) : SizedBox(), activeIndex == 0 ? ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: model.isLabOrdersLoading ? 5 : model.patientLabOrders.isNotEmpty ? model.patientLabOrders.length : 1, itemBuilder: (context, index) { final isExpanded = expandedIndex == index; return model.isLabOrdersLoading ? LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, ) : model.patientLabOrders.isNotEmpty ? AnimationConfiguration.staggeredList( position: index, duration: const Duration(milliseconds: 500), child: SlideAnimation( verticalOffset: 100.0, child: FadeInAnimation( child: LabResultItemView( onTap: () { model.currentlySelectedPatientOrder = model.patientLabOrders[ index]; labProvider.getPatientLabResultByHospital(model.patientLabOrders[ index]); labProvider .getPatientSpecialResult( model.patientLabOrders[ index]); Navigator.push( context, CustomPageRoute( page: LabResultByClinic(labOrder: model.patientLabOrders[index]), )); }, labOrder: model.patientLabOrders[index], index: index, isExpanded: isExpanded), ), ), ) : Utils.getNoDataWidget(context, noDataText: "You don't have any lab results yet.".needTranslation); }, ) : ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: model.isLabOrdersLoading ? 5 : model.uniqueTests.toList().isNotEmpty ? model.uniqueTests.toList().length : 1, itemBuilder: (context, index) { final isExpanded = expandedIndex == index; return model.isLabOrdersLoading ? LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, ) : model.uniqueTests.toList().isNotEmpty ? AnimationConfiguration.staggeredList( position: index, duration: const Duration(milliseconds: 500), child: SlideAnimation( verticalOffset: 100.0, child: FadeInAnimation( child: LabOrderByTest( appState: _appState, onTap: () { if (model.uniqueTests.toList()[index].model != null) { rangeViewModel.flush(); model.getPatientLabResult(model.uniqueTests.toList()[index].model!, model.uniqueTests.toList()[index].description!, (_appState.isArabic() ? model.uniqueTests.toList()[index].testDescriptionAr! : model.uniqueTests.toList()[index].testDescriptionEn!)); } }, tests: model.uniqueTests.toList()[index], index: index, isExpanded: isExpanded)), ), ) : Utils.getNoDataWidget(context, noDataText: "You don't have any lab results yet.".needTranslation); }, ) ], ); }, ), ), )); } Color getLabOrderStatusColor(num status) { switch (status) { case 44: return AppColors.warningColorYellow; case 45: return AppColors.warningColorYellow; case 16: return AppColors.successColor; case 17: return AppColors.successColor; default: return AppColors.greyColor; } } String getLabOrderStatusText(num status) { switch (status) { case 44: return LocaleKeys.resultsPending.tr(context: context); case 45: return LocaleKeys.resultsPending.tr(context: context); case 16: return LocaleKeys.resultsAvailable.tr(context: context); case 17: return LocaleKeys.resultsAvailable.tr(context: context); default: return ""; } } getLabSuggestions(LabViewModel model) { if (model.patientLabOrders.isEmpty) { return []; } return model.patientLabOrders.map((m) => m.testDetails).toList(); } } \ No newline at end of file +import 'dart:async'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_order_by_test.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart'; import 'package:hmg_patient_app_new/presentation/lab/search_lab_report.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/chip/custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/date_range_selector/viewmodel/date_range_view_model.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; import 'package:provider/provider.dart'; import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart'; import '../../widgets/appbar/collapsing_list_view.dart'; class LabOrdersPage extends StatefulWidget { const LabOrdersPage({super.key}); @override State createState() => _LabOrdersPageState(); } class _LabOrdersPageState extends State { late LabViewModel labProvider; late DateRangeSelectorRangeViewModel rangeViewModel; late AppState _appState; List?> labSuggestions = []; int? expandedIndex; String? selectedFilterText = ''; int activeIndex = 0; @override void initState() { scheduleMicrotask(() { labProvider.initLabProvider(); }); super.initState(); } @override Widget build(BuildContext context) { labProvider = Provider.of(context, listen: false); rangeViewModel = Provider.of(context); _appState = getIt(); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( title: LocaleKeys.labResults.tr(), search: () async { final lavVM = Provider.of(context, listen: false); if (lavVM.isLabOrdersLoading) { return; } else { String? value = await Navigator.of(context).push( CustomPageRoute( page: SearchLabResultsContent(labSuggestionsList: lavVM.labSuggestions), fullScreenDialog: true, direction: AxisDirection.down, ), ); if (value != null) { selectedFilterText = value; lavVM.filterLabReports(value); } } }, child: SingleChildScrollView( padding: EdgeInsets.all(24.h), physics: NeverScrollableScrollPhysics(), child: Consumer( builder: (context, model, child) { return Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ CustomTabBar( activeTextColor: Color(0xffED1C2B), activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1), tabs: [ CustomTabBarModel(null, "By Visit".needTranslation), CustomTabBarModel(null, "By Test".needTranslation), // CustomTabBarModel(null, "Completed".needTranslation), ], onTabChange: (index) { activeIndex = index; setState(() {}); }, ), SizedBox(height: 8.h), selectedFilterText!.isNotEmpty ? CustomChipWidget( chipText: selectedFilterText!, chipType: ChipTypeEnum.alert, isSelected: true, ) : SizedBox(), activeIndex == 0 ? ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: model.isLabOrdersLoading ? 5 : model.patientLabOrders.isNotEmpty ? model.patientLabOrders.length : 1, itemBuilder: (context, index) { final isExpanded = expandedIndex == index; return model.isLabOrdersLoading ? LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, ) : model.patientLabOrders.isNotEmpty ? AnimationConfiguration.staggeredList( position: index, duration: const Duration(milliseconds: 500), child: SlideAnimation( verticalOffset: 100.0, child: FadeInAnimation( child: LabResultItemView( onTap: () { model.currentlySelectedPatientOrder = model.patientLabOrders[ index]; labProvider.getPatientLabResultByHospital(model.patientLabOrders[ index]); labProvider .getPatientSpecialResult( model.patientLabOrders[ index]); Navigator.push( context, CustomPageRoute( page: LabResultByClinic(labOrder: model.patientLabOrders[index]), )); }, labOrder: model.patientLabOrders[index], index: index, isExpanded: isExpanded), ), ), ) : Utils.getNoDataWidget(context, noDataText: "You don't have any lab results yet.".needTranslation); }, ) : ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), padding: EdgeInsets.zero, itemCount: model.isLabOrdersLoading ? 5 : model.uniqueTests.toList().isNotEmpty ? model.uniqueTests.toList().length : 1, itemBuilder: (context, index) { final isExpanded = expandedIndex == index; return model.isLabOrdersLoading ? LabResultItemView( onTap: () {}, labOrder: null, index: index, isLoading: true, ) : model.uniqueTests.toList().isNotEmpty ? AnimationConfiguration.staggeredList( position: index, duration: const Duration(milliseconds: 500), child: SlideAnimation( verticalOffset: 100.0, child: FadeInAnimation( child: LabOrderByTest( appState: _appState, onTap: () { if (model.uniqueTests.toList()[index].model != null) { rangeViewModel.flush(); model.getPatientLabResult(model.uniqueTests.toList()[index].model!, model.uniqueTests.toList()[index].description!, (_appState.isArabic() ? model.uniqueTests.toList()[index].testDescriptionAr! : model.uniqueTests.toList()[index].testDescriptionEn!)); } }, tests: model.uniqueTests.toList()[index], index: index, isExpanded: isExpanded)), ), ) : Utils.getNoDataWidget(context, noDataText: "You don't have any lab results yet.".needTranslation); }, ) ], ); }, ), ), )); } Color getLabOrderStatusColor(num status) { switch (status) { case 44: return AppColors.warningColorYellow; case 45: return AppColors.warningColorYellow; case 16: return AppColors.successColor; case 17: return AppColors.successColor; default: return AppColors.greyColor; } } String getLabOrderStatusText(num status) { switch (status) { case 44: return LocaleKeys.resultsPending.tr(context: context); case 45: return LocaleKeys.resultsPending.tr(context: context); case 16: return LocaleKeys.resultsAvailable.tr(context: context); case 17: return LocaleKeys.resultsAvailable.tr(context: context); default: return ""; } } getLabSuggestions(LabViewModel model) { if (model.patientLabOrders.isEmpty) { return []; } return model.patientLabOrders.map((m) => m.testDetails).toList(); } } \ No newline at end of file diff --git a/lib/presentation/medical_file/widgets/lab_rad_card.dart b/lib/presentation/medical_file/widgets/lab_rad_card.dart index 42f5bff..1a56baf 100644 --- a/lib/presentation/medical_file/widgets/lab_rad_card.dart +++ b/lib/presentation/medical_file/widgets/lab_rad_card.dart @@ -60,9 +60,7 @@ class LabRadCard extends StatelessWidget { itemCount: 3, ) : "You don't have any records yet".needTranslation.toText13( - color: AppColors.greyTextColor, - isCenter: true, - ), + color: AppColors.greyTextColor, isCenter: true), SizedBox(height: 16.h), Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, From c03c84287ca867451e57a46421e4e815a3b88993 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Thu, 20 Nov 2025 12:57:49 +0300 Subject: [PATCH 08/12] Appointment details Lab & radiology integration implemented --- lib/core/api_consts.dart | 2 +- lib/core/dependencies.dart | 1 + lib/features/lab/lab_repo.dart | 40 +++++++ lib/features/lab/lab_view_model.dart | 65 ++++++++++ lib/features/radiology/radiology_repo.dart | 48 ++++++++ .../radiology/radiology_view_model.dart | 38 +++++- .../appointment_details_page.dart | 111 ++++++++++++------ .../widgets/medical_file_card.dart | 1 + 8 files changed, 269 insertions(+), 37 deletions(-) diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 7ba9f13..c996855 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -719,7 +719,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 37d0cc4..d1fecb0 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -125,6 +125,7 @@ class AppDependencies { () => RadiologyViewModel( radiologyRepo: getIt(), errorHandlerService: getIt(), + navigationService: getIt() ), ); diff --git a/lib/features/lab/lab_repo.dart b/lib/features/lab/lab_repo.dart index 3bb793b..e661157 100644 --- a/lib/features/lab/lab_repo.dart +++ b/lib/features/lab/lab_repo.dart @@ -21,6 +21,7 @@ abstract class LabRepo { Future>> getLabResultReportPDF({required PatientLabOrdersResponseModel labOrder}); + Future>> getLabResultsByAppointmentNo({required num appointmentNo, required num projectID, required num clinicID}); } class LabRepoImp implements LabRepo { @@ -135,6 +136,7 @@ class LabRepoImp implements LabRepo { request['SetupID'] = laborder!.setupID; request['ProjectID'] = laborder.projectID; request['ClinicID'] = laborder.clinicID; + request['InvoiceType'] = laborder.invoiceType ?? ""; try { GenericApiModel>? apiResponse; Failure? failure; @@ -184,6 +186,7 @@ class LabRepoImp implements LabRepo { request['SetupID'] = laborder!.setupID; request['ProjectID'] = laborder.projectID; request['ClinicID'] = laborder.clinicID; + request['InvoiceType'] = laborder.invoiceType ?? ""; try { GenericApiModel>? apiResponse; Failure? failure; @@ -278,4 +281,41 @@ class LabRepoImp implements LabRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future> getLabResultsByAppointmentNo({required num appointmentNo, required num projectID, required num clinicID}) async { + Map request = {}; + request['AppointmentNo'] = appointmentNo; + request['ProjectID'] = projectID; + request['ClinicID'] = clinicID; + + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + GET_PATIENT_LAB_ORDERS_BY_APPOINTMENT, + body: request, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: response['ListLabResultsByAppNo'], + ); + } 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/lab/lab_view_model.dart b/lib/features/lab/lab_view_model.dart index d30190e..bad4f89 100644 --- a/lib/features/lab/lab_view_model.dart +++ b/lib/features/lab/lab_view_model.dart @@ -9,11 +9,13 @@ import 'package:hmg_patient_app_new/core/utils/utils.dart' show Utils; import 'package:hmg_patient_app_new/features/lab/lab_repo.dart'; import 'package:hmg_patient_app_new/features/lab/models/resp_models/lab_result.dart'; import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart'; +import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_results/lab_result_details.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; import 'package:hmg_patient_app_new/services/navigation_service.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; +import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:intl/intl.dart' show DateFormat; import 'package:logger/logger.dart'; @@ -198,6 +200,69 @@ class LabViewModel extends ChangeNotifier { } } + Future getLabResultsByAppointmentNo( + {required num appointmentNo, + required num projectID, + required num clinicID, + required int doctorID, + required String clinicName, + required String doctorName, + required String projectName, + required String appointmentDate, + Function(dynamic)? onSuccess, + Function(String)? onError}) async { + bool isVidaPlus = Utils.isVidaPlusProject(projectID.toInt()); + final result = await labRepo.getLabResultsByAppointmentNo(appointmentNo: appointmentNo, projectID: projectID, clinicID: clinicID); + + result.fold( + (failure) async { + // await errorHandlerService.handleError(failure: failure); + if (onError != null) { + onError(failure.message); + } + }, + (apiResponse) { + if (apiResponse.messageStatus == 2) { + if (onError != null) { + onError(apiResponse.errorMessage!); + } + } else if (apiResponse.messageStatus == 1) { + if (apiResponse.data != null && apiResponse.data!.isNotEmpty) { + PatientLabOrdersResponseModel labOrder = PatientLabOrdersResponseModel(); + + labOrder.invoiceNoVP = isVidaPlus ? apiResponse.data[0]['InvoiceNo'].toString() : "0"; + labOrder.invoiceNo = isVidaPlus ? "0" : apiResponse.data[0]['InvoiceNo'].toString(); + labOrder.orderNo = apiResponse.data[0]['OrderNo'].toString(); + labOrder.invoiceType = apiResponse.data[0]['InvoiceType'].toString(); + labOrder.setupID = apiResponse.data[0]['SetupID'].toString(); + labOrder.projectID = projectID.toString(); + labOrder.clinicID = clinicID.toInt(); + labOrder.doctorID = doctorID; + labOrder.clinicDescription = clinicName; + labOrder.doctorName = doctorName; + labOrder.projectName = projectName; + labOrder.orderDate = appointmentDate; + + currentlySelectedPatientOrder = labOrder; + + getPatientLabResultByHospital(labOrder); + getPatientSpecialResult(labOrder); + + if (onSuccess != null) { + onSuccess(apiResponse); + } + navigationService.push( + CustomPageRoute( + page: LabResultByClinic(labOrder: labOrder), + ), + ); + } else {} + notifyListeners(); + } + }, + ); + } + Future getPatientLabResultByHospital( PatientLabOrdersResponseModel laborder) async { isLabResultByHospitalLoading = true; diff --git a/lib/features/radiology/radiology_repo.dart b/lib/features/radiology/radiology_repo.dart index b81fd50..2af3e0f 100644 --- a/lib/features/radiology/radiology_repo.dart +++ b/lib/features/radiology/radiology_repo.dart @@ -15,6 +15,8 @@ abstract class RadiologyRepo { Future>> getRadiologyReportPDF( {required PatientRadiologyResponseModel patientRadiologyResponseModel, required AuthenticatedUser authenticatedUser}); + + Future>>> getPatientRadiologyOrderByAppointment({required num appointmentNo, required num projectID}); } class RadiologyRepoImp implements RadiologyRepo { @@ -168,4 +170,50 @@ class RadiologyRepoImp implements RadiologyRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future>>> getPatientRadiologyOrderByAppointment({required num appointmentNo, required num projectID}) async { + Map mapDevice = { + "AppointmentNo": appointmentNo, + "ProjectID": projectID, + }; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + await apiClient.post( + GET_PATIENT_ORDERS, + body: mapDevice, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + final radOrders; + try { + if (response['FinalRadiologyList'] != null && response['FinalRadiologyList'].length != 0) { + final list = response['FinalRadiologyList']; + radOrders = list.map((item) => PatientRadiologyResponseModel.fromJson(item as Map)).toList().cast(); + } else { + final list = response['FinalRadiologyListAPI']; + radOrders = list.map((item) => PatientRadiologyResponseModel.fromJson(item as Map)).toList().cast(); + } + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: radOrders, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } } diff --git a/lib/features/radiology/radiology_view_model.dart b/lib/features/radiology/radiology_view_model.dart index de6a796..9ddd737 100644 --- a/lib/features/radiology/radiology_view_model.dart +++ b/lib/features/radiology/radiology_view_model.dart @@ -1,7 +1,11 @@ import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/features/authentication/models/resp_models/authenticated_user_resp_model.dart'; import 'package:hmg_patient_app_new/features/radiology/radiology_repo.dart'; +import 'package:hmg_patient_app_new/presentation/radiology/radiology_result_page.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; +import 'package:hmg_patient_app_new/services/navigation_service.dart'; +import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'models/resp_models/patient_radiology_response_model.dart'; @@ -11,13 +15,16 @@ class RadiologyViewModel extends ChangeNotifier { RadiologyRepo radiologyRepo; ErrorHandlerService errorHandlerService; + NavigationService navigationService; List patientRadiologyOrders = []; String radiologyImageURL = ""; String patientRadiologyReportPDFBase64 = ""; - RadiologyViewModel({required this.radiologyRepo, required this.errorHandlerService}); + late PatientRadiologyResponseModel patientRadiologyOrderByAppointment; + + RadiologyViewModel({required this.radiologyRepo, required this.errorHandlerService, required this.navigationService}); initRadiologyViewModel() { patientRadiologyOrders.clear(); @@ -48,6 +55,35 @@ class RadiologyViewModel extends ChangeNotifier { ); } + Future getPatientRadiologyOrdersByAppointment({required num appointmentNo, required num projectID, Function(dynamic)? onSuccess, Function(String)? onError}) async { + final result = await radiologyRepo.getPatientRadiologyOrderByAppointment(appointmentNo: appointmentNo, projectID: projectID); + + 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) { + notifyListeners(); + if (apiResponse.data!.isNotEmpty) { + if (onSuccess != null) { + onSuccess(apiResponse); + } + navigationService.push( + CustomPageRoute( + page: RadiologyResultPage(patientRadiologyResponseModel: apiResponse.data!.first), + ), + ); + } else { + if (onError != null) { + onError("No Radiology Orders Found".needTranslation); + } + } + } + }, + ); + } + Future getRadiologyImage( {required PatientRadiologyResponseModel patientRadiologyResponseModel, Function(dynamic)? onSuccess, Function(String)? onError}) async { final result = await radiologyRepo.getRadiologyImage(patientRadiologyResponseModel: patientRadiologyResponseModel); diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart index 24594aa..6907a61 100644 --- a/lib/presentation/appointments/appointment_details_page.dart +++ b/lib/presentation/appointments/appointment_details_page.dart @@ -12,11 +12,13 @@ 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/book_appointments/models/resp_models/doctors_list_response_model.dart'; import 'package:hmg_patient_app_new/features/contact_us/contact_us_view_model.dart'; +import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/utils/appointment_type.dart'; import 'package:hmg_patient_app_new/features/prescriptions/models/resp_models/patient_prescriptions_response_model.dart'; import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart'; +import 'package:hmg_patient_app_new/features/radiology/radiology_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/appointments/appointment_payment_page.dart'; import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart'; @@ -56,6 +58,8 @@ class _AppointmentDetailsPageState extends State { late PrescriptionsViewModel prescriptionsViewModel; late BookAppointmentsViewModel bookAppointmentsViewModel; late ContactUsViewModel contactUsViewModel; + late LabViewModel labViewModel; + late RadiologyViewModel radiologyViewModel; @override void initState() { @@ -75,6 +79,8 @@ class _AppointmentDetailsPageState extends State { prescriptionsViewModel = Provider.of(context, listen: false); bookAppointmentsViewModel = Provider.of(context, listen: false); contactUsViewModel = Provider.of(context, listen: false); + labViewModel = Provider.of(context, listen: false); + radiologyViewModel = Provider.of(context, listen: false); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: Column( @@ -292,41 +298,76 @@ class _AppointmentDetailsPageState extends State { shrinkWrap: true, children: [ MedicalFileCard( - label: "Eye Test Results".needTranslation, - textColor: AppColors.blackColor, - backgroundColor: AppColors.whiteColor, - 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, - backgroundColor: AppColors.whiteColor, - svgIcon: AppAssets.allergy_info_icon, - isLargeText: true, - iconSize: 36.w, - ), - MedicalFileCard( - label: "Vaccine Info".needTranslation, - textColor: AppColors.blackColor, - backgroundColor: AppColors.whiteColor, - svgIcon: AppAssets.vaccine_info_icon, - isLargeText: true, - iconSize: 36.w, - ).onPress(() { - // Navigator.of(context).push( - // CustomPageRoute( - // page: VaccineListPage(), + label: LocaleKeys.labResults.tr(context: context), + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.lab_result_icon, + isLargeText: true, + iconSize: 36.w, + ).onPress(() async { + LoaderBottomSheet.showLoader(loadingText: "Fetching Lab Results...".needTranslation); + await labViewModel.getLabResultsByAppointmentNo( + appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo, + projectID: widget.patientAppointmentHistoryResponseModel.projectID, + clinicID: widget.patientAppointmentHistoryResponseModel.clinicID, + doctorID: widget.patientAppointmentHistoryResponseModel.doctorID, + doctorName: widget.patientAppointmentHistoryResponseModel.doctorNameObj!, + clinicName: widget.patientAppointmentHistoryResponseModel.clinicName!, + projectName: widget.patientAppointmentHistoryResponseModel.projectName!, + appointmentDate: widget.patientAppointmentHistoryResponseModel.appointmentDate!, + onSuccess: (val) { + LoaderBottomSheet.hideLoader(); + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }, + ); + }), + MedicalFileCard( + label: "${LocaleKeys.radiology.tr(context: context)} ${LocaleKeys.radiologySubtitle.tr(context: context)}", + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.allergy_info_icon, + isLargeText: true, + iconSize: 36.w, + ).onPress(() async { + LoaderBottomSheet.showLoader(loadingText: "Fetching Radiology Results...".needTranslation); + await radiologyViewModel.getPatientRadiologyOrdersByAppointment( + appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo, + projectID: widget.patientAppointmentHistoryResponseModel.projectID, + onSuccess: (val) { + LoaderBottomSheet.hideLoader(); + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }, + ); + }), + MedicalFileCard( + label: LocaleKeys.prescriptions.tr(context: context), + textColor: AppColors.blackColor, + backgroundColor: AppColors.whiteColor, + svgIcon: AppAssets.prescription_item_icon, + isLargeText: true, + iconSize: 36.w, + ).onPress(() { + // Navigator.of(context).push( + // CustomPageRoute( + // page: VaccineListPage(), // ), // ); }), diff --git a/lib/presentation/medical_file/widgets/medical_file_card.dart b/lib/presentation/medical_file/widgets/medical_file_card.dart index b96026b..8c38363 100644 --- a/lib/presentation/medical_file/widgets/medical_file_card.dart +++ b/lib/presentation/medical_file/widgets/medical_file_card.dart @@ -29,6 +29,7 @@ class MedicalFileCard extends StatelessWidget { decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: backgroundColor, borderRadius: 12.r, + hasShadow: true ), padding: EdgeInsets.all(12.w), child: Column( From 07a3052b8e7c6988f367691b6d9bef6ca5f614f7 Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Sun, 23 Nov 2025 11:00:03 +0300 Subject: [PATCH 09/12] appointment prescription implemented --- lib/core/api_consts.dart | 2 +- lib/extensions/string_extensions.dart | 2 + .../appointment_details_page.dart | 55 +++++++++++++++++-- .../medical_file/medical_file_page.dart | 1 + .../prescription_detail_page.dart | 17 ++++-- .../prescriptions_list_page.dart | 9 ++- lib/widgets/chip/app_custom_chip_widget.dart | 4 +- 7 files changed, 73 insertions(+), 17 deletions(-) diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index e642066..4e8f3ee 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -711,7 +711,7 @@ var GET_PRESCRIPTION_INSTRUCTIONS_PDF = 'Services/ChatBot_Service.svc/REST/Chatb class ApiConsts { static const maxSmallScreen = 660; - static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.prod; + static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.uat; // static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart index 3c1765d..250453d 100644 --- a/lib/extensions/string_extensions.dart +++ b/lib/extensions/string_extensions.dart @@ -41,12 +41,14 @@ extension EmailValidator on String { FontWeight? weight, bool isBold = false, bool isUnderLine = false, + bool isCenter = false, int? maxlines, FontStyle? fontStyle, TextOverflow? textOverflow, double letterSpacing = 0}) => Text( this, + textAlign: isCenter ? TextAlign.center : null, maxLines: maxlines, overflow: textOverflow, style: TextStyle( diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart index ce24674..e620783 100644 --- a/lib/presentation/appointments/appointment_details_page.dart +++ b/lib/presentation/appointments/appointment_details_page.dart @@ -63,10 +63,10 @@ class _AppointmentDetailsPageState extends State { @override void initState() { scheduleMicrotask(() { - if (AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)) { - prescriptionsViewModel.setPrescriptionsDetailsLoading(); - prescriptionsViewModel.getPrescriptionDetails(getPrescriptionRequestModel()); - } + // if (AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)) { + // prescriptionsViewModel.setPrescriptionsDetailsLoading(); + // prescriptionsViewModel.getPrescriptionDetails(getPrescriptionRequestModel()); + // } }); super.initState(); } @@ -363,7 +363,52 @@ class _AppointmentDetailsPageState extends State { svgIcon: AppAssets.prescription_item_icon, isLargeText: true, iconSize: 36.w, - ).onPress(() { + ).onPress(() async { + LoaderBottomSheet.showLoader(loadingText: "Fetching Appointment Prescriptions...".needTranslation); + await prescriptionsViewModel.getPrescriptionDetails( + getPrescriptionRequestModel(), + onSuccess: (val) { + LoaderBottomSheet.hideLoader(); + if (val.data.isNotEmpty) { + PatientPrescriptionsResponseModel patientPrescriptionsResponseModel = PatientPrescriptionsResponseModel( + doctorImageURL: widget.patientAppointmentHistoryResponseModel.doctorImageURL, + doctorName: widget.patientAppointmentHistoryResponseModel.doctorNameObj, + appointmentDate: widget.patientAppointmentHistoryResponseModel.appointmentDate, + clinicDescription: widget.patientAppointmentHistoryResponseModel.clinicName, + decimalDoctorRate: widget.patientAppointmentHistoryResponseModel.decimalDoctorRate, + name: widget.patientAppointmentHistoryResponseModel.projectName, + isHomeMedicineDeliverySupported: false, + projectID: widget.patientAppointmentHistoryResponseModel.projectID, + clinicID: widget.patientAppointmentHistoryResponseModel.clinicID, + doctorID: widget.patientAppointmentHistoryResponseModel.doctorID, + setupID: widget.patientAppointmentHistoryResponseModel.setupID, + ); + Navigator.of(context).push( + CustomPageRoute( + page: PrescriptionDetailPage(isFromAppointments: true, prescriptionsResponseModel: patientPrescriptionsResponseModel), + ), + ); + } else { + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: "You don't have any prescriptions for this appointment.".needTranslation), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Utils.getErrorWidget(loadingText: err), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + }, + ); // Navigator.of(context).push( // CustomPageRoute( // page: VaccineListPage(), diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index 191fd01..883ca37 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -533,6 +533,7 @@ class _MedicalFilePageState extends State { Navigator.of(context).push( CustomPageRoute( page: PrescriptionDetailPage( + isFromAppointments: false, prescriptionsResponseModel: prescriptionVM.patientPrescriptionOrders[index]), ), ); diff --git a/lib/presentation/prescriptions/prescription_detail_page.dart b/lib/presentation/prescriptions/prescription_detail_page.dart index e0e78c2..d5e8138 100644 --- a/lib/presentation/prescriptions/prescription_detail_page.dart +++ b/lib/presentation/prescriptions/prescription_detail_page.dart @@ -25,9 +25,10 @@ import 'package:provider/provider.dart'; import 'package:url_launcher/url_launcher.dart'; class PrescriptionDetailPage extends StatefulWidget { - PrescriptionDetailPage({super.key, required this.prescriptionsResponseModel}); + PrescriptionDetailPage({super.key, required this.prescriptionsResponseModel, required this.isFromAppointments}); PatientPrescriptionsResponseModel prescriptionsResponseModel; + bool isFromAppointments = false; @override State createState() => _PrescriptionDetailPageState(); @@ -43,10 +44,12 @@ class _PrescriptionDetailPageState extends State { checkAndRemove(false); // locationUtils = new LocationUtils(isShowConfirmDialog: true, context: context); // WidgetsBinding.instance.addPostFrameCallback((_) => locationUtils.getCurrentLocation()); - scheduleMicrotask(() { - prescriptionsViewModel.setPrescriptionsDetailsLoading(); - prescriptionsViewModel.getPrescriptionDetails(widget.prescriptionsResponseModel); - }); + if (!widget.isFromAppointments) { + scheduleMicrotask(() { + prescriptionsViewModel.setPrescriptionsDetailsLoading(); + prescriptionsViewModel.getPrescriptionDetails(widget.prescriptionsResponseModel); + }); + } super.initState(); } @@ -146,7 +149,7 @@ class _PrescriptionDetailPageState extends State { CustomButton( text: "Download Prescription".needTranslation, onPressed: () async { - LoaderBottomSheet.showLoader(); + LoaderBottomSheet.showLoader(loadingText: "Fetching prescription PDF, Please wait...".needTranslation); await prescriptionVM.getPrescriptionPDFBase64(widget.prescriptionsResponseModel).then((val) async { LoaderBottomSheet.hideLoader(); if (prescriptionVM.prescriptionPDFBase64Data.isNotEmpty) { @@ -181,8 +184,10 @@ class _PrescriptionDetailPageState extends State { ), ), ).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 16.h), ListView.builder( shrinkWrap: true, + padding: EdgeInsets.zero, physics: NeverScrollableScrollPhysics(), itemCount: prescriptionVM.isPrescriptionsDetailsLoading ? 5 : prescriptionVM.prescriptionDetailsList.length, itemBuilder: (context, index) { diff --git a/lib/presentation/prescriptions/prescriptions_list_page.dart b/lib/presentation/prescriptions/prescriptions_list_page.dart index 7d73450..3449170 100644 --- a/lib/presentation/prescriptions/prescriptions_list_page.dart +++ b/lib/presentation/prescriptions/prescriptions_list_page.dart @@ -278,9 +278,12 @@ class _PrescriptionsListPageState extends State { model.setPrescriptionsDetailsLoading(); Navigator.of(context).push( CustomPageRoute( - page: PrescriptionDetailPage(prescriptionsResponseModel: prescription), - ), - ); + page: PrescriptionDetailPage( + prescriptionsResponseModel: prescription, + isFromAppointments: false, + ), + ), + ); }), ), ], diff --git a/lib/widgets/chip/app_custom_chip_widget.dart b/lib/widgets/chip/app_custom_chip_widget.dart index 4e655f0..6904edc 100644 --- a/lib/widgets/chip/app_custom_chip_widget.dart +++ b/lib/widgets/chip/app_custom_chip_widget.dart @@ -100,7 +100,7 @@ class AppCustomChipWidget extends StatelessWidget { ) : Chip( materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - label: richText ?? labelText!.toText10(weight: FontWeight.w500, letterSpacing: 0, color: textColor), + label: richText ?? labelText!.toText10(weight: FontWeight.w500, letterSpacing: 0, color: textColor, isCenter: true), padding: EdgeInsets.zero, backgroundColor: backgroundColor, shape: shape ?? @@ -109,7 +109,7 @@ class AppCustomChipWidget extends StatelessWidget { smoothness: 10, side: BorderSide(color: AppColors.transparent, width: 1.5), ), - labelPadding: labelPadding ?? EdgeInsetsDirectional.only(start: 2.w, end: deleteIcon?.isNotEmpty == true ? 2.w : 8.w), + labelPadding: labelPadding ?? EdgeInsetsDirectional.only(start: 6.w, end: deleteIcon?.isNotEmpty == true ? 2.w : 8.w), deleteIcon: deleteIcon?.isNotEmpty == true ? InkWell( onTap: onDeleteTap, From eb9c38729b66c2bc97456d8118a9970dab70a53c Mon Sep 17 00:00:00 2001 From: haroon amjad Date: Sun, 23 Nov 2025 11:40:00 +0300 Subject: [PATCH 10/12] Prescription delivery implementation contd. --- lib/core/api_consts.dart | 8 ++--- .../RRT/rrt_map_screen.dart | 31 +++++++++---------- .../prescriptions_list_page.dart | 4 +-- lib/widgets/map/HMSMap.dart | 4 ++- lib/widgets/map/map.dart | 21 +++++++------ 5 files changed, 34 insertions(+), 34 deletions(-) diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 4e8f3ee..a1f4062 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -754,7 +754,7 @@ class ApiConsts { TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout"; GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments"; GET_TAMARA_PAYMENT_STATUS = 'https://epharmacy.hmg.com/tamara/api/OnlineTamara/order_status?orderid='; - rcBaseUrl = 'https://rc.hmg.com/'; + rcBaseUrl = 'https://rc.hmg.com/uat/'; break; case AppEnvironmentTypeEnum.uat: baseUrl = "https://uat.hmgwebservices.com/"; @@ -764,7 +764,7 @@ class ApiConsts { TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout"; GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments"; GET_TAMARA_PAYMENT_STATUS = 'https://epharmacy.hmg.com/tamara/api/OnlineTamara/order_status?orderid='; - rcBaseUrl = 'https://rc.hmg.com/'; + rcBaseUrl = 'https://rc.hmg.com/uat/'; break; case AppEnvironmentTypeEnum.preProd: baseUrl = "https://webservices.hmg.com/"; @@ -784,7 +784,7 @@ class ApiConsts { TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout"; GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments"; GET_TAMARA_PAYMENT_STATUS = 'https://epharmacy.hmg.com/tamara/api/OnlineTamara/order_status?orderid='; - rcBaseUrl = 'https://rc.hmg.com/'; + rcBaseUrl = 'https://rc.hmg.com/uat/'; break; case AppEnvironmentTypeEnum.staging: baseUrl = "https://uat.hmgwebservices.com/"; @@ -794,7 +794,7 @@ class ApiConsts { TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout"; GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments"; GET_TAMARA_PAYMENT_STATUS = 'https://epharmacy.hmg.com/tamara/api/OnlineTamara/order_status?orderid='; - rcBaseUrl = 'https://rc.hmg.com/'; + rcBaseUrl = 'https://rc.hmg.com/uat/'; break; } } diff --git a/lib/presentation/emergency_services/RRT/rrt_map_screen.dart b/lib/presentation/emergency_services/RRT/rrt_map_screen.dart index b38b9f2..9e11c31 100644 --- a/lib/presentation/emergency_services/RRT/rrt_map_screen.dart +++ b/lib/presentation/emergency_services/RRT/rrt_map_screen.dart @@ -61,10 +61,10 @@ class RrtMapScreen extends StatelessWidget { BottomSheetType.EXPANDED: ExpanedBottomSheet(context), BottomSheetType.FIXED: FixedBottomSheet(context), }, - ).paddingAll(16.h), + ), body: Stack( children: [ - if (context.read().isGMSAvailable ) + if (context.read().isGMSAvailable) GMSMap( currentLocation: context.read().getGMSLocation(), @@ -116,15 +116,13 @@ class RrtMapScreen extends StatelessWidget { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.end, children: [ - Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, spacing: 24.h, children: [ - inputFields(context), + inputFields(context).paddingSymmetrical(16.h, 0.h), SizedBox( - height: 200.h, child: DecoratedBox( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.scaffoldBgColor, @@ -140,16 +138,14 @@ class RrtMapScreen extends StatelessWidget { Column( spacing: 4.h, children: [ - "Select Pickup Details".needTranslation.toText21( - weight: FontWeight.w600, - color: AppColors.textColor, - ), - " Please select the details of pickup" - .needTranslation - .toText12( - fontWeight: FontWeight.w500, - color: AppColors.greyTextColor, - ) + "Select Location".needTranslation.toText21( + weight: FontWeight.w600, + color: AppColors.textColor, + ), + "Please select the location".needTranslation.toText12( + fontWeight: FontWeight.w500, + color: AppColors.greyTextColor, + ) ], ), CustomButton( @@ -160,7 +156,8 @@ class RrtMapScreen extends StatelessWidget { PlaceDetails? placeDetails = locationViewModel.placeDetails; PlacePrediction? placePrediction = locationViewModel.selectedPrediction; context.read().submitRRTRequest(response?.results.first, placeDetails, placePrediction); - }) + }, + ) ], ).paddingOnly(top: 24.h, bottom: 32.h, left: 24.h, right: 24.h), ), @@ -561,7 +558,7 @@ class RrtMapScreen extends StatelessWidget { } ///decide which field to show first based on the selected calling place - inputFields(BuildContext context) { + Widget inputFields(BuildContext context) { return textPlaceInput(context); } diff --git a/lib/presentation/prescriptions/prescriptions_list_page.dart b/lib/presentation/prescriptions/prescriptions_list_page.dart index 3449170..58c5206 100644 --- a/lib/presentation/prescriptions/prescriptions_list_page.dart +++ b/lib/presentation/prescriptions/prescriptions_list_page.dart @@ -255,7 +255,7 @@ class _PrescriptionsListPageState extends State { Expanded( flex: 1, child: Container( - height: 48.h, + height: 40.h, width: 40.w, decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.textColor, @@ -268,8 +268,6 @@ class _PrescriptionsListPageState extends State { child: Utils.buildSvgWithAssets( icon: AppAssets.forward_arrow_icon_small, iconColor: AppColors.whiteColor, - // width: 8.w, - // height: 2, fit: BoxFit.contain, ), ), diff --git a/lib/widgets/map/HMSMap.dart b/lib/widgets/map/HMSMap.dart index f655479..7b9c553 100644 --- a/lib/widgets/map/HMSMap.dart +++ b/lib/widgets/map/HMSMap.dart @@ -4,6 +4,8 @@ import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_export.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:huawei_map/huawei_map.dart' ; class HMSMap extends StatefulWidget{ @@ -55,7 +57,7 @@ class _HMSMapState extends State { visible: widget.showCenterMarker, child: Align( alignment: Alignment.center, - child: Utils.buildSvgWithAssets(icon: AppAssets.pin_location, width: 24.w, height: 36.h), + child: Icon(Icons.location_pin, size: 36.h, color: AppColors.primaryRedColor).paddingOnly(bottom: 24.h), ), ) ], diff --git a/lib/widgets/map/map.dart b/lib/widgets/map/map.dart index 0c67f1b..6d04692 100644 --- a/lib/widgets/map/map.dart +++ b/lib/widgets/map/map.dart @@ -5,6 +5,8 @@ import 'package:google_maps_flutter/google_maps_flutter.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; class GMSMap extends StatelessWidget{ Completer? controller; @@ -29,14 +31,15 @@ class GMSMap extends StatelessWidget{ children: [ GoogleMap( mapType: mapType, - zoomControlsEnabled: false, - myLocationEnabled: myLocationEnabled, - myLocationButtonEnabled: false, + zoomControlsEnabled: true, + myLocationEnabled: myLocationEnabled, + myLocationButtonEnabled: false, compassEnabled: compassEnabled, initialCameraPosition: currentLocation, onCameraMove: (value) => onCameraMoved(value), onCameraIdle: ()=>onCameraIdle(), - onMapCreated: (GoogleMapController controller) { + // padding: EdgeInsets.only(bottom: 300.h), + onMapCreated: (GoogleMapController controller) { this.controller?.complete(controller); }, ), @@ -44,10 +47,10 @@ class GMSMap extends StatelessWidget{ visible: showCenterMarker, child: Align( alignment: Alignment.center, - child: Utils.buildSvgWithAssets(icon: AppAssets.pin_location, width: 24.w, height: 36.h), - ), - ) - ], - ); + child: Icon(Icons.location_pin, size: 36.h, color: AppColors.primaryRedColor).paddingOnly(bottom: 24.h), + ), + ) + ], + ); } } \ No newline at end of file From 5983caf83ae5d3df7073a53ae1ad0f66518c0b90 Mon Sep 17 00:00:00 2001 From: tahaalam Date: Sun, 23 Nov 2025 15:06:35 +0300 Subject: [PATCH 11/12] single map screen added to the project for multiple place usage --- .../emergency_services_view_model.dart | 22 +- .../location/location_view_model.dart | 58 ++++- .../RRT/rrt_map_screen.dart | 2 +- .../call_ambulance/call_ambulance_page.dart | 2 +- .../call_ambulance/tracking_screen.dart | 2 +- .../emergency_services_page.dart | 4 +- lib/widgets/map/{map.dart => gms_map.dart} | 0 lib/widgets/map/map_utility_screen.dart | 235 ++++++++++++++++++ 8 files changed, 317 insertions(+), 8 deletions(-) rename lib/widgets/map/{map.dart => gms_map.dart} (100%) create mode 100644 lib/widgets/map/map_utility_screen.dart diff --git a/lib/features/emergency_services/emergency_services_view_model.dart b/lib/features/emergency_services/emergency_services_view_model.dart index 5dbb89d..ad72ef4 100644 --- a/lib/features/emergency_services/emergency_services_view_model.dart +++ b/lib/features/emergency_services/emergency_services_view_model.dart @@ -20,6 +20,7 @@ import 'package:hmg_patient_app_new/features/emergency_services/models/OrderDisp import 'package:hmg_patient_app_new/features/emergency_services/models/request_model/RRTRequestModel.dart'; import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/EROnlineCheckInPaymentDetailsResponse.dart'; import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/RRTServiceData.dart'; +import 'package:hmg_patient_app_new/features/location/location_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/facility_selection.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart'; import 'package:hmg_patient_app_new/features/emergency_services/models/AmbulanceCallingPlace.dart'; @@ -54,6 +55,7 @@ import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/expandable_bottom_sheet/model/BottomSheetType.dart'; import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; +import 'package:hmg_patient_app_new/widgets/map/map_utility_screen.dart'; import 'package:hmg_patient_app_new/widgets/order_tracking/order_tracking_state.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; import 'package:huawei_map/huawei_map.dart' as HMSCameraServices; @@ -1047,12 +1049,26 @@ class EmergencyServicesViewModel extends ChangeNotifier { placeValueInController(); locationUtils!.getLocation( isShowConfirmDialog: true, - onSuccess: (position) { + onSuccess: (position) async { updateBottomSheetState(BottomSheetType.FIXED); - navServices.push( + bool result = await navServices.push( CustomPageRoute( - page: RrtMapScreen(), direction: AxisDirection.down), + page: MapUtilityScreen( + confirmButtonString: "Submit Request ".needTranslation, + titleString: "Select Location", + subTitleString: "Please select the location".needTranslation, + isGmsAvailable: appState.isGMSAvailable, + ), + direction: AxisDirection.down), ); + if(result){ + LocationViewModel locationViewModel = getIt.get(); + GeocodeResponse? response = locationViewModel.geocodeResponse; + PlaceDetails? placeDetails = locationViewModel.placeDetails; + PlacePrediction? placePrediction = locationViewModel.selectedPrediction; + submitRRTRequest(response?.results.first, placeDetails, placePrediction); + } + }); } else{ dialogService.showErrorBottomSheet( diff --git a/lib/features/location/location_view_model.dart b/lib/features/location/location_view_model.dart index e2e0cc5..c6ea34e 100644 --- a/lib/features/location/location_view_model.dart +++ b/lib/features/location/location_view_model.dart @@ -3,6 +3,8 @@ import 'dart:async'; import 'package:flutter/foundation.dart' show ChangeNotifier; import 'package:flutter/material.dart'; import 'package:google_maps_flutter_platform_interface/src/types/camera.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/features/location/GeocodeResponse.dart'; import 'package:hmg_patient_app_new/features/location/PlaceDetails.dart'; import 'package:hmg_patient_app_new/features/location/location_repo.dart'; @@ -18,7 +20,9 @@ class LocationViewModel extends ChangeNotifier { final LocationRepo locationRepo; final ErrorHandlerService errorHandlerService; - LocationViewModel({required this.locationRepo, required this.errorHandlerService}); + LocationViewModel({required this.locationRepo, required this.errorHandlerService}){ + placeValueInController(); + } List predictions = []; PlacePrediction? selectedPrediction; @@ -28,6 +32,26 @@ class LocationViewModel extends ChangeNotifier { Location? mapCapturedLocation; + Completer? gmsController; + Completer? hmsController; + + HMSCameraServices.CameraPosition getHMSLocation() { + return HMSCameraServices.CameraPosition(target: HMSCameraServices.LatLng(getIt().userLat, getIt().userLong), zoom: 18); + } + + + GMSMapServices.CameraPosition getGMSLocation() { + return GMSMapServices.CameraPosition(target: GMSMapServices.LatLng(getIt().userLat, getIt().userLong), zoom: 18); + } + + void placeValueInController() async{ + if (await getIt().isGMSAvailable) { + gmsController = Completer(); + } else { + hmsController = Completer(); + } + } + FutureOr getPlacesPrediction(String input) async { predictions = []; isPredictionLoading= true; @@ -112,5 +136,37 @@ class LocationViewModel extends ChangeNotifier { await getPlaceDetails(placePrediction.placeID); } + void moveToCurrentLocation() { + moveController(Location(lat: getIt().userLat, lng: getIt().userLong)); + } + void moveController(Location location) { + print("moving to location"); + print("gmsController is null or not $gmsController"); + if (getIt().isGMSAvailable) { + gmsController?.future.then((controller) { + controller.animateCamera( + GMSMapServices.CameraUpdate.newCameraPosition( + GMSMapServices.CameraPosition( + target: GMSMapServices.LatLng(location.lat, location.lng), + zoom: 18, + ), + ), + ); + }); + } else { + print("hmsController is null or not $hmsController"); + + hmsController?.future.then((controller) { + controller.animateCamera( + HMSCameraServices.CameraUpdate.newCameraPosition( + HMSCameraServices.CameraPosition( + target: HMSCameraServices.LatLng(location.lat, location.lng), + zoom: 18, + ), + ), + ); + }); + } + } } \ No newline at end of file diff --git a/lib/presentation/emergency_services/RRT/rrt_map_screen.dart b/lib/presentation/emergency_services/RRT/rrt_map_screen.dart index 9e11c31..3a17e5d 100644 --- a/lib/presentation/emergency_services/RRT/rrt_map_screen.dart +++ b/lib/presentation/emergency_services/RRT/rrt_map_screen.dart @@ -26,7 +26,7 @@ import 'package:hmg_patient_app_new/widgets/expandable_bottom_sheet/ExpandableBo import 'package:hmg_patient_app_new/widgets/expandable_bottom_sheet/model/BottomSheetType.dart'; import 'package:hmg_patient_app_new/widgets/input_widget.dart'; import 'package:hmg_patient_app_new/widgets/map/HMSMap.dart'; -import 'package:hmg_patient_app_new/widgets/map/map.dart'; +import 'package:hmg_patient_app_new/widgets/map/gms_map.dart'; import 'package:provider/provider.dart'; import '../../../widgets/common_bottom_sheet.dart'; diff --git a/lib/presentation/emergency_services/call_ambulance/call_ambulance_page.dart b/lib/presentation/emergency_services/call_ambulance/call_ambulance_page.dart index fea52a3..043c231 100644 --- a/lib/presentation/emergency_services/call_ambulance/call_ambulance_page.dart +++ b/lib/presentation/emergency_services/call_ambulance/call_ambulance_page.dart @@ -23,7 +23,7 @@ import 'package:hmg_patient_app_new/widgets/expandable_bottom_sheet/ExpandableBo import 'package:hmg_patient_app_new/widgets/expandable_bottom_sheet/model/BottomSheetType.dart'; import 'package:hmg_patient_app_new/widgets/input_widget.dart'; import 'package:hmg_patient_app_new/widgets/map/HMSMap.dart'; -import 'package:hmg_patient_app_new/widgets/map/map.dart'; +import 'package:hmg_patient_app_new/widgets/map/gms_map.dart'; import 'package:provider/provider.dart'; import '../../../widgets/common_bottom_sheet.dart'; diff --git a/lib/presentation/emergency_services/call_ambulance/tracking_screen.dart b/lib/presentation/emergency_services/call_ambulance/tracking_screen.dart index 4a9231a..e5dc0b9 100644 --- a/lib/presentation/emergency_services/call_ambulance/tracking_screen.dart +++ b/lib/presentation/emergency_services/call_ambulance/tracking_screen.dart @@ -14,7 +14,7 @@ import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:hmg_patient_app_new/widgets/map/HMSMap.dart'; -import 'package:hmg_patient_app_new/widgets/map/map.dart' show GMSMap; +import 'package:hmg_patient_app_new/widgets/map/gms_map.dart' show GMSMap; import 'package:hmg_patient_app_new/widgets/order_tracking/order_tracking_state.dart'; import 'package:hmg_patient_app_new/widgets/order_tracking/request_tracking.dart'; import 'package:lottie/lottie.dart'; diff --git a/lib/presentation/emergency_services/emergency_services_page.dart b/lib/presentation/emergency_services/emergency_services_page.dart index 8f6fd23..bce7daf 100644 --- a/lib/presentation/emergency_services/emergency_services_page.dart +++ b/lib/presentation/emergency_services/emergency_services_page.dart @@ -256,7 +256,9 @@ class EmergencyServicesPage extends StatelessWidget { isCloseButtonVisible: false, hasBottomPadding: false, backgroundColor: AppColors.primaryRedColor, - callBackFunc: () {}, + callBackFunc: () { + context.read().setTermsAndConditions(false); + }, ); }), ), diff --git a/lib/widgets/map/map.dart b/lib/widgets/map/gms_map.dart similarity index 100% rename from lib/widgets/map/map.dart rename to lib/widgets/map/gms_map.dart diff --git a/lib/widgets/map/map_utility_screen.dart b/lib/widgets/map/map_utility_screen.dart new file mode 100644 index 0000000..1fd9dca --- /dev/null +++ b/lib/widgets/map/map_utility_screen.dart @@ -0,0 +1,235 @@ +import 'dart:io'; + +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_export.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/emergency_services/emergency_services_view_model.dart'; +import 'package:hmg_patient_app_new/features/emergency_services/models/AmbulanceCallingPlace.dart'; +import 'package:hmg_patient_app_new/features/location/GeocodeResponse.dart'; +import 'package:hmg_patient_app_new/features/location/PlaceDetails.dart'; +import 'package:hmg_patient_app_new/features/location/PlacePrediction.dart'; +import 'package:hmg_patient_app_new/features/location/location_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_doctor_card.dart'; +import 'package:hmg_patient_app_new/presentation/emergency_services/call_ambulance/widgets/AddressItem.dart'; +import 'package:hmg_patient_app_new/presentation/emergency_services/call_ambulance/widgets/HospitalBottomSheetBody.dart'; +import 'package:hmg_patient_app_new/presentation/emergency_services/call_ambulance/widgets/appointment_bottom_sheet.dart' show AppointmentBottomSheet; +import 'package:hmg_patient_app_new/presentation/emergency_services/widgets/location_input_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/CustomSwitch.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/expandable_bottom_sheet/ExpandableBottomSheet.dart'; +import 'package:hmg_patient_app_new/widgets/expandable_bottom_sheet/model/BottomSheetType.dart'; +import 'package:hmg_patient_app_new/widgets/input_widget.dart'; +import 'package:hmg_patient_app_new/widgets/map/HMSMap.dart'; +import 'package:hmg_patient_app_new/widgets/map/gms_map.dart'; +import 'package:provider/provider.dart'; + +import '../../../widgets/common_bottom_sheet.dart'; + +class MapUtilityScreen extends StatelessWidget { + + final String confirmButtonString; + final String titleString; + final String subTitleString; + final bool isGmsAvailable; + final VoidCallback? onCrossClicked; + + const MapUtilityScreen({super.key, required this.confirmButtonString, required this.titleString, required this.subTitleString, required this.isGmsAvailable, this.onCrossClicked}); + + @override + Widget build(BuildContext context) { + return Scaffold( + floatingActionButton: Padding( + padding: EdgeInsetsDirectional.only(end: 8.h, bottom: 68.h), + child: DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, borderRadius: 12.h), + child: Utils.buildSvgWithAssets( + icon: AppAssets.locate_me, width: 24.h, height: 24.h) + .paddingAll(12.h) + .onPress(() { + context + .read() + .moveToCurrentLocation(); + }), + ), + ), + bottomSheet: FixedBottomSheet(context), + body: Stack( + children: [ + if (isGmsAvailable) + GMSMap( + currentLocation: + context.read().getGMSLocation(), + onCameraMoved: (value) => context + .read() + .handleGMSMapCameraMoved(value), + onCameraIdle: + context.read().handleOnCameraIdle, + myLocationEnabled: true, + inputController: + context.read().gmsController, + showCenterMarker: true, + ) + else + HMSMap( + currentLocation: + context.read().getHMSLocation(), + onCameraMoved: (value) => context + .read() + .handleHMSMapCameraMoved(value), + onCameraIdle: + context.read().handleOnCameraIdle, + myLocationEnabled: false, + inputController: + context.read().hmsController, + showCenterMarker: true, + ), + Align( + alignment: AlignmentDirectional.topStart, + child: Utils.buildSvgWithAssets( + icon: AppAssets.closeBottomNav, width: 32.h, height: 32.h) + .onPress(() { + onCrossClicked?.call(); + // context + // .read() + // .flushPickupInformation(); + + Navigator.pop(context, false); + }), + ).paddingOnly(top: 51.h, left: 24.h), + ], + ), + ); + } + + Widget FixedBottomSheet(BuildContext context) { + return GestureDetector( + onVerticalDragUpdate: (details){ + }, + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisSize: MainAxisSize.min, + spacing: 24.h, + children: [ + inputFields(context).paddingSymmetrical(16.h, 0.h), + SizedBox( + child: DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.scaffoldBgColor, + customBorder: BorderRadius.only( + topLeft: Radius.circular(24.h), + topRight: Radius.circular(24.h), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 24.h, + children: [ + Column( + spacing: 4.h, + children: [ + titleString.toText21( + weight: FontWeight.w600, + color: AppColors.textColor, + ), + subTitleString.needTranslation.toText12( + fontWeight: FontWeight.w500, + color: AppColors.greyTextColor, + ) + ], + ), + CustomButton( + text: confirmButtonString.needTranslation, + onPressed: () { + ///indicates that the screen has resulted success and should be closed + Navigator.pop(context,true); + }, + ) + ], + ).paddingOnly(top: 24.h, bottom: 32.h, left: 24.h, right: 24.h), + ), + ), + ], + ), + ], + ), + ); + } + + leadingIcon(String leadingIcon) { + return Container( + height: 40.h, + width: 40.h, + margin: EdgeInsets.only(right: 10.h), + padding: EdgeInsets.all(8.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + borderRadius: 12.h, + color: AppColors.greyColor, + ), + child: Utils.buildSvgWithAssets(icon: leadingIcon), + ); + } + + + + textPlaceInput(context) { + return Consumer(builder: (_, vm, __) { + return SizedBox( + width: MediaQuery.sizeOf(context).width, + child: TextInputWidget( + labelText: "Enter Pickup Location Manually".needTranslation, + hintText: "Enter Pickup Location".needTranslation, + controller: TextEditingController( + text: vm.geocodeResponse?.results.first.formattedAddress ?? + vm.selectedPrediction?.description, + ), + leadingIcon: AppAssets.location_pickup, + isAllowLeadingIcon: true, + isEnable: false, + prefix: null, + autoFocus: false, + isBorderAllowed: false, + keyboardType: TextInputType.text, + padding: EdgeInsets.symmetric( + vertical: ResponsiveExtension(10).h, + horizontal: ResponsiveExtension(15).h, + ), + ).onPress(() { + openLocationInputBottomSheet(context); + }), + ); + }); + } + + ///decide which field to show first based on the selected calling place + Widget inputFields(BuildContext context) { + return textPlaceInput(context); + } + + openLocationInputBottomSheet(BuildContext context) { + context.read().flushSearchPredictions(); + showCommonBottomSheetWithoutHeight( + title: "".needTranslation, + context, + child: SizedBox( + height: MediaQuery.sizeOf(context).height * .8, + child: LocationInputBottomSheet(), + ), + isFullScreen: false, + isCloseButtonVisible: true, + hasBottomPadding: false, + backgroundColor: AppColors.bottomSheetBgColor, + callBackFunc: () {}, + ); + } +} From cbaea9d5e6114b63087f7446dfc89f258cd2d258 Mon Sep 17 00:00:00 2001 From: tahaalam Date: Sun, 23 Nov 2025 15:13:28 +0300 Subject: [PATCH 12/12] screen documentation added. --- lib/widgets/map/map_utility_screen.dart | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/widgets/map/map_utility_screen.dart b/lib/widgets/map/map_utility_screen.dart index 1fd9dca..87c99fc 100644 --- a/lib/widgets/map/map_utility_screen.dart +++ b/lib/widgets/map/map_utility_screen.dart @@ -31,6 +31,16 @@ import 'package:provider/provider.dart'; import '../../../widgets/common_bottom_sheet.dart'; +/// screen to be used to get the location desired by the user +/// to place the values in the request. +/// [confirmButtonString] button text that will be displayed on the button +/// [titleString] bottom sheet title +/// [subTitleString] bottom sheet subtitle for details +/// [onCrossClicked] if something has to be done if the user close the screen +/// [isGmsAvailable] shows if the device that is running the application is GMS or HMS +/// +/// it results [true] if the user clicks on the submit button +/// and [false] if the user closes the screen without giving the consent to proceed for the request class MapUtilityScreen extends StatelessWidget { final String confirmButtonString;