diff --git a/assets/images/svg/all_payment_method.svg b/assets/images/svg/all_payment_method.svg new file mode 100644 index 0000000..ef72e6a --- /dev/null +++ b/assets/images/svg/all_payment_method.svg @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/assets/images/svg/ic_rrt_vehicle.svg b/assets/images/svg/ic_rrt_vehicle.svg new file mode 100644 index 0000000..d858fb4 --- /dev/null +++ b/assets/images/svg/ic_rrt_vehicle.svg @@ -0,0 +1,5 @@ + + + + + diff --git a/assets/images/svg/mada.svg b/assets/images/svg/mada.svg new file mode 100644 index 0000000..99fd132 --- /dev/null +++ b/assets/images/svg/mada.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/ios/Runner/AppDelegate.swift b/ios/Runner/AppDelegate.swift index b276dd7..6a5d34f 100644 --- a/ios/Runner/AppDelegate.swift +++ b/ios/Runner/AppDelegate.swift @@ -1,7 +1,7 @@ import Flutter import UIKit -import FirebaseCore -import FirebaseMessaging +//import FirebaseCore +//import FirebaseMessaging import GoogleMaps @main @objc class AppDelegate: FlutterAppDelegate { @@ -10,13 +10,13 @@ import GoogleMaps didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? ) -> Bool { GMSServices.provideAPIKey("AIzaSyB6TERnxIr0yJ3qG4ULBZbu0sAD4tGqtng") - FirebaseApp.configure() +// FirebaseApp.configure() GeneratedPluginRegistrant.register(with: self) return super.application(application, didFinishLaunchingWithOptions: launchOptions) } override func application(_ application: UIApplication, didRegisterForRemoteNotificationsWithDeviceToken deviceToken:Data){ - Messaging.messaging().apnsToken = deviceToken +// Messaging.messaging().apnsToken = deviceToken super.application(application, didRegisterForRemoteNotificationsWithDeviceToken: deviceToken) } } diff --git a/lib/core/api/api_client.dart b/lib/core/api/api_client.dart index 28475ea..aa534f6 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -161,10 +161,11 @@ class ApiClientImp implements ApiClient { // body['VersionID'] = ApiConsts.appVersionID.toString(); if (!isExternal) { - body['VersionID'] = ApiConsts.appVersionID; - body['Channel'] = ApiConsts.appChannelId; + body['VersionID'] = ApiConsts.appVersionID.toString(); + body['Channel'] = ApiConsts.appChannelId.toString(); body['IPAdress'] = ApiConsts.appIpAddress; body['generalid'] = ApiConsts.appGeneralId; + body['LanguageID'] = _appState.getLanguageID().toString(); body['Latitude'] = _appState.userLat.toString(); body['Longitude'] = _appState.userLong.toString(); @@ -183,6 +184,9 @@ class ApiClientImp implements ApiClient { } body.removeWhere((key, value) => value == null); + log("uri: ${Uri.parse(url.trim())}"); + + log("body: ${json.encode(body)}"); final bool networkStatus = await Utils.checkConnection(bypassConnectionCheck: bypassConnectionCheck); @@ -201,6 +205,7 @@ class ApiClientImp implements ApiClient { log("uri: ${Uri.parse(url.trim())}"); log("body: ${json.encode(body)}"); log("response.body: ${response.body}"); + // log("response.body: ${response.body}"); if (statusCode < 200 || statusCode >= 400) { onFailure('Error While Fetching data', statusCode, failureType: StatusCodeFailure("Error While Fetching data")); logApiEndpointError(endPoint, 'Error While Fetching data', statusCode); @@ -364,7 +369,7 @@ class ApiClientImp implements ApiClient { debugPrint("URL : $url"); // print("Body : ${json.encode(body)}"); - if (await Utils.checkConnection()) { + if (await Utils.checkConnection(bypassConnectionCheck: true)) { final response = await http.get( Uri.parse(url.trim()), headers: {'Content-Type': 'application/json', 'Accept': 'application/json'}, diff --git a/lib/core/api_consts.dart b/lib/core/api_consts.dart index 4cedda1..2be40d0 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -753,7 +753,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/"; @@ -763,7 +763,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/"; @@ -783,7 +783,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/"; @@ -793,7 +793,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; } } @@ -837,16 +837,18 @@ class ApiConsts { static final String createAdvancePayments = 'Services/Patients.svc/REST/HIS_CreateAdvancePayment'; static final String addAdvanceNumberRequest = 'Services/PayFort_Serv.svc/REST/AddAdvancedNumberRequest'; - // RC CMC ServIces + // RC COMPREHENSIVE MEDICAL CHECKUP ServIces static final String allCMCOrdersRc = 'api/cmc/list'; static final String allCMCServicesRc = 'api/cmc/getallcmc'; static final String updateCMCOrder = 'api/cmc/update'; + static final String addCMCOrder = 'api/cmc/add'; + static final String getHospitalsList = 'Services/Lists.svc/REST/GetProject'; - // CMC RC SERVICES - var GET_ALL_CMC_SERVICES_RC = 'api/cmc/getallcmc'; - var ADD_CMC_ORDER_RC = 'api/cmc/add'; - var GET_ALL_CMC_ORDERS_RC = 'api/cmc/list'; - var UPDATE_CMC_ORDER_RC = 'api/cmc/update'; + // RC HOME HEALTHCARE ServIces + static final String allHHCOrdersRc = 'api/hhc/list'; + static final String allHHCServicesRc = 'api/HHC/getallhhc'; + static final String updateHHCOrder = 'api/hhc/update'; + static final String addHHCOrder = 'api/HHC/add'; //E-REFERRAL SERVICES @@ -860,7 +862,7 @@ class ApiConsts { // ************ static values for Api **************** - static final double appVersionID = 20.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/core/app_assets.dart b/lib/core/app_assets.dart index 3f524be..5fccc6e 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -173,6 +173,9 @@ class AppAssets { static const String forward_arrow_medium = '$svgBasePath/forward_arrow_medium.svg'; static const String eReferral = '$svgBasePath/e-referral.svg'; static const String comprehensiveCheckup = '$svgBasePath/comprehensive_checkup.svg'; + static const String all_payment_method = '$svgBasePath/all_payment_method.svg'; + static const String ic_rrt_vehicle = '$svgBasePath/ic_rrt_vehicle.svg'; + //bottom navigation// static const String homeBottom = '$svgBasePath/home_bottom.svg'; diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index 835e9bf..73a93c6 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -122,7 +122,13 @@ class AppDependencies { getIt.registerLazySingleton(() => LabViewModel(labRepo: getIt(), errorHandlerService: getIt(), navigationService: getIt())); - getIt.registerLazySingleton(() => RadiologyViewModel(radiologyRepo: getIt(), errorHandlerService: getIt())); + getIt.registerLazySingleton( + () => RadiologyViewModel( + radiologyRepo: getIt(), + errorHandlerService: getIt(), + navigationService: getIt() + ), + ); getIt.registerLazySingleton(() => PrescriptionsViewModel(prescriptionsRepo: getIt(), errorHandlerService: getIt())); @@ -215,7 +221,7 @@ class AppDependencies { ); getIt.registerLazySingleton( - () => HmgServicesViewModel(hmgServicesRepo: getIt(), errorHandlerService: getIt()), + () => HmgServicesViewModel(bookAppointmentsRepo: getIt(), hmgServicesRepo: getIt(), errorHandlerService: getIt()), ); // Screen-specific VMs → Factory diff --git a/lib/core/utils/date_util.dart b/lib/core/utils/date_util.dart index 82b9909..a918706 100644 --- a/lib/core/utils/date_util.dart +++ b/lib/core/utils/date_util.dart @@ -6,6 +6,8 @@ class DateUtil { /// convert String To Date function /// [date] String we want to convert static DateTime convertStringToDate(String? date) { + + if (date == null) return DateTime.now(); if (date.isEmpty) return DateTime.now(); diff --git a/lib/core/utils/utils.dart b/lib/core/utils/utils.dart index 6f1dcc6..e3b108f 100644 --- a/lib/core/utils/utils.dart +++ b/lib/core/utils/utils.dart @@ -102,8 +102,9 @@ class Utils { ? getMonthArabic(dateTime.month) + " " + dateTime.day.toString() + ", " + dateTime.year.toString() : getMonth(dateTime.month) + " " + dateTime.day.toString() + ", " + dateTime.year.toString(); } + static String getDayMonthYearDateFormatted(DateTime? dateTime) { - if(dateTime == null ) return ""; + if (dateTime == null) return ""; return appState.isArabic() ? "${dateTime.day.toString()} ${getMonthArabic(dateTime.month)}, ${dateTime.year.toString()}" : "${dateTime.day.toString()} ${getMonth(dateTime.month)}, ${dateTime.year.toString()}"; @@ -376,16 +377,21 @@ class Utils { ).center; } - static Widget getWarningWidget( - {String? loadingText, bool isShowActionButtons = false, Widget? bodyWidget, Function? onConfirmTap, Function? onCancelTap}) { + static Widget getWarningWidget({ + String? loadingText, + bool isShowActionButtons = false, + Widget? bodyWidget, + Function? onConfirmTap, + Function? onCancelTap, + }) { return Column( mainAxisAlignment: MainAxisAlignment.center, crossAxisAlignment: CrossAxisAlignment.center, children: [ Lottie.asset(AppAnimations.warningAnimation, - repeat: false, reverse: false, frameRate: FrameRate(60), width: 128.h, height: 128.h, fit: BoxFit.fill), + repeat: false, reverse: false, frameRate: FrameRate(60), width: 100.h, height: 100.h, fit: BoxFit.fill), SizedBox(height: 8.h), - (loadingText ?? LocaleKeys.loadingText.tr()).toText14(color: AppColors.blackColor, letterSpacing: 0), + (loadingText ?? LocaleKeys.loadingText.tr()).toText15(color: AppColors.blackColor, letterSpacing: 0), SizedBox(height: 16.h), bodyWidget ?? SizedBox.shrink(), SizedBox(height: 16.h), @@ -753,14 +759,15 @@ class Utils { ); } - static Widget getPaymentAmountWithSymbol2(num habibWalletAmount, - {double iconSize = 14, + static Widget getPaymentAmountWithSymbol2( + num habibWalletAmount, { + double iconSize = 14, double? fontSize, double? letterSpacing, FontWeight? fontWeight, Color iconColor = AppColors.textColor, - Color textColor = AppColors.blackColor, - bool isSaudiCurrency = true, + Color textColor = AppColors.blackColor, + bool isSaudiCurrency = true, bool isExpanded = true, }) { return RichText( @@ -777,7 +784,7 @@ class Utils { style: TextStyle( color: textColor, fontSize: fontSize ?? 32.f, - letterSpacing: letterSpacing??-4, + letterSpacing: letterSpacing ?? -4, fontWeight: fontWeight ?? FontWeight.w600, height: 1), ), @@ -839,7 +846,12 @@ class Utils { static PatientDoctorAppointmentList? convertToPatientDoctorAppointmentList(HospitalsModel? hospital) { if (hospital == null) return null; return PatientDoctorAppointmentList( - filterName: hospital.name, distanceInKMs: hospital.distanceInKilometers?.toString(), projectTopName: hospital.name, projectBottomName: hospital.name, model: hospital, isHMC: hospital.isHMC); + filterName: hospital.name, + distanceInKMs: hospital.distanceInKilometers?.toString(), + projectTopName: hospital.name, + projectBottomName: hospital.name, + model: hospital, + isHMC: hospital.isHMC); } static bool havePrivilege(int id) { @@ -853,7 +865,4 @@ class Utils { } return isHavePrivilege; } - - - } diff --git a/lib/extensions/string_extensions.dart b/lib/extensions/string_extensions.dart index 1a6d1cc..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( @@ -223,6 +225,7 @@ extension EmailValidator on String { FontWeight? weight, TextOverflow? textOverflow, double? letterSpacing = -0.4, + Color decorationColor =AppColors.errorColor }) => Text( this, @@ -236,6 +239,7 @@ extension EmailValidator on String { overflow: textOverflow, fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal), decoration: isUnderLine ? TextDecoration.underline : null, + decorationColor: decorationColor ), ); diff --git a/lib/extensions/widget_extensions.dart b/lib/extensions/widget_extensions.dart index 424aa88..70f10bb 100644 --- a/lib/extensions/widget_extensions.dart +++ b/lib/extensions/widget_extensions.dart @@ -1,9 +1,8 @@ -import 'package:hmg_patient_app_new/core/enums.dart'; -import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:flutter/material.dart'; -import 'package:flutter/widgets.dart'; +import 'package:hmg_patient_app_new/core/enums.dart'; import 'package:hmg_patient_app_new/extensions/int_extensions.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:shimmer/shimmer.dart'; import 'package:sizer/sizer.dart'; import 'package:smooth_corner/smooth_corner.dart'; @@ -19,7 +18,8 @@ extension WidgetExtensions on Widget { Widget paddingAll(double _value) => Padding(padding: EdgeInsets.all(_value), child: this); - Widget paddingSymmetrical(double horizontal, double vertical) => Padding(padding: EdgeInsets.symmetric(horizontal: horizontal, vertical: vertical), child: this); + Widget paddingSymmetrical(double horizontal, double vertical) => + Padding(padding: EdgeInsets.symmetric(horizontal: horizontal, vertical: vertical), child: this); Widget paddingOnly({double left = 0.0, double right = 0.0, double top = 0.0, double bottom = 0.0}) => Padding(padding: EdgeInsetsDirectional.only(start: left, end: right, top: top, bottom: bottom), child: this); @@ -99,7 +99,7 @@ extension WidgetExtensions on Widget { bool disablePadding = false, double radius = 20, Color? color, - Color borderColor = AppColors.buttonColor, + Color? borderColor, bool disableWidth = false, bool isAlignment = false}) { return Container( @@ -110,7 +110,7 @@ extension WidgetExtensions on Widget { ), color: color, border: Border.all( - color: borderColor, + color: borderColor ?? Colors.transparent, width: disableWidth ? 2 : 1, ), ), diff --git a/lib/features/book_appointments/book_appointments_view_model.dart b/lib/features/book_appointments/book_appointments_view_model.dart index f24766b..a50b683 100644 --- a/lib/features/book_appointments/book_appointments_view_model.dart +++ b/lib/features/book_appointments/book_appointments_view_model.dart @@ -3,7 +3,6 @@ import 'dart:async'; import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; -import 'package:hmg_patient_app_new/core/cache_consts.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/location_util.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.dart'; @@ -105,8 +104,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { List searchedRegionList = []; List facilityList = ["hmgHospitals", "hmcMedicalClinic"]; List searchedHospitalList = []; - List - searchedPatientDoctorAppointmentHospitalsList = []; + List searchedPatientDoctorAppointmentHospitalsList = []; List searchedClinicList = []; PatientDoctorAppointmentList? selectedHospitalForFilters; @@ -114,15 +112,14 @@ class BookAppointmentsViewModel extends ChangeNotifier { String? selectedClinicForFilters; bool applyFilters = false; - ///variables for laser clinic - List femaleLaserCategory = [ + List femaleLaserCategory = [ LaserCategoryType(1, 'bodyString'), LaserCategoryType(2, 'face'), - LaserCategoryType(10,'bikini'), + LaserCategoryType(10, 'bikini'), LaserCategoryType(11, 'retouch'), ]; - List maleLaserCategory =[ + List maleLaserCategory = [ LaserCategoryType(1, 'body'), LaserCategoryType(2, 'face'), LaserCategoryType(11, 'retouch'), @@ -136,9 +133,13 @@ class BookAppointmentsViewModel extends ChangeNotifier { bool isBodyPartsLoading = false; int duration = 0; - BookAppointmentsViewModel( - {required this.bookAppointmentsRepo, required this.errorHandlerService, required this.navigationService, required this.myAppointmentsViewModel, required this.locationUtils, required this.dialogService }) { + {required this.bookAppointmentsRepo, + required this.errorHandlerService, + required this.navigationService, + required this.myAppointmentsViewModel, + required this.locationUtils, + required this.dialogService}) { initBookAppointmentViewModel(); } @@ -287,7 +288,8 @@ class BookAppointmentsViewModel extends ChangeNotifier { Future getLiveCareScheduleClinics({Function(dynamic)? onSuccess, Function(String)? onError}) async { liveCareClinicsList.clear(); - final result = await bookAppointmentsRepo.getLiveCareScheduleClinics(_appState.getAuthenticatedUser()!.age!, _appState.getAuthenticatedUser()!.gender!); + final result = + await bookAppointmentsRepo.getLiveCareScheduleClinics(_appState.getAuthenticatedUser()!.age!, _appState.getAuthenticatedUser()!.gender!); result.fold( (failure) async => await errorHandlerService.handleError(failure: failure), @@ -309,8 +311,9 @@ class BookAppointmentsViewModel extends ChangeNotifier { Future getLiveCareDoctorsList({Function(dynamic)? onSuccess, Function(String)? onError}) async { doctorsList.clear(); - final result = - await bookAppointmentsRepo.getLiveCareDoctorsList(selectedLiveCareClinic.serviceID!, _appState.getAuthenticatedUser()!.age!, _appState.getAuthenticatedUser()!.gender!, onError: onError); + final result = await bookAppointmentsRepo.getLiveCareDoctorsList( + selectedLiveCareClinic.serviceID!, _appState.getAuthenticatedUser()!.age!, _appState.getAuthenticatedUser()!.gender!, + onError: onError); result.fold( (failure) async { @@ -333,10 +336,17 @@ class BookAppointmentsViewModel extends ChangeNotifier { } //TODO: Make the API dynamic with parameters for ProjectID, isNearest, languageID, doctorId, doctorName - Future getDoctorsList({int projectID = 0, bool isNearest = false, int doctorId = 0, String doctorName = "", Function(dynamic)? onSuccess, Function(String)? onError}) async { + Future getDoctorsList( + {int projectID = 0, + bool isNearest = false, + int doctorId = 0, + String doctorName = "", + Function(dynamic)? onSuccess, + Function(String)? onError}) async { doctorsList.clear(); projectID = currentlySelectedHospitalFromRegionFlow != null ? int.parse(currentlySelectedHospitalFromRegionFlow!) : projectID; - final result = await bookAppointmentsRepo.getDoctorsList(selectedClinic.clinicID ?? 0, projectID, isNearest, doctorId, doctorName, isContinueDentalPlan: isContinueDentalPlan); + final result = await bookAppointmentsRepo.getDoctorsList(selectedClinic.clinicID ?? 0, projectID, isNearest, doctorId, doctorName, + isContinueDentalPlan: isContinueDentalPlan); result.fold( (failure) async { @@ -365,7 +375,13 @@ class BookAppointmentsViewModel extends ChangeNotifier { } Future getMappedDoctors( - {int projectID = 0, bool isNearest = false, int doctorId = 0, String doctorName = "", isContinueDentalPlan = false, Function(dynamic)? onSuccess, Function(String)? onError}) async { + {int projectID = 0, + bool isNearest = false, + int doctorId = 0, + String doctorName = "", + isContinueDentalPlan = false, + Function(dynamic)? onSuccess, + Function(String)? onError}) async { filteredHospitalList = null; hospitalList = null; isRegionListLoading = true; @@ -374,10 +390,10 @@ class BookAppointmentsViewModel extends ChangeNotifier { final result = await bookAppointmentsRepo.getDoctorsList(selectedClinic.clinicID ?? 0, projectID, isNearest, doctorId, doctorName); result.fold( - (failure) async { + (failure) async { onError?.call("No doctors found for the search criteria".needTranslation); }, - (apiResponse) async { + (apiResponse) async { if (apiResponse.messageStatus == 2) { // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); } else if (apiResponse.messageStatus == 1) { @@ -401,7 +417,8 @@ class BookAppointmentsViewModel extends ChangeNotifier { } Future getDoctorProfile({Function(dynamic)? onSuccess, Function(String)? onError}) async { - final result = await bookAppointmentsRepo.getDoctorProfile(selectedDoctor.clinicID ?? 0, selectedDoctor.projectID ?? 0, selectedDoctor.doctorID ?? 0, onError: onError); + final result = await bookAppointmentsRepo + .getDoctorProfile(selectedDoctor.clinicID ?? 0, selectedDoctor.projectID ?? 0, selectedDoctor.doctorID ?? 0, onError: onError); result.fold( (failure) async {}, @@ -457,7 +474,8 @@ class BookAppointmentsViewModel extends ChangeNotifier { // : date = DateUtil.convertStringToDateSaudiTimezone(element, int.parse(selectedDoctor.projectID.toString())); slotsList.add(FreeSlot(date, ['slot'])); - docFreeSlots.add(TimeSlot(isoTime: formatter.format(date), start: new DateTime(date.year, date.month, date.day, 0, 0, 0, 0), end: date, vidaDate: element)); + docFreeSlots.add(TimeSlot( + isoTime: formatter.format(date), start: new DateTime(date.year, date.month, date.day, 0, 0, 0, 0), end: date, vidaDate: element)); }); notifyListeners(); @@ -476,8 +494,8 @@ class BookAppointmentsViewModel extends ChangeNotifier { final DateFormat dateFormatter = DateFormat('yyyy-MM-dd'); Map _eventsParsed; - final result = await bookAppointmentsRepo.getLiveCareDoctorFreeSlots( - selectedDoctor.clinicID ?? 0, selectedLiveCareClinic.serviceID ?? 0, selectedDoctor.projectID ?? 0, selectedDoctor.doctorID ?? 0, isBookingForLiveCare, + final result = await bookAppointmentsRepo.getLiveCareDoctorFreeSlots(selectedDoctor.clinicID ?? 0, selectedLiveCareClinic.serviceID ?? 0, + selectedDoctor.projectID ?? 0, selectedDoctor.doctorID ?? 0, isBookingForLiveCare, onError: onError); result.fold( @@ -501,7 +519,8 @@ class BookAppointmentsViewModel extends ChangeNotifier { // : date = DateUtil.convertStringToDateSaudiTimezone(element, int.parse(selectedDoctor.projectID.toString())); slotsList.add(FreeSlot(date, ['slot'])); - docFreeSlots.add(TimeSlot(isoTime: formatter.format(date), start: new DateTime(date.year, date.month, date.day, 0, 0, 0, 0), end: date, vidaDate: element)); + docFreeSlots.add(TimeSlot( + isoTime: formatter.format(date), start: new DateTime(date.year, date.month, date.day, 0, 0, 0, 0), end: date, vidaDate: element)); }); notifyListeners(); @@ -513,7 +532,10 @@ class BookAppointmentsViewModel extends ChangeNotifier { ); } - Future cancelAppointment({required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, Function(dynamic)? onSuccess, Function(String)? onError}) async { + Future cancelAppointment( + {required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, + Function(dynamic)? onSuccess, + Function(String)? onError}) async { final result = await bookAppointmentsRepo.cancelAppointment(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel); result.fold( @@ -597,13 +619,15 @@ class BookAppointmentsViewModel extends ChangeNotifier { await cancelAppointment(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel).then((val) async { navigationService.pop(); Future.delayed(Duration(milliseconds: 50)).then((value) async {}); - LoadingUtils.showFullScreenLoader(barrierDismissible: true, isSuccessDialog: false, loadingText: "Booking your appointment...".needTranslation); + LoadingUtils.showFullScreenLoader( + barrierDismissible: true, isSuccessDialog: false, loadingText: "Booking your appointment...".needTranslation); await insertSpecificAppointment( onError: (err) {}, onSuccess: (apiResp) async { LoadingUtils.hideFullScreenLoader(); await Future.delayed(Duration(milliseconds: 50)).then((value) async { - LoadingUtils.showFullScreenLoader(barrierDismissible: true, isSuccessDialog: true, loadingText: LocaleKeys.appointmentSuccess.tr()); + LoadingUtils.showFullScreenLoader( + barrierDismissible: true, isSuccessDialog: true, loadingText: LocaleKeys.appointmentSuccess.tr()); await Future.delayed(Duration(milliseconds: 4000)).then((value) { LoadingUtils.hideFullScreenLoader(); Navigator.pushAndRemoveUntil( @@ -693,13 +717,15 @@ class BookAppointmentsViewModel extends ChangeNotifier { await cancelAppointment(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel).then((val) async { navigationService.pop(); Future.delayed(Duration(milliseconds: 50)).then((value) async {}); - LoadingUtils.showFullScreenLoader(barrierDismissible: true, isSuccessDialog: false, loadingText: "Booking your appointment...".needTranslation); + LoadingUtils.showFullScreenLoader( + barrierDismissible: true, isSuccessDialog: false, loadingText: "Booking your appointment...".needTranslation); await insertSpecificAppointment( onError: (err) {}, onSuccess: (apiResp) async { LoadingUtils.hideFullScreenLoader(); await Future.delayed(Duration(milliseconds: 50)).then((value) async { - LoadingUtils.showFullScreenLoader(barrierDismissible: true, isSuccessDialog: true, loadingText: LocaleKeys.appointmentSuccess.tr()); + LoadingUtils.showFullScreenLoader( + barrierDismissible: true, isSuccessDialog: true, loadingText: LocaleKeys.appointmentSuccess.tr()); await Future.delayed(Duration(milliseconds: 4000)).then((value) { LoadingUtils.hideFullScreenLoader(); Navigator.pushAndRemoveUntil( @@ -773,7 +799,9 @@ class BookAppointmentsViewModel extends ChangeNotifier { } else { filteredHospitalList = RegionList(); - var list = isHMG ? hospitalList?.registeredDoctorMap![selectedRegionId]!.hmgDoctorList : hospitalList?.registeredDoctorMap![selectedRegionId]!.hmcDoctorList; + var list = isHMG + ? hospitalList?.registeredDoctorMap![selectedRegionId]!.hmgDoctorList + : hospitalList?.registeredDoctorMap![selectedRegionId]!.hmcDoctorList; if (list != null && list.isEmpty) { notifyListeners(); @@ -856,12 +884,8 @@ class BookAppointmentsViewModel extends ChangeNotifier { notifyListeners(); } - void setSelections( - List? selectedFacilityForFilters, - List? selectedRegionForFilters, - String? selectedClinicForFilters, - PatientDoctorAppointmentList? selectedHospitalForFilters, - bool applyFilters) { + void setSelections(List? selectedFacilityForFilters, List? selectedRegionForFilters, String? selectedClinicForFilters, + PatientDoctorAppointmentList? selectedHospitalForFilters, bool applyFilters) { this.selectedFacilityForFilters = selectedFacilityForFilters; this.selectedClinicForFilters = selectedClinicForFilters; this.selectedHospitalForFilters = selectedHospitalForFilters; @@ -872,17 +896,14 @@ class BookAppointmentsViewModel extends ChangeNotifier { void getFiltersFromDoctorList() { doctorsList.forEach((element) { - if (!searchedRegionList - .contains(element.getRegionName(_appState.isArabic()))) { - searchedRegionList - .add(element.getRegionName(_appState.isArabic()) ?? ""); + if (!searchedRegionList.contains(element.getRegionName(_appState.isArabic()))) { + searchedRegionList.add(element.getRegionName(_appState.isArabic()) ?? ""); } if (!searchedHospitalList.contains(element.projectName)) { - searchedPatientDoctorAppointmentHospitalsList - .add(PatientDoctorAppointmentList() - ..filterName = element.projectName - ..isHMC = element.isHMC - ..distanceInKMs = "0"); + searchedPatientDoctorAppointmentHospitalsList.add(PatientDoctorAppointmentList() + ..filterName = element.projectName + ..isHMC = element.isHMC + ..distanceInKMs = "0"); searchedHospitalList.add(element.projectName ?? ""); } if (!searchedClinicList.contains(element.clinicName)) { @@ -939,27 +960,15 @@ class BookAppointmentsViewModel extends ChangeNotifier { return doctorsList; } var list = doctorsList.where((element) { - var isInSelectedRegion = (selectedRegionForFilters?.isEmpty == true) - ? true - : selectedRegionForFilters - ?.any((region) => region == element.getRegionName(isArabic())); - var shouldApplyFacilityFilter = - (selectedFacilityForFilters?.isEmpty == true) ? false : true; - var isHMC = (selectedFacilityForFilters?.isEmpty == true) - ? true - : selectedFacilityForFilters?.any((item) => item.contains("hmc")); - var isInSelectedClinic = (selectedClinicForFilters == null) - ? true - : selectedClinicForFilters == element.clinicName; - var isInSelectedHospital = (selectedHospitalForFilters == null) - ? true - : element.projectName == selectedHospitalForFilters?.filterName; + var isInSelectedRegion = + (selectedRegionForFilters?.isEmpty == true) ? true : selectedRegionForFilters?.any((region) => region == element.getRegionName(isArabic())); + var shouldApplyFacilityFilter = (selectedFacilityForFilters?.isEmpty == true) ? false : true; + var isHMC = (selectedFacilityForFilters?.isEmpty == true) ? true : selectedFacilityForFilters?.any((item) => item.contains("hmc")); + var isInSelectedClinic = (selectedClinicForFilters == null) ? true : selectedClinicForFilters == element.clinicName; + var isInSelectedHospital = (selectedHospitalForFilters == null) ? true : element.projectName == selectedHospitalForFilters?.filterName; var facilityFilter = ((shouldApplyFacilityFilter == true) ? isHMC : true); - return (isInSelectedRegion ?? true) && - (facilityFilter ?? true) && - isInSelectedClinic && - isInSelectedHospital; + return (isInSelectedRegion ?? true) && (facilityFilter ?? true) && isInSelectedClinic && isInSelectedHospital; }).toList(); return list; } @@ -1003,7 +1012,8 @@ class BookAppointmentsViewModel extends ChangeNotifier { dentalChiefComplaintsList.clear(); notifyListeners(); int patientID = _appState.isAuthenticated ? _appState.getAuthenticatedUser()!.patientId ?? -1 : -1; - final result = await bookAppointmentsRepo.getDentalChiefComplaintsList(patientID: patientID, projectID: int.parse(currentlySelectedHospitalFromRegionFlow ?? "0"), clinicID: 17); + final result = await bookAppointmentsRepo.getDentalChiefComplaintsList( + patientID: patientID, projectID: int.parse(currentlySelectedHospitalFromRegionFlow ?? "0"), clinicID: 17); result.fold( (failure) async => await errorHandlerService.handleError(failure: failure), @@ -1051,7 +1061,7 @@ class BookAppointmentsViewModel extends ChangeNotifier { ); } - setBodyType(int bodyType){ + setBodyType(int bodyType) { selectedBodyTypeIndex = bodyType; selectedCategory = 0; selectedBodyPartList = []; @@ -1059,33 +1069,33 @@ class BookAppointmentsViewModel extends ChangeNotifier { notifyListeners(); } - FutureOr getLaserClinic() async{ + FutureOr getLaserClinic() async { isBodyPartsLoading = true; int id = bodyTypes[selectedBodyTypeIndex][selectedCategory].laserCategoryID; int projectID = currentlySelectedHospitalFromRegionFlow != null ? int.parse(currentlySelectedHospitalFromRegionFlow!) : 0; int languageID = _appState.isArabic() ? 1 : 0; final result = await bookAppointmentsRepo.getLaserClinics(id, projectID, languageID); result.fold( - (failure) { + (failure) { isBodyPartsLoading = false; notifyListeners(); }, - (apiResponse) {isBodyPartsLoading = false; + (apiResponse) { + isBodyPartsLoading = false; if (apiResponse.messageStatus == 2) { // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); } else if (apiResponse.messageStatus == 1) { - List response =apiResponse.data!; - if(response.first.category == 2 || response.first.category == 10 ) response.remove(response.first); + List response = apiResponse.data!; + if (response.first.category == 2 || response.first.category == 10) response.remove(response.first); laserBodyPartsList = response; } - notifyListeners(); - + notifyListeners(); }, ); } int getDuration() { - var duration = 0; + var duration = 0; var lowerUpperLegsList = selectedBodyPartList.where((element) => element.mappingCode == "47" || element.mappingCode == "48")?.toList() ?? []; var upperLowerArmsList = selectedBodyPartList.where((element) => element.mappingCode == "40" || element.mappingCode == "41")?.toList() ?? []; @@ -1110,21 +1120,25 @@ class BookAppointmentsViewModel extends ChangeNotifier { } void setSelectedBodyPart(LaserBodyPart part) { - if(selectedBodyPartList.contains(part)){ + if (selectedBodyPartList.contains(part)) { selectedBodyPartList.remove(part); this.duration = getDuration(); notifyListeners(); } else { - if(this.duration == 90){ - dialogService.showErrorBottomSheet(message: "Duration can not exceed 90 min".needTranslation,); + if (this.duration == 90) { + dialogService.showErrorBottomSheet( + message: "Duration can not exceed 90 min".needTranslation, + ); return; } selectedBodyPartList.add(part); var duration = getDuration(); - if(duration > 90){ + if (duration > 90) { selectedBodyPartList.remove(part); - dialogService.showErrorBottomSheet(message: "Duration Exceeds 90 min".needTranslation,); + dialogService.showErrorBottomSheet( + message: "Duration Exceeds 90 min".needTranslation, + ); return; } this.duration = duration; @@ -1137,10 +1151,10 @@ class BookAppointmentsViewModel extends ChangeNotifier { } String getLaserProcedureNameWRTLanguage(LaserBodyPart part) { - if(_appState.isArabic()){ - return part.bodyPartN??""; - }else { - return part.bodyPart??""; + if (_appState.isArabic()) { + return part.bodyPartN ?? ""; + } else { + return part.bodyPart ?? ""; } } diff --git a/lib/features/contact_us/contact_us_repo.dart b/lib/features/contact_us/contact_us_repo.dart index f2b1169..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 { @@ -72,13 +76,57 @@ 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()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } + + @override + Future>> 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()); diff --git a/lib/features/contact_us/contact_us_view_model.dart b/lib/features/contact_us/contact_us_view_model.dart index 7826bd1..1185700 100644 --- a/lib/features/contact_us/contact_us_view_model.dart +++ b/lib/features/contact_us/contact_us_view_model.dart @@ -1,7 +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 { @@ -11,17 +19,43 @@ class ContactUsViewModel extends ChangeNotifier { bool isHMGLocationsListLoading = false; bool isHMGHospitalsListSelected = true; + bool isLiveChatProjectsListLoading = false; + bool isSendFeedbackTabSelected = true; List hmgHospitalsLocationsList = []; List hmgPharmacyLocationsList = []; + List liveChatProjectsList = []; + + int selectedLiveChatProjectIndex = -1; + + 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() { isHMGLocationsListLoading = true; isHMGHospitalsListSelected = true; + isLiveChatProjectsListLoading = true; hmgHospitalsLocationsList.clear(); hmgPharmacyLocationsList.clear(); + liveChatProjectsList.clear(); + feedbackAttachmentList.clear(); + selectedFeedbackType = FeedbackType(id: 5, nameEN: "Not classified", nameAR: 'غير محدد'); + setPatientFeedbackSelectedAppointment(null); getHMGLocations(); notifyListeners(); } @@ -31,6 +65,36 @@ class ContactUsViewModel extends ChangeNotifier { notifyListeners(); } + setSelectedLiveChatProjectIndex(int index) { + selectedLiveChatProjectIndex = index; + notifyListeners(); + } + + setIsSendFeedbackTabSelected(bool isSelected) { + isSendFeedbackTabSelected = isSelected; + 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(); @@ -62,4 +126,73 @@ 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); + } + } + }, + ); + } + + 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/features/emergency_services/emergency_services_repo.dart b/lib/features/emergency_services/emergency_services_repo.dart index ccfc210..c63f0ee 100644 --- a/lib/features/emergency_services/emergency_services_repo.dart +++ b/lib/features/emergency_services/emergency_services_repo.dart @@ -8,9 +8,11 @@ import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.dart'; import 'package:hmg_patient_app_new/features/authentication/models/resp_models/authenticated_user_resp_model.dart'; import 'package:hmg_patient_app_new/features/emergency_services/models/request_model/PatientER_RC.dart'; +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/AmbulanceRequestOrdersModel.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/ProjectAvgERWaitingTime.dart'; +import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/RRTServiceData.dart'; import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/rrt_procedures_response_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; @@ -18,7 +20,7 @@ import 'package:hmg_patient_app_new/services/logger_service.dart'; import 'models/resp_model/PatientERTransportationMethod.dart'; abstract class EmergencyServicesRepo { - Future>>> getRRTProcedures(); + Future>>> getRRTProcedures(int languageId); Future>>> getNearestEr({int? id, int? projectID}); @@ -48,7 +50,14 @@ abstract class EmergencyServicesRepo { Future>>> getTransportationOrders({int? id}); - Future>> cancelOrder(int? iD, int patientId); + Future>> cancelOrder(int? iD, int patientId); + + Future>> submitRRTRequest(RRTRequestModel request); + + Future>> getRRTOrders({int? id}); + + Future>> cancelRRTOrder(int? iD); + Future>> getTermsAndCondition(); } class EmergencyServicesRepoImp implements EmergencyServicesRepo { @@ -95,8 +104,8 @@ class EmergencyServicesRepoImp implements EmergencyServicesRepo { } @override - Future>>> getRRTProcedures() async { - Map mapDevice = {"ProjectID": 15}; + Future>>> getRRTProcedures(int languageId) async { + Map mapDevice = {"ProjectID": 15, "languageID":1}; try { GenericApiModel>? apiResponse; @@ -558,4 +567,155 @@ class EmergencyServicesRepoImp implements EmergencyServicesRepo { return Left(UnknownFailure(e.toString())); } } + + @override + Future>> submitRRTRequest(RRTRequestModel request) async { + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + body: request.toJson(), + ADD_RRT_ORDER_RC, + isRCService: true, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: true, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } + + @override + Future>> cancelRRTOrder(int? iD) async{ + try { + GenericApiModel? apiResponse; + + Map request = {"Id": iD, "ClickButton": 14}; + + Failure? failure; + await apiClient.post( + body: request, + UPDATE_RRT_ORDER_RC, + isRCService: true, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: true, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + + } + + @override + Future>> getRRTOrders({int? id}) async { + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + body: {}, + GET_ALL_RRT_ORDERS_RC, + isRCService: true, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final list = response['response']; + + RRTServiceData serviceData = RRTServiceData(); + list.forEach((item) { + if (item["StatusId"] == 1 || item["StatusId"] == 2) { + // Pending + serviceData.pendingOrders.add(GetCMCAllOrdersResponseModel.fromJson(item)); + } + serviceData.completedOrders.add(GetCMCAllOrdersResponseModel.fromJson(item)); + }); + + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: serviceData, + ); + } catch (e) { + failure = DataParsingFailure(e.toString()); + } + }, + ); + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + return Left(UnknownFailure(e.toString())); + } + } + + @override + Future>> getTermsAndCondition() async { + try { + GenericApiModel? apiResponse; + Failure? failure; + await apiClient.post( + body: {}, + GET_USER_TERMS, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + final agreement = response['UserAgreementContent']; + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: null, + data: agreement, + ); + + } 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/emergency_services/emergency_services_view_model.dart b/lib/features/emergency_services/emergency_services_view_model.dart index 9526f16..5dbb89d 100644 --- a/lib/features/emergency_services/emergency_services_view_model.dart +++ b/lib/features/emergency_services/emergency_services_view_model.dart @@ -2,22 +2,30 @@ import 'dart:async'; import 'package:flutter/material.dart'; import 'package:google_maps_flutter/google_maps_flutter.dart' as GMSMapServices; +import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_state.dart'; import 'package:hmg_patient_app_new/core/dependencies.dart'; import 'package:hmg_patient_app_new/core/location_util.dart'; import 'package:hmg_patient_app_new/core/utils/date_util.dart'; import 'package:hmg_patient_app_new/core/utils/doctor_response_mapper.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/authentication/authentication_view_model.dart'; import 'package:hmg_patient_app_new/features/authentication/models/resp_models/authenticated_user_resp_model.dart'; import 'package:hmg_patient_app_new/features/emergency_services/emergency_services_repo.dart'; +import 'package:hmg_patient_app_new/features/emergency_services/models/OrderDisplay.dart'; +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/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'; import 'package:hmg_patient_app_new/features/emergency_services/models/request_model/PatientER_RC.dart'; import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/AmbulanceRequestOrdersModel.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/PatientERTransportationMethod.dart' show PatientERTransportationMethod; import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/ProjectAvgERWaitingTime.dart'; @@ -25,21 +33,25 @@ import 'package:hmg_patient_app_new/features/emergency_services/models/resp_mode import 'package:hmg_patient_app_new/features/location/PlaceDetails.dart' show PlaceDetails; import 'package:hmg_patient_app_new/features/location/PlacePrediction.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/doctor_list_api_response.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_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_repo.dart'; import 'package:hmg_patient_app_new/presentation/authentication/login.dart'; +import 'package:hmg_patient_app_new/presentation/emergency_services/RRT/rrt_map_screen.dart'; +import 'package:hmg_patient_app_new/presentation/emergency_services/RRT/rrt_request_type_select.dart'; +import 'package:hmg_patient_app_new/presentation/emergency_services/RRT/terms_and_condition.dart'; import 'package:hmg_patient_app_new/presentation/emergency_services/call_ambulance/call_ambulance_page.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/emergency_services/er_online_checkin/er_online_checkin_payment_details_page.dart'; import 'package:hmg_patient_app_new/presentation/emergency_services/call_ambulance/requesting_services_page.dart'; import 'package:hmg_patient_app_new/presentation/emergency_services/call_ambulance/tracking_screen.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/emergency_services/er_online_checkin/er_online_checkin_payment_details_page.dart'; import 'package:hmg_patient_app_new/presentation/emergency_services/nearest_er_page.dart'; import 'package:hmg_patient_app_new/routes/app_routes.dart' show AppRoutes; import 'package:hmg_patient_app_new/services/dialog_service.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/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/order_tracking/order_tracking_state.dart'; @@ -71,7 +83,6 @@ class EmergencyServicesViewModel extends ChangeNotifier { List nearestERList = []; List nearestERFilteredList = []; - List RRTProceduresList = []; List? hospitalList; List? hmgHospitalList; @@ -83,7 +94,7 @@ class EmergencyServicesViewModel extends ChangeNotifier { int hmcCount = 0; bool pickupFromInsideTheLocation = true; List? appointments; - List? orders = []; + List? ambulanceOrders = []; //ambulance selection data section List transportationOptions = []; @@ -91,7 +102,6 @@ class EmergencyServicesViewModel extends ChangeNotifier { AmbulanceCallingPlace callingPlace = AmbulanceCallingPlace.FROM_HOSPITAL; AmbulanceDirection ambulanceDirection = AmbulanceDirection.ONE_WAY; - late RRTProceduresResponseModel selectedRRTProcedure; bool patientHasAdvanceERBalance = false; bool isERBookAppointment = false; @@ -99,13 +109,27 @@ class EmergencyServicesViewModel extends ChangeNotifier { BottomSheetType bottomSheetType = BottomSheetType.FIXED; + ///RRT request data + List RRTProceduresList = []; + RRTProceduresResponseModel? selectedRRTProcedure; + bool agreedToTermsAndCondition = false; + RRTServiceData? ordersRRT; + TextEditingController rrtNotes = TextEditingController(); + + + List allOrders = []; + List orderDisplayList = []; + bool historyLoading= false; + OrderDislpay currentlyDisplayedOrder = OrderDislpay.ALL; + + + setSelectedRRTProcedure(RRTProceduresResponseModel procedure) { selectedRRTProcedure = procedure; notifyListeners(); } - get isGMSAvailable - { + get isGMSAvailable { return appState.isGMSAvailable; } @@ -132,11 +156,25 @@ class EmergencyServicesViewModel extends ChangeNotifier { bool isMyAppointmentsLoading = false; + String? termsAndConditions; + Future getRRTProcedures({Function(dynamic)? onSuccess, Function(String)? onError}) async { + + print("the app state is ${appState.isAuthenticated}"); + if (!appState.isAuthenticated) { + dialogService.showErrorBottomSheet( + message: "You Need To Login First To Continue".needTranslation, + onOkPressed: () { + navServices.pop(); + getIt().onLoginPressed(); + }); + return; + } + RRTProceduresList.clear(); notifyListeners(); - final result = await emergencyServicesRepo.getRRTProcedures(); + final result = await emergencyServicesRepo.getRRTProcedures(appState.getLanguageID()); result.fold( (failure) async => await errorHandlerService.handleError(failure: failure), @@ -173,7 +211,8 @@ class EmergencyServicesViewModel extends ChangeNotifier { if (query.isEmpty) { nearestERFilteredList = nearestERList; } else { - nearestERFilteredList = nearestERList.where((er) => er.projectName != null && er.projectName!.toLowerCase().contains(query.toLowerCase())).toList(); + nearestERFilteredList = + nearestERList.where((er) => er.projectName != null && er.projectName!.toLowerCase().contains(query.toLowerCase())).toList(); } notifyListeners(); } @@ -256,8 +295,7 @@ class EmergencyServicesViewModel extends ChangeNotifier { onSuccess: (position) { updateBottomSheetState(BottomSheetType.FIXED); navServices.push( - CustomPageRoute( - page: CallAmbulancePage(), direction: AxisDirection.down), + CustomPageRoute(page: CallAmbulancePage(), direction: AxisDirection.down), ); }); } else { @@ -265,9 +303,7 @@ class EmergencyServicesViewModel extends ChangeNotifier { message: "You Need To Login First To Continue".needTranslation, onOkPressed: () { navServices.pop(); - navServices.pushAndReplace( - AppRoutes.loginScreen - ); + navServices.pushAndReplace(AppRoutes.loginScreen); }); } } @@ -299,19 +335,20 @@ class EmergencyServicesViewModel extends ChangeNotifier { void setIsGMSAvailable(bool value) { notifyListeners(); } + Future checkPatientERAdvanceBalance({Function(dynamic)? onSuccess, Function(String)? onError}) async { final result = await emergencyServicesRepo.checkPatientERAdvanceBalance(); result.fold( // (failure) async => await errorHandlerService.handleError(failure: failure), - (failure) { + (failure) { patientHasAdvanceERBalance = false; isERBookAppointment = true; if (onSuccess != null) { onSuccess(failure.message); } }, - (apiResponse) { + (apiResponse) { if (apiResponse.messageStatus == 2) { // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); patientHasAdvanceERBalance = false; @@ -332,12 +369,12 @@ class EmergencyServicesViewModel extends ChangeNotifier { final result = await emergencyServicesRepo.checkPatientERPaymentInformation(projectID: selectedHospital!.iD); result.fold( - (failure) { + (failure) { if (onError != null) { onError(failure.message); } }, - (apiResponse) { + (apiResponse) { if (apiResponse.messageStatus == 2) { } else if (apiResponse.messageStatus == 1) { erOnlineCheckInPaymentDetailsResponse = apiResponse.data!; @@ -350,7 +387,8 @@ class EmergencyServicesViewModel extends ChangeNotifier { ); } - Future ER_CreateAdvancePayment({required String paymentMethodName, required String paymentReference, Function(dynamic)? onSuccess, Function(String)? onError}) async { + Future ER_CreateAdvancePayment( + {required String paymentMethodName, required String paymentReference, Function(dynamic)? onSuccess, Function(String)? onError}) async { final result = await emergencyServicesRepo.createAdvancePaymentForER( projectID: selectedHospital!.iD, authUser: appState.getAuthenticatedUser()!, @@ -359,12 +397,12 @@ class EmergencyServicesViewModel extends ChangeNotifier { paymentReference: paymentReference); result.fold( - (failure) { + (failure) { if (onError != null) { onError(failure.message); } }, - (apiResponse) { + (apiResponse) { if (apiResponse.messageStatus == 2) { } else if (apiResponse.messageStatus == 1) { // erOnlineCheckInPaymentDetailsResponse = apiResponse.data!; @@ -378,12 +416,17 @@ class EmergencyServicesViewModel extends ChangeNotifier { } Future addAdvanceNumberRequest( - {required String advanceNumber, required String paymentReference, required String appointmentNo, Function(dynamic)? onSuccess, Function(String)? onError}) async { - final result = await emergencyServicesRepo.addAdvanceNumberRequest(advanceNumber: advanceNumber, paymentReference: paymentReference, appointmentNo: appointmentNo); + {required String advanceNumber, + required String paymentReference, + required String appointmentNo, + Function(dynamic)? onSuccess, + Function(String)? onError}) async { + final result = await emergencyServicesRepo.addAdvanceNumberRequest( + advanceNumber: advanceNumber, paymentReference: paymentReference, appointmentNo: appointmentNo); result.fold( - (failure) async => await errorHandlerService.handleError(failure: failure), - (apiResponse) { + (failure) async => await errorHandlerService.handleError(failure: failure), + (apiResponse) { if (apiResponse.messageStatus == 2) { // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); } else if (apiResponse.messageStatus == 1) { @@ -401,12 +444,12 @@ class EmergencyServicesViewModel extends ChangeNotifier { result.fold( // (failure) async => await errorHandlerService.handleError(failure: failure), - (failure) { + (failure) { if (onError != null) { onError(failure.message); } }, - (apiResponse) { + (apiResponse) { if (apiResponse.messageStatus == 2) { if (onError != null) { onError(apiResponse.errorMessage!); @@ -426,12 +469,12 @@ class EmergencyServicesViewModel extends ChangeNotifier { result.fold( // (failure) async => await errorHandlerService.handleError(failure: failure), - (failure) { + (failure) { if (onError != null) { onError(failure.message); } }, - (apiResponse) { + (apiResponse) { if (apiResponse.messageStatus == 2) { if (onError != null) { onError(apiResponse.data["InvoiceResponse"]["Message"]); @@ -457,16 +500,13 @@ class EmergencyServicesViewModel extends ChangeNotifier { onOkPressed: () { navServices.pop(); print("inside the ok button"); - getIt().onLoginPressed(); + getIt().onLoginPressed(); }); return; } - if (transportationOptions.isNotEmpty) return; - int? id = appState.getAuthenticatedUser()?.patientId; - LoaderBottomSheet.showLoader( - loadingText: "Getting Ambulance Transport Option".needTranslation); + LoaderBottomSheet.showLoader(loadingText: "Getting Ambulance Transport Option".needTranslation); notifyListeners(); var response = await emergencyServicesRepo.getTransportationMethods(id: id); @@ -485,8 +525,7 @@ class EmergencyServicesViewModel extends ChangeNotifier { Future getTransportationMethods() async { int? id = appState.getAuthenticatedUser()?.patientId; - LoaderBottomSheet.showLoader( - loadingText: "Getting Ambulance Transport Option".needTranslation); + LoaderBottomSheet.showLoader(loadingText: "Getting Ambulance Transport Option".needTranslation); notifyListeners(); var response = await emergencyServicesRepo.getTransportationMethods(id: id); @@ -597,11 +636,7 @@ class EmergencyServicesViewModel extends ChangeNotifier { sourceList = hmcHospitalList; break; } - displayList = sourceList - ?.where((hospital) => - hospital.name != null && - hospital.name!.toLowerCase().contains(query.toLowerCase())) - .toList(); + displayList = sourceList?.where((hospital) => hospital.name != null && hospital.name!.toLowerCase().contains(query.toLowerCase())).toList(); notifyListeners(); } @@ -620,7 +655,6 @@ class EmergencyServicesViewModel extends ChangeNotifier { notifyListeners(); } - void setSelectedHospital(HospitalsModel? hospital) { selectedHospital = hospital; notifyListeners(); @@ -668,13 +702,12 @@ class EmergencyServicesViewModel extends ChangeNotifier { } Future updateAppointment(bool value) async { - if (value) { await getAppointments(); } else { clearAppointmentData(); } - if(appointments?.isNotEmpty == true) { + if (appointments?.isNotEmpty == true) { haveAnAppointment = value; } notifyListeners(); @@ -729,18 +762,24 @@ class EmergencyServicesViewModel extends ChangeNotifier { } Future getTransportationOrders({bool shouldNavigateToTrackingScreen = false, bool showLoader = false}) async { - if(shouldNavigateToTrackingScreen == false && showLoader ) { + if (shouldNavigateToTrackingScreen == false && showLoader) { LoaderBottomSheet.showLoader(loadingText: "Fetching Orders"); } int? id = appState.getAuthenticatedUser()?.patientId; + historyLoading = true; + notifyListeners(); var response = await emergencyServicesRepo.getTransportationOrders(id: id); - if(shouldNavigateToTrackingScreen == false && showLoader ) { - LoaderBottomSheet.hideLoader();} + if (shouldNavigateToTrackingScreen == false && showLoader) { + LoaderBottomSheet.hideLoader(); + } response.fold( (failure) async { + historyLoading = false; + notifyListeners(); if (shouldNavigateToTrackingScreen) { - navServices.pushAndRemoveUntil(CustomPageRoute(page: TrackingScreen(state: OrderTrackingState.waitingForCall)), ModalRoute.withName("/EmergencyServicesPage")); + navServices.pushAndRemoveUntil( + CustomPageRoute(page: TrackingScreen(state: OrderTrackingState.waitingForCall)), ModalRoute.withName("/EmergencyServicesPage")); } }, (apiResponse) { @@ -753,8 +792,12 @@ class EmergencyServicesViewModel extends ChangeNotifier { )), ModalRoute.withName("/EmergencyServicesPage")); } - - orders = apiResponse.data; + historyLoading = false; + ambulanceOrders = apiResponse.data; + allOrders.clear(); + allOrders.addAll(ambulanceOrders??[]); + allOrders.addAll(ordersRRT?.completedOrders??[]); + changeOrderDisplayItems(OrderDislpay.ALL); notifyListeners(); }, ); @@ -828,10 +871,10 @@ class EmergencyServicesViewModel extends ChangeNotifier { Future cancelOrder(AmbulanceRequestOrdersModel? order, {bool shouldPop = false}) async { dialogService.showCommonBottomSheetWithoutH( - message: "Do you want to cancel the order".needTranslation, + message: "Do you want to cancel the request".needTranslation, onOkPressed: () async { navServices.pop(); - LoaderBottomSheet.showLoader(loadingText: "Cancelling Order".needTranslation); + LoaderBottomSheet.showLoader(loadingText: "Cancelling request".needTranslation); var response = await emergencyServicesRepo.cancelOrder(order?.iD, appState.getAuthenticatedUser()?.patientId ?? 0); LoaderBottomSheet.hideLoader(); response.fold((failure) => errorHandlerService.handleError(failure: failure), (success) { @@ -843,4 +886,201 @@ class EmergencyServicesViewModel extends ChangeNotifier { navServices.pop(); }); } + + + RRTRequestModel createRRTRequest(GeocodeResult? result, PlaceDetails? place, PlacePrediction? placePrediction){ + AuthenticatedUser? user = appState.getAuthenticatedUser(); + if (user == null) throw Exception("Authentication Required to Continue"); + + RRTRequestModel rrtRequestModel = new RRTRequestModel(); + Procedures procedures = new Procedures(); + rrtRequestModel.procedures = []; + + + procedures.serviceID = selectedRRTProcedure?.procedureID; + + rrtRequestModel.latitude = ((result?.geometry.location.lat) ?? place?.lat); + rrtRequestModel.longitude = ((result?.geometry.location.lat) ?? place?.lat); + rrtRequestModel.additionalDetails = ""; + rrtRequestModel.nationality = user.nationalityId; + rrtRequestModel.paymentAmount = selectedRRTProcedure?.patientShareWithTax; + rrtRequestModel.nearestProjectId = 0; + rrtRequestModel.patientId = user.patientId; + rrtRequestModel.patientOutSa = user.outSa; + rrtRequestModel.procedures!.add(procedures); + + return rrtRequestModel; + } + + ///method to toggle the value for the aggremnent to the terms and conditon for the rrt + void setTermsAndConditions(bool value) { + agreedToTermsAndCondition = value; + notifyListeners(); + } + + FutureOr submitRRTRequest(GeocodeResult? result, PlaceDetails? place, PlacePrediction? placePrediction) async { + RRTRequestModel request = createRRTRequest(result, place, placePrediction); + navServices.push(CustomPageRoute(page: RequestingServicesPage())); + + var response = await emergencyServicesRepo.submitRRTRequest(request); + response.fold((failure) { + navServices.pushAndRemoveUntil( + CustomPageRoute( + page: TrackingScreen( + isRRTOrder: true, + state: OrderTrackingState.failed, + )), + ModalRoute.withName("/EmergencyServicesPage")); + }, (success) { + getRRTOrders(shouldNavigateToTrackingScreen: true); + }); + } + + Future getRRTOrders({bool shouldNavigateToTrackingScreen = false, bool showLoader = false}) async { + if(shouldNavigateToTrackingScreen == false && showLoader ) { + LoaderBottomSheet.showLoader(loadingText: "Fetching Orders"); + } + historyLoading = true; + notifyListeners(); + int? id = appState.getAuthenticatedUser()?.patientId; + + var response = await emergencyServicesRepo.getRRTOrders(id: id); + if(shouldNavigateToTrackingScreen == false && showLoader ) { + LoaderBottomSheet.hideLoader();} + response.fold( + (failure) async { + historyLoading = false; + notifyListeners(); + if (shouldNavigateToTrackingScreen) { + navServices.pushAndRemoveUntil(CustomPageRoute(page: TrackingScreen(isRRTOrder: true,state: OrderTrackingState.waitingForCall)), ModalRoute.withName("/EmergencyServicesPage")); + } + }, + (apiResponse) { + if (shouldNavigateToTrackingScreen) { + navServices.pushAndRemoveUntil( + CustomPageRoute( + page: TrackingScreen( + state: OrderTrackingState.waitingForCall, + isRRTOrder: true, + rrtOrder: apiResponse.data?.pendingOrders.first, + )), + ModalRoute.withName("/EmergencyServicesPage")); + } + historyLoading = false; + ordersRRT = apiResponse.data; + allOrders.clear(); + allOrders.addAll(ambulanceOrders??[]); + allOrders.addAll(ordersRRT?.completedOrders??[]); + changeOrderDisplayItems(OrderDislpay.ALL); + notifyListeners(); + }, + ); + } + + + FutureOr cancelRRTOrder(int? orderID, {bool shouldPop = false}) async { + dialogService.showCommonBottomSheetWithoutH( + message: "Do you want to cancel the request".needTranslation, + onOkPressed: () async { + navServices.pop(); + LoaderBottomSheet.showLoader(loadingText: "Cancelling request".needTranslation); + var response = await emergencyServicesRepo.cancelRRTOrder(orderID); + LoaderBottomSheet.hideLoader(); + response.fold((failure) => errorHandlerService.handleError(failure: failure), (success) { + getRRTOrders(); + if (shouldPop) navServices.pop(); + }); + }, + onCancelPressed: () { + navServices.pop(); + }); + } + + void changeOrderDisplayItems(OrderDislpay currentlyDisplayedOrder){ + this.currentlyDisplayedOrder = currentlyDisplayedOrder; + switch(currentlyDisplayedOrder){ + case OrderDislpay.ALL: + orderDisplayList = allOrders; + break; + case OrderDislpay.RRT: + orderDisplayList = ordersRRT?.completedOrders ?? []; + break; + case OrderDislpay.AMBULANCE: + orderDisplayList = ambulanceOrders??[]; + break; + } + notifyListeners(); + } + + void openRRT(){ + print("the app state is ${appState.isAuthenticated}"); + if (appState.isAuthenticated) { + if(agreedToTermsAndCondition == false){ + dialogService.showErrorBottomSheet(message: "You Need To Agree To Terms And Conditions".needTranslation, onOkPressed: (){ + if(navServices.context == null ) return; + showCommonBottomSheetWithoutHeight( + navServices.context!, + padding: EdgeInsets.only(top: 24.h), + titleWidget: Transform.flip( + flipX: isArabic, + child: Utils.buildSvgWithAssets( + icon: AppAssets.arrow_back, + iconColor: Color(0xff2B353E), + fit: BoxFit.contain, + ), + ).onPress(() { + navServices.pop(); + }), + // title: "Rapid Response Team (RRT)".needTranslation, + child: RrtRequestTypeSelect(), + isFullScreen: false, + isCloseButtonVisible: true, + hasBottomPadding: false, + backgroundColor: AppColors.bottomSheetBgColor, + callBackFunc: () { + navServices.pop(); + }, + ); + }); + return; + } + placeValueInController(); + locationUtils!.getLocation( + isShowConfirmDialog: true, + onSuccess: (position) { + updateBottomSheetState(BottomSheetType.FIXED); + navServices.push( + CustomPageRoute( + page: RrtMapScreen(), direction: AxisDirection.down), + ); + }); + } else{ + dialogService.showErrorBottomSheet( + message: "You Need To Login First To Continue".needTranslation, + onOkPressed: () { + navServices.pop(); + getIt().onLoginPressed(); + }); + } + } + clearRRTData(){ + selectedRRTProcedure = null; + } + + + FutureOr getTermsAndConditions() async { + LoaderBottomSheet.showLoader(loadingText: "Fetching Terms And Conditions".needTranslation); + var response = await emergencyServicesRepo.getTermsAndCondition(); + LoaderBottomSheet.hideLoader(); + response.fold((failure)=>errorHandlerService.handleError(failure: failure),(success){ + termsAndConditions = success.data; + print("the response terms are $termsAndConditions"); + notifyListeners(); + navServices.push( + CustomPageRoute( + page: TermsAndCondition(termsAndCondition:success.data??""), direction: AxisDirection.down), + ); + }); + } + } diff --git a/lib/features/emergency_services/models/OrderDisplay.dart b/lib/features/emergency_services/models/OrderDisplay.dart new file mode 100644 index 0000000..9f1e929 --- /dev/null +++ b/lib/features/emergency_services/models/OrderDisplay.dart @@ -0,0 +1,3 @@ +enum OrderDislpay{ + ALL,RRT,AMBULANCE +} \ No newline at end of file diff --git a/lib/features/emergency_services/models/request_model/RRTRequestModel.dart b/lib/features/emergency_services/models/request_model/RRTRequestModel.dart new file mode 100644 index 0000000..dfb5b1b --- /dev/null +++ b/lib/features/emergency_services/models/request_model/RRTRequestModel.dart @@ -0,0 +1,75 @@ +class RRTRequestModel { + num? patientId; + int? patientOutSa; + bool? isOutPatient; + int? nearestProjectId; + num? longitude; + num? latitude; + String? additionalDetails; + String? nationality; + num? paymentAmount; + List? procedures; + + RRTRequestModel( + {this.patientId, + this.patientOutSa, + this.isOutPatient, + this.nearestProjectId, + this.longitude, + this.latitude, + this.additionalDetails, + this.nationality, + this.paymentAmount, + this.procedures}); + + RRTRequestModel.fromJson(Map json) { + patientId = json['patientId']; + patientOutSa = json['patientOutSa']; + isOutPatient = json['isOutPatient']; + nearestProjectId = json['nearestProjectId']; + longitude = json['longitude']; + latitude = json['latitude']; + additionalDetails = json['additionalDetails']; + nationality = json['nationality']; + paymentAmount = json['paymentAmount']; + if (json['procedures'] != null) { + procedures = []; + json['procedures'].forEach((v) { + procedures!.add(new Procedures.fromJson(v)); + }); + } + } + + Map toJson() { + final Map data = new Map(); + data['patientId'] = this.patientId; + data['patientOutSa'] = this.patientOutSa; + data['isOutPatient'] = this.isOutPatient; + data['nearestProjectId'] = this.nearestProjectId; + data['longitude'] = this.longitude; + data['latitude'] = this.latitude; + data['additionalDetails'] = this.additionalDetails; + data['nationality'] = this.nationality; + data['paymentAmount'] = this.paymentAmount; + if (this.procedures != null) { + data['procedures'] = this.procedures!.map((v) => v.toJson()).toList(); + } + return data; + } +} + +class Procedures { + String? serviceID; + + Procedures({this.serviceID}); + + Procedures.fromJson(Map json) { + serviceID = json['ServiceID']; + } + + Map toJson() { + final Map data = new Map(); + data['ServiceID'] = this.serviceID; + return data; + } +} diff --git a/lib/features/emergency_services/models/request_model/service_price.dart b/lib/features/emergency_services/models/request_model/service_price.dart new file mode 100644 index 0000000..abb57be --- /dev/null +++ b/lib/features/emergency_services/models/request_model/service_price.dart @@ -0,0 +1,53 @@ +class ServicePrice { + String? currency; + dynamic maxPrice; + dynamic maxTotalPrice; + dynamic maxVAT; + dynamic minPrice; + dynamic minTotalPrice; + dynamic minVAT; + dynamic price; + dynamic totalPrice; + dynamic vat; + + ServicePrice({ + this.currency, + this.maxPrice, + this.maxTotalPrice, + this.maxVAT, + this.minPrice, + this.minTotalPrice, + this.minVAT, + this.price, + this.totalPrice, + this.vat}); + + ServicePrice.fromJson(dynamic json) { + currency = json["Currency"]; + maxPrice = json["MaxPrice"]; + maxTotalPrice = json["MaxTotalPrice"]; + maxVAT = json["MaxVAT"]; + minPrice = json["MinPrice"]; + minTotalPrice = json["MinTotalPrice"]; + minVAT = json["MinVAT"]; + price = json["Price"]; + totalPrice = json["TotalPrice"]; + vat = json["VAT"]; + } + + Map toJson() { + var map = {}; + map["Currency"] = currency; + map["MaxPrice"] = maxPrice; + map["MaxTotalPrice"] = maxTotalPrice; + map["MaxVAT"] = maxVAT; + map["MinPrice"] = minPrice; + map["MinTotalPrice"] = minTotalPrice; + map["MinVAT"] = minVAT; + map["Price"] = price; + map["TotalPrice"] = totalPrice; + map["VAT"] = vat; + return map; + } + +} \ No newline at end of file diff --git a/lib/features/emergency_services/models/resp_model/RRTServiceData.dart b/lib/features/emergency_services/models/resp_model/RRTServiceData.dart new file mode 100644 index 0000000..e4e7ce7 --- /dev/null +++ b/lib/features/emergency_services/models/resp_model/RRTServiceData.dart @@ -0,0 +1,406 @@ +class RRTServiceData { + List pendingOrders = []; + List completedOrders = []; + ServicePrice servicePrice = ServicePrice(); +} + +class GetCMCAllOrdersResponseModel { + int? iD; + int? patientId; + int? patientOutSa; + bool? isOutPatient; + int? projectId; + int? nearestProjectId; + dynamic longitude; + dynamic latitude; + dynamic appointmentNo; + dynamic dischargeId; + int? statusId; + int? serviceId; + int? channel; + Orderpayment? orderpayment; + dynamic orderselectedservice; + dynamic wforder; + dynamic orderapprovalobj; + String? created; + dynamic createdBy; + dynamic modified; + dynamic modifiedBy; + bool? isDeleted; + String? statusText; + int? paymentStatus; + dynamic clientRequestid; + dynamic paymentStatusText; + String? projectName; + String? nearestProjectName; + dynamic paymentAmount; + WFOrder? wFOrder; + String? serviceText; + bool? isSentForApproval; + int? exaCartOrderId; + bool? isTimer; + int? timeSeconds; + int? totalPendingSeconds; + int? timeMinute; + int? timeHour; + int? timeTotalSeconds; + int? timeTotalMinute; + int? timeTotalHour; + dynamic approvalStatus; + bool? isActive; + int? clickButton; + List? procedures; + dynamic pickupLocation; + dynamic dropOffLocation; + dynamic clinicName; + dynamic doctorName; + dynamic branch; + dynamic time; + dynamic notes; + + GetCMCAllOrdersResponseModel( + {this.iD, + this.patientId, + this.patientOutSa, + this.isOutPatient, + this.projectId, + this.nearestProjectId, + this.longitude, + this.latitude, + this.appointmentNo, + this.dischargeId, + this.statusId, + this.serviceId, + this.channel, + this.orderpayment, + this.orderselectedservice, + this.wforder, + this.orderapprovalobj, + this.created, + this.createdBy, + this.modified, + this.modifiedBy, + this.isDeleted, + this.statusText, + this.paymentStatus, + this.clientRequestid, + this.paymentStatusText, + this.projectName, + this.nearestProjectName, + this.paymentAmount, + this.wFOrder, + this.serviceText, + this.isSentForApproval, + this.exaCartOrderId, + this.isTimer, + this.timeSeconds, + this.totalPendingSeconds, + this.timeMinute, + this.timeHour, + this.timeTotalSeconds, + this.timeTotalMinute, + this.timeTotalHour, + this.approvalStatus, + this.isActive, + this.clickButton, + this.procedures, + this.pickupLocation, + this.dropOffLocation, + this.clinicName, + this.doctorName, + this.branch, + this.time, + this.notes}); + + GetCMCAllOrdersResponseModel.fromJson(Map json) { + iD = json['ID']; + patientId = json['PatientId']; + patientOutSa = json['PatientOutSa']; + isOutPatient = json['IsOutPatient']; + projectId = json['ProjectId']; + nearestProjectId = json['NearestProjectId']; + longitude = json['Longitude']; + latitude = json['Latitude']; + appointmentNo = json['AppointmentNo']; + dischargeId = json['DischargeId']; + statusId = json['StatusId']; + serviceId = json['ServiceId']; + channel = json['Channel']; + orderpayment = json['orderpayment'] != null + ? new Orderpayment.fromJson(json['orderpayment']) + : null; + orderselectedservice = json['orderselectedservice']; + wforder = json['wforder']; + orderapprovalobj = json['orderapprovalobj']; + created = json['Created']; + createdBy = json['CreatedBy']; + modified = json['Modified']; + modifiedBy = json['ModifiedBy']; + isDeleted = json['IsDeleted']; + statusText = json['StatusText']; + paymentStatus = json['PaymentStatus']; + clientRequestid = json['ClientRequestid']; + paymentStatusText = json['PaymentStatusText']; + projectName = json['ProjectName']; + nearestProjectName = json['NearestProjectName']; + paymentAmount = json['PaymentAmount']; + wFOrder = json['WF_order'] != null + ? new WFOrder.fromJson(json['WF_order']) + : null; + serviceText = json['ServiceText']; + isSentForApproval = json['isSentForApproval']; + exaCartOrderId = json['ExaCart_OrderId']; + isTimer = json['isTimer']; + timeSeconds = json['TimeSeconds']; + totalPendingSeconds = json['TotalPendingSeconds']; + timeMinute = json['TimeMinute']; + timeHour = json['TimeHour']; + timeTotalSeconds = json['TimeTotalSeconds']; + timeTotalMinute = json['TimeTotalMinute']; + timeTotalHour = json['TimeTotalHour']; + approvalStatus = json['ApprovalStatus']; + isActive = json['isActive']; + clickButton = json['ClickButton']; + pickupLocation = json['PickupLocation']; + dropOffLocation = json['DropOffLocation']; + clinicName = json['clinicName']; + doctorName = json['DoctorName']; + branch = json['Branch']; + time = json['Time']; + notes = json['Notes']; + } + + Map toJson() { + final Map data = new Map(); + data['ID'] = this.iD; + data['PatientId'] = this.patientId; + data['PatientOutSa'] = this.patientOutSa; + data['IsOutPatient'] = this.isOutPatient; + data['ProjectId'] = this.projectId; + data['NearestProjectId'] = this.nearestProjectId; + data['Longitude'] = this.longitude; + data['Latitude'] = this.latitude; + data['AppointmentNo'] = this.appointmentNo; + data['DischargeId'] = this.dischargeId; + data['StatusId'] = this.statusId; + data['ServiceId'] = this.serviceId; + data['Channel'] = this.channel; + if (this.orderpayment != null) { + data['orderpayment'] = this.orderpayment!.toJson(); + } + data['orderselectedservice'] = this.orderselectedservice; + + data['wforder'] = this.wforder; + data['orderapprovalobj'] = this.orderapprovalobj; + data['Created'] = this.created; + data['CreatedBy'] = this.createdBy; + data['Modified'] = this.modified; + data['ModifiedBy'] = this.modifiedBy; + data['IsDeleted'] = this.isDeleted; + data['StatusText'] = this.statusText; + data['PaymentStatus'] = this.paymentStatus; + data['ClientRequestid'] = this.clientRequestid; + data['PaymentStatusText'] = this.paymentStatusText; + data['ProjectName'] = this.projectName; + data['NearestProjectName'] = this.nearestProjectName; + data['PaymentAmount'] = this.paymentAmount; + if (this.wFOrder != null) { + data['WF_order'] = this.wFOrder!.toJson(); + } + data['ServiceText'] = this.serviceText; + data['isSentForApproval'] = this.isSentForApproval; + data['ExaCart_OrderId'] = this.exaCartOrderId; + data['isTimer'] = this.isTimer; + data['TimeSeconds'] = this.timeSeconds; + data['TotalPendingSeconds'] = this.totalPendingSeconds; + data['TimeMinute'] = this.timeMinute; + data['TimeHour'] = this.timeHour; + data['TimeTotalSeconds'] = this.timeTotalSeconds; + data['TimeTotalMinute'] = this.timeTotalMinute; + data['TimeTotalHour'] = this.timeTotalHour; + data['ApprovalStatus'] = this.approvalStatus; + data['isActive'] = this.isActive; + data['ClickButton'] = this.clickButton; + data['PickupLocation'] = this.pickupLocation; + data['DropOffLocation'] = this.dropOffLocation; + data['clinicName'] = this.clinicName; + data['DoctorName'] = this.doctorName; + data['Branch'] = this.branch; + data['Time'] = this.time; + data['Notes'] = this.notes; + return data; + } +} + +class Orderpayment { + int? iD; + int? orderId; + dynamic clientRequestId; + dynamic totalAmount; + int? paymentStatus; + dynamic order; + String? created; + dynamic createdBy; + dynamic modified; + dynamic modifiedBy; + bool? isDeleted; + + Orderpayment( + {this.iD, + this.orderId, + this.clientRequestId, + this.totalAmount, + this.paymentStatus, + this.order, + this.created, + this.createdBy, + this.modified, + this.modifiedBy, + this.isDeleted}); + + Orderpayment.fromJson(Map json) { + iD = json['ID']; + orderId = json['OrderId']; + clientRequestId = json['ClientRequestId']; + totalAmount = json['TotalAmount']; + paymentStatus = json['PaymentStatus']; + order = json['Order']; + created = json['Created']; + createdBy = json['CreatedBy']; + modified = json['Modified']; + modifiedBy = json['ModifiedBy']; + isDeleted = json['IsDeleted']; + } + + Map toJson() { + final Map data = new Map(); + data['ID'] = this.iD; + data['OrderId'] = this.orderId; + data['ClientRequestId'] = this.clientRequestId; + data['TotalAmount'] = this.totalAmount; + data['PaymentStatus'] = this.paymentStatus; + data['Order'] = this.order; + data['Created'] = this.created; + data['CreatedBy'] = this.createdBy; + data['Modified'] = this.modified; + data['ModifiedBy'] = this.modifiedBy; + data['IsDeleted'] = this.isDeleted; + return data; + } +} + +class WFOrder { + dynamic wfButtonsDTO; + int? iD; + int? orderId; + int? previousStep; + int? nextStep; + int? serviceId; + dynamic order; + String? created; + dynamic createdBy; + dynamic modified; + dynamic modifiedBy; + bool? isDeleted; + + WFOrder( + {this.wfButtonsDTO, + this.iD, + this.orderId, + this.previousStep, + this.nextStep, + this.serviceId, + this.order, + this.created, + this.createdBy, + this.modified, + this.modifiedBy, + this.isDeleted}); + + WFOrder.fromJson(Map json) { + wfButtonsDTO = json['wf_ButtonsDTO']; + iD = json['ID']; + orderId = json['OrderId']; + previousStep = json['PreviousStep']; + nextStep = json['NextStep']; + serviceId = json['ServiceId']; + order = json['Order']; + created = json['Created']; + createdBy = json['CreatedBy']; + modified = json['Modified']; + modifiedBy = json['ModifiedBy']; + isDeleted = json['IsDeleted']; + } + + Map toJson() { + final Map data = new Map(); + data['wf_ButtonsDTO'] = this.wfButtonsDTO; + data['ID'] = this.iD; + data['OrderId'] = this.orderId; + data['PreviousStep'] = this.previousStep; + data['NextStep'] = this.nextStep; + data['ServiceId'] = this.serviceId; + data['Order'] = this.order; + data['Created'] = this.created; + data['CreatedBy'] = this.createdBy; + data['Modified'] = this.modified; + data['ModifiedBy'] = this.modifiedBy; + data['IsDeleted'] = this.isDeleted; + return data; + } +} + + +class ServicePrice { + String? currency; + dynamic maxPrice; + dynamic maxTotalPrice; + dynamic maxVAT; + dynamic minPrice; + dynamic minTotalPrice; + dynamic minVAT; + dynamic price; + dynamic totalPrice; + dynamic vat; + + ServicePrice({ + this.currency, + this.maxPrice, + this.maxTotalPrice, + this.maxVAT, + this.minPrice, + this.minTotalPrice, + this.minVAT, + this.price, + this.totalPrice, + this.vat}); + + ServicePrice.fromJson(dynamic json) { + currency = json["Currency"]; + maxPrice = json["MaxPrice"]; + maxTotalPrice = json["MaxTotalPrice"]; + maxVAT = json["MaxVAT"]; + minPrice = json["MinPrice"]; + minTotalPrice = json["MinTotalPrice"]; + minVAT = json["MinVAT"]; + price = json["Price"]; + totalPrice = json["TotalPrice"]; + vat = json["VAT"]; + } + + Map toJson() { + var map = {}; + map["Currency"] = currency; + map["MaxPrice"] = maxPrice; + map["MaxTotalPrice"] = maxTotalPrice; + map["MaxVAT"] = maxVAT; + map["MinPrice"] = minPrice; + map["MinTotalPrice"] = minTotalPrice; + map["MinVAT"] = minVAT; + map["Price"] = price; + map["TotalPrice"] = totalPrice; + map["VAT"] = vat; + return map; + } + +} \ No newline at end of file diff --git a/lib/features/hmg_services/hmg_services_repo.dart b/lib/features/hmg_services/hmg_services_repo.dart index 1115f18..6e1d533 100644 --- a/lib/features/hmg_services/hmg_services_repo.dart +++ b/lib/features/hmg_services/hmg_services_repo.dart @@ -6,10 +6,12 @@ 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/hmg_services/models/req_models/check_activation_e_referral_req_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/req_models/cmc_create_new_order_req_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/req_models/order_update_req_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_all_cities_resp_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_all_orders_resp_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_services_resp_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart'; import 'package:hmg_patient_app_new/services/logger_service.dart'; import 'package:provider/provider.dart'; @@ -18,12 +20,32 @@ import 'models/req_models/send_activation_code_ereferral_req_model.dart'; import 'models/resq_models/relationship_type_resp_mode.dart'; abstract class HmgServicesRepo { - Future>>> getAllCmcOrders(); + Future>>> getAllComprehensiveCheckupOrders(); + + Future>>> getAllHomeHealthCareCheckupOrders(); Future>> updateCmcPresOrder(OrderUpdateRequestModel requestModel); + Future>> updateHhcPresOrder(OrderUpdateRequestModel requestModel); + Future>>> getAllCmcServices({required int patientID}); + Future>>> getAllHhcServices({required int patientID}); + + Future>>> getHospitalsList(); + + Future>> addCmcOrder({ + required int projectID, + required int orderServiceID, + required List services, + }); + + Future>> addHhcOrder({ + required int projectID, + required int orderServiceID, + required List services, + }); + Future>>> getRelationshipTypes(); Future>>> getAllCities(); @@ -43,7 +65,7 @@ class HmgServicesRepoImp implements HmgServicesRepo { HmgServicesRepoImp({required this.apiClient, required this.loggerService}); @override - Future>>> getAllCmcOrders() async { + Future>>> getAllComprehensiveCheckupOrders() async { Map requestBody = {}; try { @@ -101,6 +123,65 @@ class HmgServicesRepoImp implements HmgServicesRepo { } } + @override + Future>>> getAllHomeHealthCareCheckupOrders() async { + Map requestBody = {}; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + + await apiClient.post( + ApiConsts.allHHCOrdersRc, + isRCService: true, + body: requestBody, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + loggerService.logError("HHC Orders API Failed: $error, Status: $statusCode"); + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + List cmcOrdersList = []; + // Log the full response for debugging + // Extract MessageStatus and ErrorEndUserMessage from root level + final apiErrorMessage = response['ErrorEndUserMessage'] as String?; + // Parse the response array + if (response['response'] != null && response['response'] is List) { + final ordersList = response['response'] as List; + + for (var orderJson in ordersList) { + if (orderJson is Map) { + try { + cmcOrdersList.add(GetCMCAllOrdersResponseModel.fromJson(orderJson)); + } catch (e) { + loggerService.logError("Error parsing individual order: ${e.toString()}"); + } + } + } + } + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: apiErrorMessage ?? errorMessage, + data: cmcOrdersList, + ); + } catch (e) { + loggerService.logError("Error parsing HHC orders: ${e.toString()}"); + failure = DataParsingFailure(e.toString()); + } + }, + ); + + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + loggerService.logError("Unknown error in getAllHHCOrders: ${e.toString()}"); + return Left(UnknownFailure(e.toString())); + } + } + @override Future>>> getAllCmcServices({required int patientID}) async { Map requestBody = {}; @@ -154,6 +235,59 @@ class HmgServicesRepoImp implements HmgServicesRepo { } } + @override + Future>>> getAllHhcServices({required int patientID}) async { + Map requestBody = {}; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + + await apiClient.post( + '${ApiConsts.allHHCServicesRc}?patientID=$patientID', + isRCService: true, + isAllowAny: true, + body: requestBody, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + loggerService.logError("HHC Services API Failed: $error, Status: $statusCode"); + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + List hhcServicesList = []; + + if (response['response'] != null && response['response'] is List) { + final servicesList = response['response'] as List; + + for (var serviceJson in servicesList) { + if (serviceJson is Map) { + hhcServicesList.add(GetCMCServicesResponseModel.fromJson(serviceJson)); + } + } + } + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: hhcServicesList, + ); + } catch (e) { + loggerService.logError("Error parsing HHC services: ${e.toString()}"); + failure = DataParsingFailure(e.toString()); + } + }, + ); + + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + log("Unknown error in getAllHhcServices: ${e.toString()}"); + return Left(UnknownFailure(e.toString())); + } + } + @override Future>> updateCmcPresOrder(OrderUpdateRequestModel requestModel) async { try { @@ -194,6 +328,216 @@ class HmgServicesRepoImp implements HmgServicesRepo { } } + @override + Future>> updateHhcPresOrder(OrderUpdateRequestModel requestModel) async { + try { + GenericApiModel? apiResponse; + Failure? failure; + + await apiClient.post( + ApiConsts.updateHHCOrder, + isRCService: true, + body: requestModel.toJson(), + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + loggerService.logError("Update HHC Order API Failed: $error, Status: $statusCode"); + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: true, + ); + + loggerService.logInfo("HHC Order updated successfully: PresOrderID=${requestModel.presOrderID}"); + } catch (e) { + loggerService.logError("Error processing update HHC order response: ${e.toString()}"); + failure = DataParsingFailure(e.toString()); + } + }, + ); + + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + loggerService.logError("Unknown error in updateHhcPresOrder: ${e.toString()}"); + return Left(UnknownFailure(e.toString())); + } + } + + @override + Future>>> getHospitalsList() async { + Map requestBody = {}; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + + await apiClient.post( + ApiConsts.getHospitalsList, + isRCService: false, // This uses the base HIS API URL, not RC + body: requestBody, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + loggerService.logError("Get Hospitals List API Failed: $error, Status: $statusCode"); + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + List hospitalsList = []; + + loggerService.logInfo("Hospitals List Raw Response: $response"); + + if (response['ListProject'] != null && response['ListProject'] is List) { + final projectsList = response['ListProject'] as List; + + for (var projectJson in projectsList) { + try { + if (projectJson is Map) { + hospitalsList.add(HospitalsModel.fromJson(projectJson)); + } + } catch (e) { + loggerService.logError('Error parsing hospital item: ${e.toString()}'); + } + } + } else { + loggerService.logInfo('Hospitals list response array is empty or missing'); + } + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: hospitalsList, + ); + + loggerService.logInfo("Hospitals fetched successfully: ${hospitalsList.length} hospitals"); + } catch (e) { + loggerService.logError("Error parsing hospitals list: ${e.toString()}"); + failure = DataParsingFailure(e.toString()); + } + }, + ); + + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + loggerService.logError("Unknown error in getHospitalsList: ${e.toString()}"); + return Left(UnknownFailure(e.toString())); + } + } + + @override + Future>> addCmcOrder({ + required int projectID, + required int orderServiceID, + required List services, + }) async { + try { + GenericApiModel? apiResponse; + Failure? failure; + + final requestBody = { + 'ProjectID': projectID, + 'OrderServiceID': orderServiceID, + 'procedures': services.map((service) => service.toJson()).toList(), + }; + + await apiClient.post( + ApiConsts.addCMCOrder, + isRCService: true, + body: requestBody, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + loggerService.logError("Add CMC Order API Failed: $error, Status: $statusCode"); + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + int requestId = 0; + if (response is Map) { + requestId = response['response']; + } + try { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: requestId, + ); + + loggerService.logInfo("CMC Order added successfully: ProjectID=$projectID, OrderServiceID=$orderServiceID"); + } catch (e) { + loggerService.logError("Error processing add CMC order response: ${e.toString()}"); + failure = DataParsingFailure(e.toString()); + } + }, + ); + + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + loggerService.logError("Unknown error in addCmcOrder: ${e.toString()}"); + return Left(UnknownFailure(e.toString())); + } + } + + @override + Future>> addHhcOrder({ + required int projectID, + required int orderServiceID, + required List services, + }) async { + try { + GenericApiModel? apiResponse; + Failure? failure; + + final requestBody = { + 'ProjectID': projectID, + 'OrderServiceID': orderServiceID, + 'procedures': services.map((service) => service.toJson()).toList(), + }; + + await apiClient.post( + ApiConsts.addHHCOrder, + isRCService: true, + body: requestBody, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + loggerService.logError("Add HHC Order API Failed: $error, Status: $statusCode"); + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + int requestId = 0; + if (response is Map) { + requestId = response['response']; + } + try { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: requestId, + ); + + loggerService.logInfo("HHC Order added successfully: ProjectID=$projectID, OrderServiceID=$orderServiceID"); + } catch (e) { + loggerService.logError("Error processing add HHC order response: ${e.toString()}"); + failure = DataParsingFailure(e.toString()); + } + }, + ); + + if (failure != null) return Left(failure!); + if (apiResponse == null) return Left(ServerFailure("Unknown error")); + return Right(apiResponse!); + } catch (e) { + loggerService.logError("Unknown error in addHhcOrder: ${e.toString()}"); + return Left(UnknownFailure(e.toString())); + } + } + /*e-referral functions*/ diff --git a/lib/features/hmg_services/hmg_services_view_model.dart b/lib/features/hmg_services/hmg_services_view_model.dart index 5dd5482..1e97a63 100644 --- a/lib/features/hmg_services/hmg_services_view_model.dart +++ b/lib/features/hmg_services/hmg_services_view_model.dart @@ -1,12 +1,15 @@ import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; +import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_repo.dart'; import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_repo.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/req_models/create_e_referral_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/req_models/cmc_create_new_order_req_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/req_models/order_update_req_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/req_models/send_activation_code_ereferral_req_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_all_cities_resp_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_all_orders_resp_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_services_resp_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart'; import 'package:hmg_patient_app_new/services/error_handler_service.dart'; import 'models/req_models/check_activation_e_referral_req_model.dart'; @@ -14,28 +17,68 @@ import 'models/resq_models/relationship_type_resp_mode.dart'; class HmgServicesViewModel extends ChangeNotifier { final HmgServicesRepo hmgServicesRepo; + final BookAppointmentsRepo bookAppointmentsRepo; final ErrorHandlerService errorHandlerService; - HmgServicesViewModel({required this.hmgServicesRepo, required this.errorHandlerService}); + HmgServicesViewModel({required this.bookAppointmentsRepo, required this.hmgServicesRepo, required this.errorHandlerService}); bool isCmcOrdersLoading = false; bool isCmcServicesLoading = false; bool isUpdatingOrder = false; + bool isHospitalListLoading = false; + + // HHC specific loading states + bool isHhcOrdersLoading = false; + bool isHhcServicesLoading = false; List cmcOrdersList = []; List cmcServicesList = []; + List hospitalsList = []; + List filteredHospitalsList = []; + HospitalsModel? selectedHospital; + + // HHC specific lists + List hhcOrdersList = []; + List hhcServicesList = []; + + // CMC order creation state + HospitalsModel? selectedHospitalForOrder; + GetCMCServicesResponseModel? selectedServiceForOrder; + + // HHC order creation state (no hospital selection needed for home healthcare) + GetCMCServicesResponseModel? selectedServiceForHhcOrder; List relationTypes =[]; List getAllCitiesList =[]; Future getOrdersList() async { + // HHC multiple services selection + List selectedHhcServices = []; + + Future getCmcOrdersList() async { cmcOrdersList.clear(); isCmcOrdersLoading = true; notifyListeners(); await getAllCmcOrders(); } + // Helper to sort hospitals by distance (ascending). Safely converts distanceinkMS to double. + void _sortHospitalsByDistance(List list) { + double toDouble(dynamic v) { + if (v == null) return double.infinity; + if (v is num) return v.toDouble(); + if (v is String) return double.tryParse(v) ?? double.infinity; + return double.infinity; + } + + list.sort((a, b) { + final da = toDouble(a.distanceInKilometers); + final db = toDouble(b.distanceInKilometers); + return da.compareTo(db); + }); + } + Future getAllCmcOrders({ Function(dynamic)? onSuccess, Function(String)? onError, @@ -43,7 +86,7 @@ class HmgServicesViewModel extends ChangeNotifier { isCmcOrdersLoading = true; notifyListeners(); - final result = await hmgServicesRepo.getAllCmcOrders(); + final result = await hmgServicesRepo.getAllComprehensiveCheckupOrders(); result.fold( (failure) async { @@ -146,6 +189,342 @@ class HmgServicesViewModel extends ChangeNotifier { return success; } + Future getHospitalsList({Function(dynamic)? onSuccess, Function(String)? onError}) async { + isHospitalListLoading = true; + notifyListeners(); + + final result = await hmgServicesRepo.getHospitalsList(); + + result.fold( + (failure) async { + isHospitalListLoading = false; + notifyListeners(); + await errorHandlerService.handleError(failure: failure); + if (onError != null) { + onError(failure.toString()); + } + }, + (apiResponse) { + isHospitalListLoading = false; + if (apiResponse.messageStatus == 1) { + hospitalsList = apiResponse.data ?? []; + filteredHospitalsList = List.from(hospitalsList); + // ensure hospitals are sorted by distance before showing + _sortHospitalsByDistance(filteredHospitalsList); + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } else { + notifyListeners(); + if (onError != null) { + onError(apiResponse.errorMessage ?? 'Unknown error'); + } + } + }, + ); + } + + void filterHospitalsByString(String searchText, bool isArabic) { + if (searchText.isEmpty) { + filteredHospitalsList = List.from(hospitalsList); + _sortHospitalsByDistance(filteredHospitalsList); + } else { + filteredHospitalsList = hospitalsList.where((HospitalsModel hospital) { + final name = isArabic ? (hospital.nameN ?? '') : (hospital.name ?? ''); + return name.toLowerCase().contains(searchText.toLowerCase()); + }).toList(); + _sortHospitalsByDistance(filteredHospitalsList); + } + notifyListeners(); + } + + void setSelectedHospital(HospitalsModel? hospital) { + selectedHospital = hospital; + notifyListeners(); + } + + void clearHospitalSelection() { + selectedHospital = null; + filteredHospitalsList = List.from(hospitalsList); + _sortHospitalsByDistance(filteredHospitalsList); + notifyListeners(); + } + + // CMC Order management methods + void setSelectedHospitalForOrder(HospitalsModel? hospital) { + selectedHospitalForOrder = hospital; + notifyListeners(); + } + + void setSelectedServiceForOrder(GetCMCServicesResponseModel? service) { + selectedServiceForOrder = service; + notifyListeners(); + } + + void clearOrderSelection() { + selectedHospitalForOrder = null; + selectedServiceForOrder = null; + notifyListeners(); + } + + bool get isOrderReadyToConfirm => selectedHospitalForOrder != null && selectedServiceForOrder != null; + + Future addCmcOrder({ + required int projectID, + required int orderServiceID, + required List services, + Function(int)? onSuccess, + Function(String)? onError, + }) async { + isUpdatingOrder = true; + notifyListeners(); + + final result = await hmgServicesRepo.addCmcOrder( + projectID: projectID, + orderServiceID: orderServiceID, + services: services, + ); + + int requestId = 0; + + result.fold( + (failure) async { + isUpdatingOrder = false; + notifyListeners(); + await errorHandlerService.handleError(failure: failure); + if (onError != null) { + onError(failure.toString()); + } + }, + (apiResponse) { + isUpdatingOrder = false; + if (apiResponse.messageStatus == 1) { + requestId = apiResponse.data ?? 0; + notifyListeners(); + if (onSuccess != null) { + onSuccess(requestId); + } + } else { + notifyListeners(); + if (onError != null) { + onError(apiResponse.errorMessage ?? 'Unknown error'); + } + } + }, + ); + + return requestId; + } + +// ******************* HOME HEALTHCARE APIs ******************** + + Future getHhcOrdersList() async { + hhcOrdersList.clear(); + isHhcOrdersLoading = true; + notifyListeners(); + await getAllHhcOrders(); + } + + Future getAllHhcOrders({ + Function(dynamic)? onSuccess, + Function(String)? onError, + }) async { + isHhcOrdersLoading = true; + notifyListeners(); + + final result = await hmgServicesRepo.getAllHomeHealthCareCheckupOrders(); + + result.fold( + (failure) async { + isHhcOrdersLoading = false; + notifyListeners(); + await errorHandlerService.handleError(failure: failure); + if (onError != null) { + onError(failure.toString()); + } + }, + (apiResponse) { + isHhcOrdersLoading = false; + if (apiResponse.messageStatus == 1) { + hhcOrdersList = apiResponse.data ?? []; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } else { + notifyListeners(); + if (onError != null) { + onError(apiResponse.errorMessage ?? 'Unknown error'); + } + } + }, + ); + } + + Future getAllHhcServices({required int patientID, Function(dynamic)? onSuccess, Function(String)? onError}) async { + isHhcServicesLoading = true; + notifyListeners(); + + final result = await hmgServicesRepo.getAllHhcServices(patientID: patientID); + + result.fold( + (failure) async { + isHhcServicesLoading = false; + notifyListeners(); + await errorHandlerService.handleError(failure: failure); + if (onError != null) { + onError(failure.toString()); + } + }, + (apiResponse) { + isHhcServicesLoading = false; + if (apiResponse.messageStatus == 1) { + hhcServicesList = apiResponse.data ?? []; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } else { + notifyListeners(); + if (onError != null) { + onError(apiResponse.errorMessage ?? 'Unknown error'); + } + } + }, + ); + } + + Future updateHhcPresOrder({ + required OrderUpdateRequestModel requestModel, + Function(dynamic)? onSuccess, + Function(String)? onError, + }) async { + isUpdatingOrder = true; + notifyListeners(); + + final result = await hmgServicesRepo.updateHhcPresOrder(requestModel); + + bool success = false; + + result.fold( + (failure) async { + isUpdatingOrder = false; + notifyListeners(); + await errorHandlerService.handleError(failure: failure); + if (onError != null) { + onError(failure.toString()); + } + }, + (apiResponse) { + isUpdatingOrder = false; + if (apiResponse.messageStatus == 1) { + success = true; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } else { + notifyListeners(); + if (onError != null) { + onError(apiResponse.errorMessage ?? 'Unknown error'); + } + } + }, + ); + + return success; + } + + Future addHhcOrder({ + required int projectID, + required int orderServiceID, + required List services, + Function(int)? onSuccess, + Function(String)? onError, + }) async { + isUpdatingOrder = true; + notifyListeners(); + + final result = await hmgServicesRepo.addHhcOrder( + projectID: projectID, + orderServiceID: orderServiceID, + services: services, + ); + + int requestId = 0; + + result.fold( + (failure) async { + isUpdatingOrder = false; + notifyListeners(); + await errorHandlerService.handleError(failure: failure); + if (onError != null) { + onError(failure.toString()); + } + }, + (apiResponse) { + isUpdatingOrder = false; + if (apiResponse.messageStatus == 1) { + requestId = apiResponse.data ?? 0; + notifyListeners(); + if (onSuccess != null) { + onSuccess(requestId); + } + } else { + notifyListeners(); + if (onError != null) { + onError(apiResponse.errorMessage ?? 'Unknown error'); + } + } + }, + ); + + return requestId; + } + + // HHC Order management methods (no hospital selection for home healthcare) + void setSelectedServiceForHhcOrder(GetCMCServicesResponseModel? service) { + selectedServiceForHhcOrder = service; + notifyListeners(); + } + + void clearHhcOrderSelection() { + selectedServiceForHhcOrder = null; + selectedHhcServices.clear(); + notifyListeners(); + } + + bool get isHhcOrderReadyToConfirm => selectedServiceForHhcOrder != null; + + // Multiple HHC services selection methods + void toggleHhcServiceSelection(GetCMCServicesResponseModel service) { + final index = selectedHhcServices.indexWhere((s) => s.iD == service.iD); + if (index != -1) { + selectedHhcServices.removeAt(index); + } else { + selectedHhcServices.add(service); + } + notifyListeners(); + } + + bool isHhcServiceSelected(GetCMCServicesResponseModel service) { + return selectedHhcServices.any((s) => s.iD == service.iD); + } + + double getHhcSelectedServicesTotal() { + double total = 0.0; + for (var service in selectedHhcServices) { + total += (service.priceTotal ?? 0); + } + return total; + } + + void clearHhcServicesSelection() { + selectedHhcServices.clear(); + notifyListeners(); + } + /*e-referral functions*/ Future getRelationshipType({ diff --git a/lib/features/hmg_services/models/req_models/cmc_create_service_order_req_model.dart b/lib/features/hmg_services/models/req_models/cmc_create_service_order_req_model.dart new file mode 100644 index 0000000..af2808d --- /dev/null +++ b/lib/features/hmg_services/models/req_models/cmc_create_service_order_req_model.dart @@ -0,0 +1,41 @@ +class CmcCreateServiceOrderReqModel { + int? recordID; + String? serviceID; + String? selectedServiceName; + String? selectedServiceNameAR; + dynamic price; + dynamic vAT; + dynamic totalPrice; + + CmcCreateServiceOrderReqModel({ + this.recordID, + this.serviceID, + this.selectedServiceName, + this.selectedServiceNameAR, + this.price, + this.vAT, + this.totalPrice, + }); + + CmcCreateServiceOrderReqModel.fromJson(Map json) { + recordID = json['RecordID']; + serviceID = json['ServiceID']; + selectedServiceName = json['selectedServiceName']; + selectedServiceNameAR = json['selectedServiceNameAR']; + price = json['Price']; + vAT = json['VAT']; + totalPrice = json['TotalPrice']; + } + + Map toJson() { + final Map data = {}; + data['RecordID'] = recordID; + data['ServiceID'] = serviceID; + data['selectedServiceName'] = selectedServiceName; + data['selectedServiceNameAR'] = selectedServiceNameAR; + data['Price'] = price; + data['VAT'] = vAT; + data['TotalPrice'] = totalPrice; + return data; + } +} 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 12f0f27..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'; @@ -75,14 +77,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 +94,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 +160,7 @@ class LabViewModel extends ChangeNotifier { filterSuggestions() { final List labels = patientLabOrders - .expand((order) => order.testDetails!) + .expand((order) => order.testDetails ?? []) .map((detail) => detail.description) .whereType() .toList(); @@ -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/my_appointments/models/resp_models/hospital_model.dart b/lib/features/my_appointments/models/resp_models/hospital_model.dart index 9a211d0..a807b99 100644 --- a/lib/features/my_appointments/models/resp_models/hospital_model.dart +++ b/lib/features/my_appointments/models/resp_models/hospital_model.dart @@ -62,9 +62,9 @@ class HospitalsModel { mainProjectID = json['MainProjectID']; projectOutSA = json['ProjectOutSA']; usingInDoctorApp = json['UsingInDoctorApp']; - this.isHMC = json["IsHMC"]; - this.regionArabic = json['RegionNameN']; - this.regionEnglish = json['RegionName']; + isHMC = json["IsHMC"]; + regionArabic = json['RegionNameN']; + regionEnglish = json['RegionName']; } String? getRegionName(bool isArabic) { @@ -83,24 +83,22 @@ class HospitalsModel { Map toJson() { final Map data = new Map(); - data['Desciption'] = this.desciption; - data['DesciptionN'] = this.desciptionN; - data['ID'] = this.iD; - data['LegalName'] = this.legalName; - data['LegalNameN'] = this.legalNameN; - data['Name'] = this.name; - data['NameN'] = this.nameN; - data['PhoneNumber'] = this.phoneNumber; - data['SetupID'] = this.setupID; - data['DistanceInKilometers'] = this.distanceInKilometers; - data['IsActive'] = this.isActive; - data['Latitude'] = this.latitude; - data['Longitude'] = this.longitude; - data['MainProjectID'] = this.mainProjectID; - data['ProjectOutSA'] = this.projectOutSA; - data['UsingInDoctorApp'] = this.usingInDoctorApp; + data['Desciption'] = desciption; + data['DesciptionN'] = desciptionN; + data['ID'] = iD; + data['LegalName'] = legalName; + data['LegalNameN'] = legalNameN; + data['Name'] = name; + data['NameN'] = nameN; + data['PhoneNumber'] = phoneNumber; + data['SetupID'] = setupID; + data['DistanceInKilometers'] = distanceInKilometers; + data['IsActive'] = isActive; + data['Latitude'] = latitude; + data['Longitude'] = longitude; + data['MainProjectID'] = mainProjectID; + data['ProjectOutSA'] = projectOutSA; + data['UsingInDoctorApp'] = usingInDoctorApp; return data; } - - } 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 d39d84f..4a87906 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,6 +15,7 @@ class RadiologyViewModel extends ChangeNotifier { RadiologyRepo radiologyRepo; ErrorHandlerService errorHandlerService; + NavigationService navigationService; List patientRadiologyOrders = []; List filteredRadiologyOrders = []; @@ -22,7 +27,9 @@ class RadiologyViewModel extends ChangeNotifier { List get radiologySuggestions => _radiologySuggestionsList; - RadiologyViewModel({required this.radiologyRepo, required this.errorHandlerService}); + late PatientRadiologyResponseModel patientRadiologyOrderByAppointment; + + RadiologyViewModel({required this.radiologyRepo, required this.errorHandlerService, required this.navigationService}); initRadiologyViewModel() { patientRadiologyOrders.clear(); @@ -57,6 +64,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); @@ -107,23 +143,20 @@ class RadiologyViewModel extends ChangeNotifier { }, ); } + filterSuggestions() { - final List labels = patientRadiologyOrders - .map((detail) => detail.description) - .whereType() - .toList(); + final List labels = patientRadiologyOrders.map((detail) => detail.description).whereType().toList(); _radiologySuggestionsList = labels.toSet().toList(); notifyListeners(); } + filterRadiologyReports(String query) { if (query.isEmpty) { - patientRadiologyOrders =tempRadiologyOrders;// reset + patientRadiologyOrders = tempRadiologyOrders; // reset } else { - filteredRadiologyOrders = - filteredRadiologyOrders.where((desc) => desc.description!.toLowerCase().contains(query.toLowerCase())).toList(); + filteredRadiologyOrders = filteredRadiologyOrders.where((desc) => desc.description!.toLowerCase().contains(query.toLowerCase())).toList(); patientRadiologyOrders = filteredRadiologyOrders; } notifyListeners(); } - } diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart index 12f3589..e620783 100644 --- a/lib/presentation/appointments/appointment_details_page.dart +++ b/lib/presentation/appointments/appointment_details_page.dart @@ -11,26 +11,32 @@ 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/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'; 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'; import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; -import 'package:hmg_patient_app_new/widgets/shimmer/common_shimmer_widget.dart'; import 'package:maps_launcher/maps_launcher.dart'; import 'package:provider/provider.dart'; @@ -50,14 +56,17 @@ class _AppointmentDetailsPageState extends State { late MyAppointmentsViewModel myAppointmentsViewModel; late PrescriptionsViewModel prescriptionsViewModel; late BookAppointmentsViewModel bookAppointmentsViewModel; + late ContactUsViewModel contactUsViewModel; + late LabViewModel labViewModel; + late RadiologyViewModel radiologyViewModel; @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(); } @@ -68,6 +77,9 @@ 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); + labViewModel = Provider.of(context, listen: false); + radiologyViewModel = Provider.of(context, listen: false); return Scaffold( backgroundColor: AppColors.bgScaffoldColor, body: Column( @@ -75,7 +87,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, @@ -263,219 +284,345 @@ class _AppointmentDetailsPageState extends State { SizedBox(height: 16.h), ], ) - : 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, - ), - 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 CommonShimmerWidget() - : 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, - ), - ), - ], - ), - ], + // : SizedBox.shrink() + : GridView( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, + crossAxisSpacing: 16.h, + mainAxisSpacing: 16.w, + mainAxisExtent: 115.h, + ), + physics: NeverScrollableScrollPhysics(), + padding: EdgeInsets.zero, + shrinkWrap: true, + children: [ + MedicalFileCard( + 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(() 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(), + // ), + // ); + }), + ], + ), + // 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/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/appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart b/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart index 29a7b96..ad48a6d 100644 --- a/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart +++ b/lib/presentation/appointments/widgets/hospital_bottom_sheet/hospital_bottom_sheet_body.dart @@ -1,18 +1,13 @@ -import 'package:easy_localization/easy_localization.dart' - show tr, StringTranslateExtension; +import 'package:easy_localization/easy_localization.dart' show StringTranslateExtension; import 'package:flutter/material.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/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart'; import 'package:hmg_patient_app_new/features/my_appointments/appointment_via_region_viewmodel.dart'; import 'package:hmg_patient_app_new/features/my_appointments/models/facility_selection.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/hospital_bottom_sheet/hospital_list_items.dart'; -import 'package:hmg_patient_app_new/presentation/appointments/widgets/hospital_bottom_sheet/type_selection_widget.dart'; -import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; import 'package:hmg_patient_app_new/theme/colors.dart' show AppColors; import 'package:hmg_patient_app_new/widgets/input_widget.dart'; import 'package:provider/provider.dart'; @@ -53,8 +48,8 @@ class HospitalBottomSheetBody extends StatelessWidget { hintText: LocaleKeys.searchHospital.tr(), controller: searchText, onChange: (value) { - appointmentsViewModel.filterHospitalListByString(value, regionalViewModel.selectedRegionId , regionalViewModel.selectedFacilityType == - FacilitySelection.HMG.name); + appointmentsViewModel.filterHospitalListByString( + value, regionalViewModel.selectedRegionId, regionalViewModel.selectedFacilityType == FacilitySelection.HMG.name); }, isEnable: true, prefix: null, @@ -77,25 +72,15 @@ class HospitalBottomSheetBody extends StatelessWidget { SizedBox( height: MediaQuery.sizeOf(context).height * .4, child: ListView.separated( - itemBuilder: (_, index) - { - var hospital = regionalViewModel.selectedFacilityType == - FacilitySelection.HMG.name - ? appointmentsViewModel - .filteredHospitalList! - .registeredDoctorMap![ - regionalViewModel.selectedRegionId!]! - .hmgDoctorList![index] - : appointmentsViewModel - .filteredHospitalList - ?.registeredDoctorMap?[ - regionalViewModel.selectedRegionId!] - ?.hmcDoctorList?[index]; + itemBuilder: (_, index) { + var hospital = regionalViewModel.selectedFacilityType == FacilitySelection.HMG.name + ? appointmentsViewModel.filteredHospitalList!.registeredDoctorMap![regionalViewModel.selectedRegionId!]!.hmgDoctorList![index] + : appointmentsViewModel.filteredHospitalList?.registeredDoctorMap?[regionalViewModel.selectedRegionId!]?.hmcDoctorList?[index]; return HospitalListItem( - hospitalData: hospital, - isLocationEnabled: appointmentsViewModel.isLocationEnabled(), - ).onPress(() { - regionalViewModel.setHospitalModel(hospital); + hospitalData: hospital, + isLocationEnabled: appointmentsViewModel.isLocationEnabled(), + ).onPress(() { + regionalViewModel.setHospitalModel(hospital); if (regionalViewModel.regionBottomSheetType == RegionBottomSheetType.FOR_REGION) { regionalViewModel.setBottomSheetState(AppointmentViaRegionState.CLINIC_SELECTION); regionalViewModel.handleLastStepForRegion(); @@ -104,21 +89,18 @@ class HospitalBottomSheetBody extends StatelessWidget { regionalViewModel.handleLastStepForClinic(); } else if (regionalViewModel.regionBottomSheetType == RegionBottomSheetType.REGION_FOR_DENTAL_AND_LASER) { regionalViewModel.setBottomSheetState(AppointmentViaRegionState.DOCTOR_SELECTION); - regionalViewModel.handleLastStepForClinicForDentalAndLaser(appointmentsViewModel.selectedClinic.clinicID??-1); + regionalViewModel.handleLastStepForClinicForDentalAndLaser(appointmentsViewModel.selectedClinic.clinicID ?? -1); // regionalViewModel.handleLastStepForClinic(); } - });}, + }); + }, separatorBuilder: (_, __) => SizedBox( height: 16.h, ), - itemCount: (regionalViewModel.selectedFacilityType == - FacilitySelection.HMG.name - ? (appointmentsViewModel.filteredHospitalList?.registeredDoctorMap?[ - regionalViewModel.selectedRegionId]?.hmgDoctorList) - : (appointmentsViewModel - .filteredHospitalList - ?.registeredDoctorMap?[ - regionalViewModel.selectedRegionId]?.hmcDoctorList))?.length ?? + itemCount: (regionalViewModel.selectedFacilityType == FacilitySelection.HMG.name + ? (appointmentsViewModel.filteredHospitalList?.registeredDoctorMap?[regionalViewModel.selectedRegionId]?.hmgDoctorList) + : (appointmentsViewModel.filteredHospitalList?.registeredDoctorMap?[regionalViewModel.selectedRegionId]?.hmcDoctorList)) + ?.length ?? 0), ) ], diff --git a/lib/presentation/authentication/quick_login.dart b/lib/presentation/authentication/quick_login.dart index f10d84f..bdeb0ff 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,18 +24,13 @@ 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), + return Padding( + padding: EdgeInsets.all(24.h), child: Column( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center, @@ -104,11 +102,12 @@ class QuickLoginState extends State { borderColor: Color(0xffED1C2B), textColor: Colors.white, icon: AppAssets.apple_finder, + height: 56.h, )), ], ), SizedBox( - height: 16, + height: 16.h, ), Row( mainAxisAlignment: MainAxisAlignment.end, @@ -117,11 +116,13 @@ class QuickLoginState extends State { 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/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/comprehensive_checkup/cmc_order_detail_page.dart b/lib/presentation/comprehensive_checkup/cmc_order_detail_page.dart index 97e5e6d..7547fd0 100644 --- a/lib/presentation/comprehensive_checkup/cmc_order_detail_page.dart +++ b/lib/presentation/comprehensive_checkup/cmc_order_detail_page.dart @@ -1,18 +1,21 @@ import 'dart:async'; +import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; -import 'package:fluttertoast/fluttertoast.dart'; +import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/utils/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/hmg_services/hmg_services_view_model.dart'; -import 'package:hmg_patient_app_new/features/hmg_services/models/req_models/order_update_req_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_all_orders_resp_model.dart'; -import 'package:hmg_patient_app_new/services/dialog_service.dart'; +import 'package:hmg_patient_app_new/presentation/comprehensive_checkup/widgets/cmc_ui_selection_helper.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; -import 'package:intl/intl.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:shimmer/shimmer.dart'; class CmcOrderDetailPage extends StatefulWidget { const CmcOrderDetailPage({super.key}); @@ -22,14 +25,12 @@ class CmcOrderDetailPage extends StatefulWidget { } class _CmcOrderDetailPageState extends State { - bool _isLoading = false; - @override void initState() { super.initState(); final hmgServicesViewModel = context.read(); scheduleMicrotask(() async { - await hmgServicesViewModel.getOrdersList(); + await hmgServicesViewModel.getCmcOrdersList(); }); } @@ -54,234 +55,129 @@ class _CmcOrderDetailPageState extends State { if (dateString == null) return ''; try { final date = DateTime.parse(dateString); - return DateFormat('dd MMM yyyy').format(date); + return DateFormat('MMM dd, yyyy').format(date); } catch (e) { return dateString; } } - Future _showCancelConfirmationDialog({ - required BuildContext context, - required HmgServicesViewModel viewModel, - required GetCMCAllOrdersResponseModel order, - }) async { - final dialogService = context.read(); - - await dialogService.showCommonBottomSheetWithoutH( - label: "Confirm Cancellation".needTranslation, - message: "Are you sure you want to cancel this order?".needTranslation, - onOkPressed: () async { - Navigator.of(context).pop(); - - // Show loading state - setState(() { - _isLoading = true; - }); - - final requestModel = OrderUpdateRequestModel( - presOrderID: order.iD, - rejectionReason: "", - presOrderStatus: 4, // Cancelled status - editedBy: 3, - ); - - final success = await viewModel.updateCmcPresOrder( - requestModel: requestModel, - onSuccess: (_) async { - setState(() { - _isLoading = false; - }); - Fluttertoast.showToast( - msg: "Order cancelled successfully".needTranslation, - toastLength: Toast.LENGTH_SHORT, - gravity: ToastGravity.BOTTOM, - backgroundColor: Colors.green, - textColor: Colors.white, - ); - await viewModel.getAllCmcOrders(); - }, - onError: (error) { - setState(() { - _isLoading = false; - }); - Fluttertoast.showToast( - msg: error, - toastLength: Toast.LENGTH_SHORT, - gravity: ToastGravity.BOTTOM, - backgroundColor: Colors.red, - textColor: Colors.white, - ); - }, - ); - - if (!success) { - setState(() { - _isLoading = false; - }); - } - }, - onCancelPressed: () { - Navigator.of(context).pop(); - }, - ); - } - Widget _buildLoadingShimmer() { return ListView.separated( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), - padding: EdgeInsets.all(21.w), itemCount: 3, separatorBuilder: (_, __) => SizedBox(height: 12.h), itemBuilder: (context, index) { - return Shimmer.fromColors( - baseColor: Colors.grey[300]!, - highlightColor: Colors.grey[100]!, - child: Container( - height: 120.h, - decoration: BoxDecoration( - color: Colors.white, - borderRadius: BorderRadius.circular(10.r), - ), - ), - ); + return _buildOrderCard(GetCMCAllOrdersResponseModel(), isLoading: true); }, ); } - Widget _buildOrderCard(GetCMCAllOrdersResponseModel order) { + Widget _buildOrderCard(GetCMCAllOrdersResponseModel order, {bool isLoading = false}) { final statusColor = _getStatusColor(order.statusId); final canCancel = order.statusId == 1 || order.statusId == 2; - return Container( - decoration: BoxDecoration( - color: statusColor, - borderRadius: BorderRadius.circular(10.r), - boxShadow: [ - BoxShadow( - color: const Color(0xff000000).withValues(alpha: 0.05), - blurRadius: 27, - offset: const Offset(0, -3), - ), - ], + return AnimatedContainer( + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: true, ), - child: Container( - margin: EdgeInsets.only(left: 6.w), - padding: EdgeInsets.symmetric(vertical: 14.h, horizontal: 12.w), - decoration: BoxDecoration( - color: Colors.white, - border: Border.all(color: Colors.white, width: 1), - borderRadius: BorderRadius.only( - bottomRight: Radius.circular(10.r), - topRight: Radius.circular(10.r), - ), - ), - child: Row( + child: Padding( + padding: EdgeInsets.all(16.w), + child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Expanded( - child: Column( - mainAxisAlignment: MainAxisAlignment.start, - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - order.statusText ?? '', - style: TextStyle( - fontSize: 12.f, - fontWeight: FontWeight.w600, - color: statusColor, - letterSpacing: -0.4, - height: 16 / 10, - ), - ), - SizedBox(height: 6.h), - Text( - '${"Request ID".needTranslation}: ${order.iD}', - style: TextStyle( - fontSize: 16.f, - fontWeight: FontWeight.w600, - color: const Color(0xff2E303A), - letterSpacing: -0.64, - height: 25 / 16, - ), - ), - SizedBox(height: 4.h), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "${"Hospital".needTranslation}: ", - style: TextStyle( - fontSize: 12.f, - fontWeight: FontWeight.w600, - color: const Color(0xff575757), - letterSpacing: -0.4, - height: 16 / 10, - ), - ), - Expanded( - child: Text( - order.projectName?.trim() ?? '', - style: TextStyle( - fontSize: 14.f, - fontWeight: FontWeight.w600, - color: const Color(0xff2B353E), - letterSpacing: -0.56, - ), - ), - ), - ], - ), - ], - ), - ), - Column( + // Status and Date Row + Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.end, + crossAxisAlignment: CrossAxisAlignment.start, children: [ - Text( - _formatDate(order.created), - style: TextStyle( - fontSize: 12.f, - fontWeight: FontWeight.w600, - color: const Color(0xff2B353E), - letterSpacing: -0.4, - height: 16 / 10, + Container( + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 6.h), + decoration: BoxDecoration( + color: statusColor.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(8.r), ), + child: (isLoading ? "Processing" : order.statusText ?? '') + .toText12( + color: statusColor, + fontWeight: FontWeight.w600, + ) + .toShimmer2(isShow: isLoading), ), - if (canCancel) ...[ - SizedBox(height: 12.h), - InkWell( - onTap: _isLoading - ? null - : () { - _showCancelConfirmationDialog( - context: context, - viewModel: context.read(), - order: order, - ); - }, - child: Container( - padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 14.w), - decoration: BoxDecoration( - color: _isLoading ? Colors.grey : const Color(0xffD02127), - border: Border.all(color: Colors.white, width: 1), - borderRadius: BorderRadius.circular(10.r), - ), - child: Text( - "Cancel".needTranslation, - style: TextStyle( - fontSize: 12.f, - fontWeight: FontWeight.w600, - color: Colors.white, - letterSpacing: -0.4, - ), + SizedBox(width: 8.w), + (isLoading ? "Jan 15, 2024" : _formatDate(order.created)) + .toText12( + color: AppColors.textColorLight, + fontWeight: FontWeight.w500, + ) + .toShimmer2(isShow: isLoading), + ], + ), + + SizedBox(height: 16.h), + + // Request ID + Row( + children: [ + if (!isLoading) ...[ + "Request ID:".needTranslation.toText14( + color: AppColors.textColorLight, + weight: FontWeight.w500, ), - ), - ), + SizedBox(width: 4.w), ], + (isLoading ? "12345" : "${order.iD ?? '-'}").toText16(isBold: true).toShimmer2(isShow: isLoading), ], ), + + SizedBox(height: 12.h), + + // Chips for Hospital, Service, and Amount + Wrap( + spacing: 6.w, + runSpacing: 6.h, + children: [ + // Hospital + if (order.projectName != null || isLoading) + AppCustomChipWidget( + icon: AppAssets.hospital, + labelText: isLoading ? "Hospital Name" : order.projectName ?? '-', + ).toShimmer2(isShow: isLoading), + + // Service + if (order.serviceText != null || isLoading) + AppCustomChipWidget( + icon: AppAssets.servicesBottom, + labelText: isLoading ? "Service Name" : order.serviceText ?? '-', + ).toShimmer2(isShow: isLoading), + ], + ), + + // Cancel Button + if (canCancel || isLoading) ...[ + SizedBox(height: 16.h), + Row( + children: [ + Expanded( + child: CustomButton( + text: "Cancel Order".needTranslation, + onPressed: isLoading ? () {} : () => CmcUiSelectionHelper.showCancelConfirmationDialog(context: context, order: order), + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 14.f, + fontWeight: FontWeight.w600, + borderRadius: 10.r, + height: 44.h, + ).toShimmer2(isShow: isLoading), + ), + ], + ), + ] ], ), ), @@ -290,20 +186,22 @@ class _CmcOrderDetailPageState extends State { Widget _buildEmptyState() { return Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Icon(Icons.inbox_outlined, size: 80.w, color: AppColors.greyColor), - SizedBox(height: 16.h), - Text( - "No orders found".needTranslation, - style: TextStyle( - fontSize: 16.f, - color: AppColors.greyTextColor, - fontWeight: FontWeight.w500, - ), + child: Padding( + padding: EdgeInsets.symmetric(vertical: 40.h), + child: Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 12.r, + hasShadow: false, ), - ], + child: Utils.getNoDataWidget( + context, + noDataText: "You don't have any CMC orders yet.".needTranslation, + isSmallWidget: true, + width: 62.w, + height: 62.h, + ), + ), ), ); } @@ -313,31 +211,44 @@ class _CmcOrderDetailPageState extends State { return CollapsingListView( title: "CMC Orders".needTranslation, isLeading: true, - child: Consumer( - builder: (context, viewModel, child) { - if (viewModel.isCmcOrdersLoading) { - return _buildLoadingShimmer(); - } - - if (viewModel.cmcOrdersList.isEmpty) { - return SizedBox( - height: MediaQuery.of(context).size.height * 0.6, - child: _buildEmptyState(), - ); - } - - return ListView.separated( - shrinkWrap: true, - physics: const NeverScrollableScrollPhysics(), - padding: EdgeInsets.all(21.w), - itemCount: viewModel.cmcOrdersList.length, - separatorBuilder: (_, __) => SizedBox(height: 12.h), - itemBuilder: (context, index) { - final order = viewModel.cmcOrdersList.reversed.toList()[index]; - return _buildOrderCard(order); - }, - ); - }, + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Consumer( + builder: (context, viewModel, child) { + if (viewModel.isCmcOrdersLoading) { + return _buildLoadingShimmer(); + } + + if (viewModel.cmcOrdersList.isEmpty) { + return _buildEmptyState(); + } + + return ListView.separated( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: viewModel.cmcOrdersList.length, + separatorBuilder: (_, __) => SizedBox(height: 12.h), + itemBuilder: (context, index) { + final order = viewModel.cmcOrdersList.reversed.toList()[index]; + + return AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: _buildOrderCard(order), + ), + ), + ); + }, + ); + }, + ), + ], + ).paddingSymmetrical(24.w, 0), ), ); } diff --git a/lib/presentation/comprehensive_checkup/cmc_selection_review_page.dart b/lib/presentation/comprehensive_checkup/cmc_selection_review_page.dart new file mode 100644 index 0000000..b6164d9 --- /dev/null +++ b/lib/presentation/comprehensive_checkup/cmc_selection_review_page.dart @@ -0,0 +1,515 @@ +import 'dart:developer'; + +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/route_extensions.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/hmg_services/hmg_services_view_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/req_models/cmc_create_new_order_req_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_services_resp_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart'; +import 'package:hmg_patient_app_new/features/payfort/payfort_view_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/comprehensive_checkup/widgets/cmc_ui_selection_helper.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/loader/bottomsheet_loader.dart'; +import 'package:maps_launcher/maps_launcher.dart'; +import 'package:provider/provider.dart'; + +class CmcSelectionReviewPage extends StatefulWidget { + final GetCMCServicesResponseModel selectedService; + final HospitalsModel? preSelectedHospital; + + const CmcSelectionReviewPage({super.key, required this.selectedService, this.preSelectedHospital}); + + @override + State createState() => _CmcSelectionReviewPageState(); +} + +class _CmcSelectionReviewPageState extends State { + @override + void initState() { + super.initState(); + // Initialize ViewModel state with preselected hospital if provided + if (widget.preSelectedHospital != null) { + WidgetsBinding.instance.addPostFrameCallback((_) { + final hmgServicesViewModel = context.read(); + hmgServicesViewModel.setSelectedHospitalForOrder(widget.preSelectedHospital); + hmgServicesViewModel.setSelectedServiceForOrder(widget.selectedService); + }); + } + } + + @override + Widget build(BuildContext context) { + final appState = getIt.get(); + final isArabic = appState.isArabic(); + + return CollapsingListView( + title: "Summary".needTranslation, + bottomChild: _buildBottomButton(), + child: SingleChildScrollView( + padding: EdgeInsets.all(16.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildOrderSummaryCard(isArabic), + SizedBox(height: 16.h), + _buildSelectedServiceCard(isArabic), + SizedBox(height: 16.h), + _buildPaymentSummary(), + ], + ), + ), + ); + } + + Widget _buildOrderSummaryCard(bool isArabic) { + return Consumer( + builder: (context, hmgServicesViewModel, child) { + final selectedHospital = hmgServicesViewModel.selectedHospitalForOrder; + final isLocationSelected = selectedHospital != null; + + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 16.r, + ), + padding: EdgeInsets.all(16.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + "Select Hospital".needTranslation, + style: TextStyle( + fontSize: 16.f, + fontWeight: FontWeight.w700, + color: AppColors.blackColor, + letterSpacing: -0.5, + ), + ), + SizedBox(height: 12.h), + _buildHospitalSelector(isArabic, selectedHospital, isLocationSelected), + if (isLocationSelected) ...[ + SizedBox(height: 16.h), + _buildHospitalMap(selectedHospital), + ], + ], + ), + ); + }, + ); + } + + Widget _buildHospitalSelector(bool isArabic, HospitalsModel? selectedHospital, bool isLocationSelected) { + return InkWell( + onTap: _showHospitalSelectionBottomSheet, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 14.h), + decoration: BoxDecoration( + color: AppColors.bgScaffoldColor, + borderRadius: BorderRadius.circular(12.r), + border: Border.all( + color: AppColors.greyColor.withAlpha(51), + width: 1, + ), + ), + child: Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + isLocationSelected && selectedHospital != null + ? (isArabic ? (selectedHospital.nameN ?? selectedHospital.name ?? '') : (selectedHospital.name ?? '')) + : "Select Hospital".needTranslation, + style: TextStyle( + fontSize: 14.f, + fontWeight: isLocationSelected ? FontWeight.w600 : FontWeight.w400, + color: isLocationSelected ? AppColors.blackColor : AppColors.greyTextColor, + letterSpacing: -0.4, + ), + ), + ], + ), + ), + Icon( + Icons.keyboard_arrow_down, + color: AppColors.greyTextColor, + size: 24.h, + ), + ], + ), + ), + ); + } + + Widget _buildHospitalMap(HospitalsModel selectedHospital) { + final String lat = selectedHospital.latitude ?? "0.0"; + final String lng = selectedHospital.longitude ?? "0.0"; + + log("selectedHospital: $lng and $lat"); + + if (lat == "0.0" || lng == "0.0") return SizedBox.shrink(); + + final String staticMapUrl = + "https://maps.googleapis.com/maps/api/staticmap?center=$lat,$lng&zoom=16&size=600x300&maptype=roadmap&markers=color:red%7C$lat,$lng&key=AIzaSyCyDbWUM9d_sBUGIE8PcuShzPaqO08NSC8"; + + return Stack( + children: [ + ClipRRect( + borderRadius: BorderRadius.circular(12.r), + child: Image.network( + staticMapUrl, + height: 200.h, + width: double.infinity, + fit: BoxFit.cover, + loadingBuilder: (context, child, loadingProgress) { + if (loadingProgress == null) return child; + return Container( + height: 200.h, + decoration: BoxDecoration( + color: AppColors.bgScaffoldColor, + borderRadius: BorderRadius.circular(12.r), + ), + child: Center( + child: CircularProgressIndicator( + color: AppColors.primaryRedColor, + ), + ), + ); + }, + errorBuilder: (context, error, stackTrace) { + return Container( + height: 200.h, + decoration: BoxDecoration( + color: AppColors.bgScaffoldColor, + borderRadius: BorderRadius.circular(12.r), + ), + child: Center( + child: Icon( + Icons.error_outline, + size: 48.h, + color: AppColors.greyTextColor, + ), + ), + ); + }, + ), + ), + Positioned( + bottom: 12.h, + right: 12.w, + child: InkWell( + onTap: () => _launchDirections(selectedHospital), + child: Container( + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 8.h), + decoration: BoxDecoration( + color: AppColors.whiteColor, + borderRadius: BorderRadius.circular(1000.r), + boxShadow: [ + BoxShadow( + color: Color.fromARGB(26, 0, 0, 0), + blurRadius: 8, + offset: Offset(0, 2), + ), + ], + ), + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + Utils.buildSvgWithAssets( + icon: AppAssets.directions_icon, + width: 16.w, + height: 16.h, + ), + SizedBox(width: 6.w), + Text( + "Get Directions".needTranslation, + style: TextStyle( + fontSize: 12.f, + fontWeight: FontWeight.w600, + color: AppColors.blackColor, + letterSpacing: -0.4, + ), + ), + ], + ), + ), + ), + ), + ], + ); + } + + Widget _buildSelectedServiceCard(bool isArabic) { + final serviceName = isArabic ? (widget.selectedService.textN ?? widget.selectedService.text ?? '') : (widget.selectedService.text ?? ''); + final price = widget.selectedService.priceTotal ?? 0.0; + + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 16.r, + ), + padding: EdgeInsets.all(16.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Selected Service".needTranslation.toText14( + weight: FontWeight.w600, + color: AppColors.greyTextColor, + letterSpacing: -0.4, + ), + SizedBox(height: 6.h), + Row( + children: [ + Expanded(child: serviceName.toText16(weight: FontWeight.w700, color: AppColors.blackColor, letterSpacing: -0.5)), + ], + ), + ], + ), + ); + } + + Widget _buildPaymentSummary() { + // Use selected service from widget + final service = widget.selectedService; + + log("service: ${service.toJson()}"); + + final double amountBeforeTax = service.price ?? 0.0; + final double taxAmount = service.priceVAT ?? 0.0; + final double totalAmount = service.priceTotal ?? (amountBeforeTax + taxAmount); + + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: false, + ), + child: Consumer(builder: (context, payfortVM, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 24.h), + "Total amount to pay".needTranslation.toText18(isBold: true).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 17.h), + + // Amount before tax + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "Amount before tax".needTranslation.toText14(isBold: true), + Utils.getPaymentAmountWithSymbol( + amountBeforeTax.toString().toText16(isBold: true), + AppColors.blackColor, + 13, + isSaudiCurrency: true, + ), + ], + ).paddingSymmetrical(24.h, 0.h), + + // VAT (use label VAT 15% if desired) + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "VAT 15%".needTranslation.toText14(isBold: true, color: AppColors.greyTextColor), + Utils.getPaymentAmountWithSymbol( + taxAmount.toString().toText14(isBold: true, color: AppColors.greyTextColor), + AppColors.greyTextColor, + 13, + isSaudiCurrency: true, + ), + ], + ).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 17.h), + // Total Amount + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "".needTranslation.toText14(isBold: true), + Utils.getPaymentAmountWithSymbol( + totalAmount.toString().toText24(isBold: true), + AppColors.blackColor, + 17, + isSaudiCurrency: true, + ), + ], + ).paddingSymmetrical(24.h, 0.h), + + SizedBox(height: 16.h), + ], + ); + }), + ); + } + + Widget _buildBottomButton() { + return Consumer( + builder: (context, hmgServicesViewModel, child) { + final isLocationSelected = hmgServicesViewModel.selectedHospitalForOrder != null; + + return SafeArea( + top: false, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h), + decoration: BoxDecoration( + color: AppColors.whiteColor, + boxShadow: [ + BoxShadow( + color: Color.fromARGB(13, 0, 0, 0), + blurRadius: 8, + offset: Offset(0, -2), + ), + ], + ), + child: CustomButton( + text: "Confirm".needTranslation, + onPressed: () { + isLocationSelected ? _handleConfirm() : null; + }, + textColor: AppColors.whiteColor, + backgroundColor: isLocationSelected ? AppColors.successColor : AppColors.greyColor, + borderRadius: 12.r, + borderColor: Colors.transparent, + borderWidth: 0, + padding: EdgeInsets.symmetric(vertical: 14.h), + ), + ), + ); + }, + ); + } + + void _showHospitalSelectionBottomSheet() { + CmcUiSelectionHelper.showHospitalSelectionBottomSheet(context: context, onHospitalSelected: (hospital) => context.pop()); + } + + void _launchDirections(HospitalsModel selectedHospital) { + final double lat = double.parse(selectedHospital.latitude ?? "0.0"); + final double lng = double.parse(selectedHospital.longitude ?? "0.0"); + + if (lat != 0.0 && lng != 0.0) { + MapsLauncher.launchCoordinates( + lat, + lng, + selectedHospital.name ?? "Hospital", + ); + } + } + + showSuccessBottomSheet(int requestId, HmgServicesViewModel hmgServicesViewModel) { + return showCommonBottomSheetWithoutHeight( + context, + child: Padding( + padding: EdgeInsets.all(16.w), + child: Column( + children: [ + Utils.getSuccessWidget(loadingText: "Your request has been successfully submitted.".needTranslation), + Row( + children: [ + "Here is your request #: ".needTranslation.toText14( + color: AppColors.textColorLight, + weight: FontWeight.w500, + ), + SizedBox(width: 4.w), + ("$requestId").toText16(isBold: true), + ], + ), + SizedBox(height: 24.h), + Row( + children: [ + Expanded( + child: CustomButton( + height: 56.h, + text: LocaleKeys.ok.tr(), + onPressed: () { + context.pop(); + context.pop(); + hmgServicesViewModel.getAllCmcOrders(); + }, + textColor: AppColors.whiteColor, + ), + ), + ], + ), + ], + ), + ), + isCloseButtonVisible: false, + isDismissible: false, + isFullScreen: false, + ); + } + + void _handleConfirm() { + final hmgServicesViewModel = context.read(); + final selectedHospital = hmgServicesViewModel.selectedHospitalForOrder; + + if (selectedHospital == null) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text("Please select a hospital to continue".needTranslation), + backgroundColor: AppColors.errorColor, + ), + ); + return; + } + + final selectedService = widget.selectedService; + return showCommonBottomSheetWithoutHeight( + title: LocaleKeys.notice.tr(context: context), + context, + child: Utils.getWarningWidget( + loadingText: "Are you sure you want to submit this request?".needTranslation, + isShowActionButtons: true, + onCancelTap: () { + Navigator.pop(context); + }, + onConfirmTap: () async { + Navigator.pop(context); + LoaderBottomSheet.showLoader(); + + // Create the services list + final servicesList = [ + PatientERCMCInsertServicesList( + recordID: selectedService.iD, + serviceID: selectedService.serviceID, + selectedServiceName: selectedService.text, + selectedServiceNameAR: selectedService.textN, + price: selectedService.price, + vAT: selectedService.priceVAT, + totalPrice: selectedService.priceTotal, + ), + ]; + + await hmgServicesViewModel.addCmcOrder( + projectID: selectedHospital.mainProjectID ?? 0, + orderServiceID: selectedService.orderServiceID ?? 3, + services: servicesList, + onSuccess: (requestId) { + LoaderBottomSheet.hideLoader(); + showSuccessBottomSheet(requestId, hmgServicesViewModel); + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); + // showCommonBottomSheetWithoutHeight(context, child: Utils.getErrorWidget(loadingText: err), callBackFunc: () {}); + }, + ); + }), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } +} diff --git a/lib/presentation/comprehensive_checkup/comprehensive_checkup_page.dart b/lib/presentation/comprehensive_checkup/comprehensive_checkup_page.dart index d242e55..5529b93 100644 --- a/lib/presentation/comprehensive_checkup/comprehensive_checkup_page.dart +++ b/lib/presentation/comprehensive_checkup/comprehensive_checkup_page.dart @@ -1,5 +1,6 @@ import 'dart:async'; +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'; @@ -12,9 +13,12 @@ import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_view_mode import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_all_orders_resp_model.dart'; import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_services_resp_model.dart'; import 'package:hmg_patient_app_new/presentation/comprehensive_checkup/cmc_order_detail_page.dart'; +import 'package:hmg_patient_app_new/presentation/comprehensive_checkup/cmc_selection_review_page.dart'; +import 'package:hmg_patient_app_new/presentation/comprehensive_checkup/widgets/cmc_ui_selection_helper.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/media_viewer/full_screen_image_viewer.dart'; import 'package:hmg_patient_app_new/widgets/radio_list_tile_widget.dart'; import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart'; @@ -67,120 +71,142 @@ class _ComprehensiveCheckupPageState extends State { if (status == 1) { // pending - statusColor = const Color(0xffCC9B14); + statusColor = AppColors.statusPendingColor; } else if (status == 2) { // processing - statusColor = const Color(0xff2E303A); + statusColor = AppColors.statusProcessingColor; } else if (status == 3) { // completed - statusColor = const Color(0xff359846); + statusColor = AppColors.statusCompletedColor; } else { // cancel / rejected - statusColor = const Color(0xffD02127); + statusColor = AppColors.statusRejectedColor; } + final canCancel = order.statusId == 1 || order.statusId == 2; + return Container( width: double.infinity, - margin: EdgeInsets.all(16.h), - decoration: BoxDecoration( + margin: EdgeInsets.all(16.w), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.whiteColor, - borderRadius: BorderRadius.circular(12.h), - boxShadow: [ - BoxShadow( - color: Color.fromARGB(13, 0, 0, 0), - blurRadius: 4, - offset: const Offset(0, 2), - ), - ], + borderRadius: 24.h, + hasShadow: true, ), - child: Container( - padding: EdgeInsets.all(14.h), + child: Padding( + padding: EdgeInsets.all(16.w), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ + // Status and Date Row Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, crossAxisAlignment: CrossAxisAlignment.start, children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - statusDisp, - style: TextStyle( - fontSize: 12.h, - fontWeight: FontWeight.w600, - color: statusColor, - letterSpacing: -0.4, - ), - ), - SizedBox(height: 6.h), - Text( - '${"Request ID".needTranslation}: ${order.iD}', - style: TextStyle( - fontSize: 16.h, - fontWeight: FontWeight.w600, - color: AppColors.blackColor, - letterSpacing: -0.64, - ), - ), - SizedBox(height: 4.h), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "${"Hospital".needTranslation}: ", - style: TextStyle( - fontSize: 12.h, - fontWeight: FontWeight.w600, - color: AppColors.greyTextColor, - letterSpacing: -0.4, - ), - ), - Expanded( - child: Text( - order.projectName ?? "", - style: TextStyle( - fontSize: 14.h, - fontWeight: FontWeight.w600, - color: const Color(0xff2B353E), - letterSpacing: -0.56, - ), - ), - ), - ], - ), - SizedBox(height: 4.h), - Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text( - "${"Service Name".needTranslation}: ", - style: TextStyle( - fontSize: 12.h, - fontWeight: FontWeight.w600, - color: AppColors.greyTextColor, - letterSpacing: -0.4, - ), - ), - Expanded( - child: Text( - order.serviceText ?? "", - style: TextStyle( - fontSize: 14.h, - fontWeight: FontWeight.w600, - color: const Color(0xff2B353E), - letterSpacing: -0.56, - ), - ), - ), - ], - ), - ], + Container( + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 6.h), + decoration: BoxDecoration( + color: statusColor.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(8.r), + ), + child: statusDisp.toText12( + color: statusColor, + fontWeight: FontWeight.w600, ), ), + SizedBox(width: 8.w), + if (order.created != null) + DateFormat('MMM dd, yyyy').format(DateTime.parse(order.created!)).toText12( + color: AppColors.textColorLight, + fontWeight: FontWeight.w500, + ), + ], + ), + + SizedBox(height: 16.h), + + // Request ID + Row( + children: [ + "Request ID:".needTranslation.toText14(color: AppColors.textColorLight, weight: FontWeight.w500), + SizedBox(width: 4.w), + "${order.iD ?? '-'}".toText16(isBold: true), ], ), + + SizedBox(height: 12.h), + + // Chips for Hospital, Service, and Amount + Wrap( + spacing: 6.w, + runSpacing: 6.h, + children: [ + // Hospital + if (order.projectName != null) + AppCustomChipWidget( + icon: AppAssets.hospital, + labelText: order.projectName ?? '-', + ), + + // Service + if (order.serviceText != null) + AppCustomChipWidget( + icon: AppAssets.file_icon, + labelText: order.serviceText ?? '-', + ), + ], + ), + + SizedBox(height: 16.h), + + // Info message + Container( + padding: EdgeInsets.all(12.w), + decoration: BoxDecoration( + color: AppColors.infoBannerBgColor, + borderRadius: BorderRadius.circular(10.r), + border: Border.all( + color: AppColors.infoBannerBorderColor, + width: 1, + ), + ), + child: Row( + children: [ + Icon( + Icons.info_outline, + size: 20.w, + color: AppColors.infoBannerIconColor, + ), + SizedBox(width: 8.w), + Expanded( + child: "You have a pending order. Please wait for it to be processed.".needTranslation.toText12( + color: AppColors.infoBannerTextColor, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + if (canCancel) ...[ + SizedBox(height: 16.h), + Row( + children: [ + Expanded( + child: CustomButton( + text: "Cancel Order".needTranslation, + onPressed: () => CmcUiSelectionHelper.showCancelConfirmationDialog(context: context, order: order), + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 14.f, + fontWeight: FontWeight.w600, + borderRadius: 10.r, + height: 44.h, + ), + ), + ], + ), + ] ], ), ), @@ -264,10 +290,14 @@ class _ComprehensiveCheckupPageState extends State { color: Color.fromARGB(51, 0, 0, 0), borderRadius: BorderRadius.circular(1000.r), ), - margin: EdgeInsets.all(12.h), + margin: EdgeInsets.all(16.h), child: IconButton( - icon: Icon(Icons.zoom_in, color: Colors.white), - padding: EdgeInsets.all(12.h), + icon: Icon( + Icons.zoom_in, + color: Colors.white, + size: 26.w, + ), + padding: EdgeInsets.all(10.h), onPressed: () => _showFullScreenImage(context, imagePath, isSvg: false), ), ), @@ -281,13 +311,22 @@ class _ComprehensiveCheckupPageState extends State { void _proceedWithSelectedService() { if (_selectedService != null) { - // TODO: Navigate to next step or create order - // This will be implemented based on your flow - // For now, just show a message - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text('Selected: ${_selectedService!.text}'), - ), + final hmgServicesViewModel = context.read(); + + // Store selected service in ViewModel + hmgServicesViewModel.setSelectedServiceForOrder(_selectedService); + hmgServicesViewModel.getHospitalsList(); + // Show hospital selection bottom sheet using common helper + CmcUiSelectionHelper.showHospitalSelectionBottomSheet( + context: context, + onHospitalSelected: (hospital) { + Navigator.of(context).pushReplacement( + CustomPageRoute( + page: CmcSelectionReviewPage(selectedService: _selectedService!, preSelectedHospital: hospital), + direction: AxisDirection.left, + ), + ); + }, ); } } @@ -296,15 +335,15 @@ class _ComprehensiveCheckupPageState extends State { return ListView.separated( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), - padding: EdgeInsets.all(21.w), - itemCount: 3, + padding: EdgeInsets.all(16.w), + itemCount: 10, separatorBuilder: (_, __) => SizedBox(height: 12.h), itemBuilder: (context, index) { return Shimmer.fromColors( baseColor: Colors.grey[300]!, highlightColor: Colors.grey[100]!, child: Container( - height: 120.h, + height: 80.h, decoration: BoxDecoration( color: Colors.white, borderRadius: BorderRadius.circular(10.r), @@ -319,23 +358,16 @@ class _ComprehensiveCheckupPageState extends State { Widget build(BuildContext context) { return CollapsingListView( title: "Comprehensive Checkup".needTranslation, - requests: () { - Navigator.of(context).push(CustomPageRoute(page: CmcOrderDetailPage(), direction: AxisDirection.up)); - }, - // bottomChild sticks to the bottom of the scaffold; make it reactive using Consumer + history: () => Navigator.of(context).push(CustomPageRoute(page: CmcOrderDetailPage(), direction: AxisDirection.up)), bottomChild: Consumer( builder: (context, hmgServicesViewModel, child) { - // if still loading, don't show the bottom button if (hmgServicesViewModel.isCmcOrdersLoading || hmgServicesViewModel.isCmcServicesLoading) return SizedBox.shrink(); - final pendingOrder = _getPendingOrder(hmgServicesViewModel.cmcOrdersList); - - // show button only when there is no pending order and a service is selected if (pendingOrder == null && _selectedServiceId != null) { return SafeArea( top: false, child: Padding( - padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h), + padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 12.h), child: CustomButton( borderWidth: 0, text: "Next".needTranslation, @@ -348,7 +380,6 @@ class _ComprehensiveCheckupPageState extends State { ), ); } - return SizedBox.shrink(); }, ), @@ -357,12 +388,8 @@ class _ComprehensiveCheckupPageState extends State { if (hmgServicesViewModel.isCmcOrdersLoading || hmgServicesViewModel.isCmcServicesLoading) { return _buildLoadingShimmer(); } - final pendingOrder = _getPendingOrder(hmgServicesViewModel.cmcOrdersList); - if (pendingOrder != null) { - // Return the pending order card directly. The outer CollapsingListView's - // CustomScrollView/SliverList will provide proper scroll constraints. return _buildPendingOrderCard(pendingOrder); } else { return _buildServiceSelectionList(hmgServicesViewModel.cmcServicesList); diff --git a/lib/presentation/comprehensive_checkup/old_cmc_page.dart b/lib/presentation/comprehensive_checkup/old_cmc_page.dart deleted file mode 100644 index 042eab0..0000000 --- a/lib/presentation/comprehensive_checkup/old_cmc_page.dart +++ /dev/null @@ -1,195 +0,0 @@ -// import 'package:hmg_patient_app/core/enum/viewstate.dart'; -// import 'package:hmg_patient_app/core/model/AlHabibMedicalService/ComprehensiveMedicalCheckup/cmc_insert_pres_order_request_model.dart'; -// import 'package:hmg_patient_app/core/viewModels/AlHabibMedicalService/cmc_view_model.dart'; -// import 'package:hmg_patient_app/core/viewModels/project_view_model.dart'; -// import 'package:hmg_patient_app/pages/AlHabibMedicalService/ComprehensiveMedicalCheckup/NewCMC/new_cmc_step_three_page.dart'; -// import 'package:hmg_patient_app/uitl/translations_delegate_base.dart'; -// import 'package:hmg_patient_app/uitl/utils.dart'; -// import 'package:hmg_patient_app/uitl/utils_new.dart'; -// import 'package:hmg_patient_app/widgets/buttons/defaultButton.dart'; -// import 'package:hmg_patient_app/widgets/dragable_sheet.dart'; -// import 'package:hmg_patient_app/widgets/others/app_scaffold_widget.dart'; -// import 'package:hmg_patient_app/widgets/photo_view_page.dart'; -// import 'package:flutter/cupertino.dart'; -// import 'package:flutter/material.dart'; -// import 'package:flutter_svg/svg.dart'; -// import 'package:provider/provider.dart'; -// -// import 'new_cmc_step_tow_page.dart'; -// -// class NewCMCStepOnePage extends StatefulWidget { -// final CMCInsertPresOrderRequestModel cMCInsertPresOrderRequestModel; -// final Function changePageViewIndex; -// final CMCViewModel model; -// -// final double latitude; -// final double longitude; -// -// const NewCMCStepOnePage({Key? key, required this.cMCInsertPresOrderRequestModel, required this.model, required this.changePageViewIndex, required this.latitude, required this.longitude}) -// : super(key: key); -// -// @override -// _NewCMCStepOnePageState createState() => _NewCMCStepOnePageState(); -// } -// -// class _NewCMCStepOnePageState extends State { -// int selectedItem = 0; -// -// @override -// void initState() { -// super.initState(); -// } -// -// @override -// Widget build(BuildContext context) { -// ProjectViewModel projectViewModel = Provider.of(context); -// -// return AppScaffold( -// isShowAppBar: false, -// baseViewModel: widget.model, -// body: Column( -// children: [ -// Expanded( -// child: SingleChildScrollView( -// physics: BouncingScrollPhysics(), -// padding: EdgeInsets.all(21), -// child: Column( -// children: [ -// ListView.separated( -// physics: NeverScrollableScrollPhysics(), -// shrinkWrap: true, -// itemBuilder: (context, index) { -// return Row( -// children: [ -// Radio( -// value: num.tryParse(widget.model.cmcAllServicesList[index].serviceID!), -// activeColor: Colors.red[800], -// onChanged: (newValue) async { -// selectedItem = index; -// PatientERCMCInsertServicesList patientERCMCInsertServicesList = PatientERCMCInsertServicesList( -// price: widget.model.cmcAllServicesList[index].price, -// serviceID: widget.model.cmcAllServicesList[index].serviceID, -// selectedServiceName: widget.model.cmcAllServicesList[index].text, -// selectedServiceNameAR: widget.model.cmcAllServicesList[index].textN, -// recordID: 1, -// totalPrice: widget.model.cmcAllServicesList[index].priceTotal, -// vAT: widget.model.cmcAllServicesList[index].priceVAT); -// setState(() { -// widget.cMCInsertPresOrderRequestModel.patientERCMCInsertServicesList = [patientERCMCInsertServicesList]; -// }); -// // CMCGetItemsRequestModel cMCGetItemsRequestModel = new CMCGetItemsRequestModel(checkupType: newValue); -// // await widget.model.getCheckupItems(cMCGetItemsRequestModel: cMCGetItemsRequestModel); -// }, -// groupValue: widget.cMCInsertPresOrderRequestModel.patientERCMCInsertServicesList!.length > 0 -// ? int.parse(widget.cMCInsertPresOrderRequestModel.patientERCMCInsertServicesList![0].serviceID!) -// : 1), -// Expanded( -// child: Padding( -// padding: const EdgeInsets.only( -// left: 10, -// right: 10, -// top: 20, -// bottom: 20, -// ), -// child: Text( -// projectViewModel.isArabic ? widget.model.cmcAllServicesList[index].textN! : widget.model.cmcAllServicesList[index].text!, -// style: TextStyle( -// fontSize: 14, -// fontWeight: FontWeight.w600, -// letterSpacing: -0.45, -// ), -// ), -// ), -// ), -// ], -// ); -// }, -// separatorBuilder: (context, index) { -// return mDivider(Colors.grey); -// }, -// itemCount: widget.model.cmcAllServicesList.length), -// Stack( -// children: [ -// Image.asset( -// projectViewModel.isArabic ? "assets/images/cc_ar.png" : "assets/images/cc_en.png", -// width: double.infinity, -// ), -// Align( -// alignment: Alignment.topRight, -// child: Container( -// decoration: containerColorRadiusBorder( -// Colors.black.withOpacity(0.2), -// 1000, -// Colors.white, -// ), -// margin: EdgeInsets.all(12), -// child: IconButton( -// icon: SvgPicture.asset( -// "assets/images/new/ic_zoom.svg", -// color: Colors.white, -// ), -// padding: EdgeInsets.all(12), -// onPressed: () { -// showDraggableDialog(context, PhotoViewPage(projectViewModel.isArabic ? "assets/images/cc_ar.png" : "assets/images/cc_en.png")); -// }, -// ), -// ), -// ), -// ], -// ), -// ], -// ), -// ), -// ), -// Container( -// color: Colors.white, -// padding: EdgeInsets.only(top: 16, bottom: 16, right: 21, left: 21), -// child: DefaultButton( -// TranslationBase.of(context).next, -// () async { -// if (widget.cMCInsertPresOrderRequestModel.patientERCMCInsertServicesList!.length != 0 || widget.cMCInsertPresOrderRequestModel.patientERCMCInsertServicesList == null) { -// // int index = widget.model.cmcAllServicesList.length; -// PatientERCMCInsertServicesList patientERCMCInsertServicesList = new PatientERCMCInsertServicesList( -// price: widget.model.cmcAllServicesList[selectedItem].price, -// serviceID: widget.model.cmcAllServicesList[selectedItem].serviceID.toString(), -// selectedServiceName: widget.model.cmcAllServicesList[selectedItem].text, -// selectedServiceNameAR: widget.model.cmcAllServicesList[selectedItem].textN, -// recordID: 1, -// totalPrice: widget.model.cmcAllServicesList[selectedItem].priceTotal, -// vAT: widget.model.cmcAllServicesList[selectedItem].priceVAT, -// ); -// -// widget.cMCInsertPresOrderRequestModel.patientID = projectViewModel.user!.patientID; -// widget.cMCInsertPresOrderRequestModel.patientOutSA = projectViewModel.user!.outSA; -// -// widget.cMCInsertPresOrderRequestModel.patientERCMCInsertServicesList = [patientERCMCInsertServicesList]; -// navigateTo( -// context, -// NewCMCStepThreePage( -// cmcInsertPresOrderRequestModel: widget.cMCInsertPresOrderRequestModel, -// model: widget.model, -// ), -// ); -// // await widget.model.getCustomerInfo(); -// if (widget.model.state == ViewState.ErrorLocal) { -// Utils.showErrorToast(); -// } else { -// // navigateTo( -// // context, -// // NewCMCStepTowPage( -// // longitude: widget.longitude, -// // latitude: widget.latitude, -// // cmcInsertPresOrderRequestModel: widget.cMCInsertPresOrderRequestModel, -// // model: widget.model, -// // ), -// // ); -// } -// } -// }, -// ), -// ), -// ], -// ), -// ); -// } -// } diff --git a/lib/presentation/comprehensive_checkup/old_detail_page.dart b/lib/presentation/comprehensive_checkup/old_detail_page.dart deleted file mode 100644 index 27070fe..0000000 --- a/lib/presentation/comprehensive_checkup/old_detail_page.dart +++ /dev/null @@ -1,183 +0,0 @@ -// import 'package:hmg_patient_app/core/enum/viewstate.dart'; -// import 'package:hmg_patient_app/core/model/AlHabibMedicalService/ComprehensiveMedicalCheckup/GetCMCAllOrdersResponseModel.dart'; -// import 'package:hmg_patient_app/core/model/AlHabibMedicalService/HomeHealthCare/get_hhc_all_pres_orders_response_model.dart'; -// import 'package:hmg_patient_app/core/model/AlHabibMedicalService/HomeHealthCare/update_pres_oreder_request_model.dart'; -// import 'package:hmg_patient_app/core/viewModels/AlHabibMedicalService/cmc_view_model.dart'; -// import 'package:hmg_patient_app/core/viewModels/project_view_model.dart'; -// import 'package:hmg_patient_app/uitl/app_toast.dart'; -// import 'package:hmg_patient_app/uitl/date_uitl.dart'; -// import 'package:hmg_patient_app/uitl/gif_loader_dialog_utils.dart'; -// import 'package:hmg_patient_app/uitl/translations_delegate_base.dart'; -// import 'package:hmg_patient_app/uitl/utils.dart'; -// import 'package:hmg_patient_app/uitl/utils_new.dart'; -// import 'package:hmg_patient_app/widgets/buttons/defaultButton.dart'; -// import 'package:hmg_patient_app/widgets/buttons/secondary_button.dart'; -// import 'package:hmg_patient_app/widgets/data_display/text.dart'; -// import 'package:hmg_patient_app/widgets/dialogs/ConfirmWithMessageDialog.dart'; -// import 'package:hmg_patient_app/widgets/others/app_scaffold_widget.dart'; -// import 'package:flutter/material.dart'; -// import 'package:provider/provider.dart'; -// -// import 'Dialog/confirm_cancel_order_dialog.dart'; -// -// class OrdersLogDetailsPage extends StatelessWidget { -// final CMCViewModel model; -// -// const OrdersLogDetailsPage({Key ?key, required this.model}) : super(key: key); -// -// @override -// Widget build(BuildContext context) { -// ProjectViewModel projectViewModel = Provider.of(context); -// -// void showConfirmMessage(CMCViewModel model, GetCMCAllOrdersResponseModel order) { -// showDialog( -// context: context, -// builder: (cxt) => ConfirmWithMessageDialog( -// message: TranslationBase.of(context).cancelOrderMsg, -// onTap: () { -// UpdatePresOrderRequestModel updatePresOrderRequestModel = UpdatePresOrderRequestModel(presOrderID: order.iD, rejectionReason: "", presOrderStatus: 4, editedBy: 3); -// Future.delayed(new Duration(milliseconds: 300)).then((value) async { -// GifLoaderDialogUtils.showMyDialog(context); -// await model.updateCmcPresOrder(updatePresOrderRequestModel); -// if (model.state == ViewState.ErrorLocal) { -// Utils.showErrorToast(model.error); -// GifLoaderDialogUtils.hideDialog(context); -// } else { -// AppToast.showSuccessToast(message: TranslationBase.of(context).processDoneSuccessfully); -// await model.getCmcAllPresOrders(); -// GifLoaderDialogUtils.hideDialog(context); -// } -// }); -// }, -// )); -// return; -// } -// -// return AppScaffold( -// isShowAppBar: false, -// baseViewModel: model, -// body: model.cmcAllPresOrders.length > 0 ? ListView.separated( -// padding: EdgeInsets.all(21), -// physics: BouncingScrollPhysics(), -// itemBuilder: (context, index) { -// GetCMCAllOrdersResponseModel order = model.cmcAllPresOrders.reversed.toList()[index]; -// -// int status = order.statusId!; -// String _statusDisp = order.statusText!; -// late Color _color; -// if (status == 1) { -// //pending -// _color = Color(0xffCC9B14); -// } else if (status == 2) { -// //processing -// _color = Color(0xff2E303A); -// } else if (status == 3) { -// //completed -// _color = Color(0xff359846); -// } else if (status == 4 || status == 6 || status == 7) { -// //cancel // Rejected -// _color = Color(0xffD02127); -// } -// return Container( -// decoration: BoxDecoration( -// color: _color, -// borderRadius: BorderRadius.all( -// Radius.circular(10.0), -// ), -// boxShadow: [ -// BoxShadow( -// color: Color(0xff000000).withOpacity(.05), -// blurRadius: 27, -// offset: Offset(0, -3), -// ), -// ], -// ), -// child: Container( -// margin: EdgeInsets.only(left: projectViewModel.isArabic ? 0 : 6, right: projectViewModel.isArabic ? 6 : 0), -// padding: EdgeInsets.symmetric(vertical: 14, horizontal: 12), -// decoration: BoxDecoration( -// color: Colors.white, -// border: Border.all(color: Colors.white, width: 1), -// borderRadius: BorderRadius.only( -// bottomRight: projectViewModel.isArabic ? Radius.circular(0) : Radius.circular(10.0), -// topRight: projectViewModel.isArabic ? Radius.circular(0) : Radius.circular(10.0), -// bottomLeft: projectViewModel.isArabic ? Radius.circular(10.0) : Radius.circular(0), -// topLeft: projectViewModel.isArabic ? Radius.circular(10.0) : Radius.circular(0), -// ), -// ), -// // clipBehavior: Clip.antiAlias, -// child: Row( -// crossAxisAlignment: CrossAxisAlignment.start, -// children: [ -// Expanded( -// child: Column( -// mainAxisAlignment: MainAxisAlignment.start, -// crossAxisAlignment: CrossAxisAlignment.start, -// children: [ -// Text( -// _statusDisp, -// style: TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: _color, letterSpacing: -0.4, height: 16 / 10), -// ), -// SizedBox(height: 6), -// Text( -// '${TranslationBase.of(context).requestID}: ${order.iD}', -// style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Color(0xff2E303A), letterSpacing: -0.64, height: 25 / 16), -// ), -// Row( -// crossAxisAlignment: CrossAxisAlignment.start, -// children: [ -// Text( -// TranslationBase.of(context).hospital + ": ", -// style: TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xff575757), letterSpacing: -0.4, height: 16 / 10), -// ), -// Expanded( -// child: Text( -// // !projectViewModel.isArabic ? order.nearestProjectDescription.trim().toString() : order.nearestProjectDescriptionN.toString(), -// order.projectName != null ? order.projectName!.trim().toString() : "", -// style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: Color(0xff2B353E), letterSpacing: -0.56), -// ), -// ), -// ], -// ) -// ], -// ), -// ), -// Column( -// mainAxisAlignment: MainAxisAlignment.spaceBetween, -// crossAxisAlignment: CrossAxisAlignment.end, -// children: [ -// Text( -// DateUtil.getDayMonthYearDateFormatted(DateTime.tryParse(order.created!)!), -// style: TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xff2B353E), letterSpacing: -0.4, height: 16 / 10), -// ), -// SizedBox(height: 12), -// if (order.statusId == 1 || order.statusId == 2) -// InkWell( -// onTap: () { -// showConfirmMessage(model, order); -// }, -// child: Container( -// padding: EdgeInsets.symmetric(vertical: 8, horizontal: 14), -// decoration: BoxDecoration( -// color: Color(0xffD02127), -// border: Border.all(color: Colors.white, width: 1), -// borderRadius: BorderRadius.circular(10), -// ), -// child: Text( -// TranslationBase.of(context).cancel_nocaps, -// style: TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: Colors.white, letterSpacing: -0.4), -// ), -// ), -// ), -// ], -// ), -// ], -// ), -// ), -// ); -// }, -// separatorBuilder: (context, index) => SizedBox(height: 12), -// itemCount: model.cmcAllPresOrders.length) : getNoDataWidget(context), -// ); -// } -// } diff --git a/lib/presentation/comprehensive_checkup/widgets/cmc_hospital_bottom_sheet_body.dart b/lib/presentation/comprehensive_checkup/widgets/cmc_hospital_bottom_sheet_body.dart new file mode 100644 index 0000000..98e91b8 --- /dev/null +++ b/lib/presentation/comprehensive_checkup/widgets/cmc_hospital_bottom_sheet_body.dart @@ -0,0 +1,111 @@ +import 'package:easy_localization/easy_localization.dart'; +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/enums.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/features/hmg_services/hmg_services_view_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/presentation/comprehensive_checkup/widgets/cmc_hospital_list_item.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/input_widget.dart'; +import 'package:provider/provider.dart'; +import 'package:shimmer/shimmer.dart'; + +class CmcHospitalBottomSheetBody extends StatelessWidget { + final Function(HospitalsModel) onHospitalSelected; + + const CmcHospitalBottomSheetBody({super.key, required this.onHospitalSelected}); + + Widget _buildLoadingShimmer() { + return ListView.separated( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: 4, + separatorBuilder: (_, __) => SizedBox(height: 12.h), + itemBuilder: (context, index) { + return Shimmer.fromColors( + baseColor: Colors.grey[300]!, + highlightColor: Colors.grey[100]!, + child: Container( + height: 80.h, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(10.r), + ), + ), + ); + }, + ); + } + + @override + Widget build(BuildContext context) { + final appState = getIt.get(); + final bool isArabic = appState.isArabic(); + final bool isLocationEnabled = (appState.userLat != 0) && (appState.userLong != 0); + + return Consumer( + builder: (BuildContext context, HmgServicesViewModel hmgServicesViewModel, Widget? child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Choose your preferred hospital for the service".needTranslation.toText14( + weight: FontWeight.w400, + color: AppColors.greyTextColor, + letterSpacing: -0.4, + ), + SizedBox(height: 16.h), + TextInputWidget( + labelText: LocaleKeys.search.tr(), + hintText: LocaleKeys.searchHospital.tr(), + onChange: (value) { + hmgServicesViewModel.filterHospitalsByString(value ?? '', isArabic); + }, + isEnable: true, + prefix: null, + autoFocus: false, + isBorderAllowed: false, + keyboardType: TextInputType.text, + isAllowLeadingIcon: true, + selectionType: SelectionTypeEnum.search, + padding: EdgeInsets.symmetric(vertical: ResponsiveExtension(10).h, horizontal: ResponsiveExtension(15).h), + ), + ], + ), + SizedBox(height: 8.h), + SizedBox( + height: MediaQuery.of(context).size.height * 0.4, + child: hmgServicesViewModel.isHospitalListLoading + ? _buildLoadingShimmer() + : hmgServicesViewModel.filteredHospitalsList.isEmpty + ? Center( + child: "No hospitals Found".needTranslation.toText16(weight: FontWeight.w500, color: AppColors.greyTextColor), + ) + : ListView.separated( + itemCount: hmgServicesViewModel.filteredHospitalsList.length, + separatorBuilder: (context, index) => SizedBox(height: 12.h), + itemBuilder: (context, index) { + final hospital = hmgServicesViewModel.filteredHospitalsList[index]; + return CmcHospitalListItem( + hospital: hospital, + isLocationEnabled: isLocationEnabled, + onPress: () { + hmgServicesViewModel.setSelectedHospital(hospital); + onHospitalSelected(hospital); + }, + ); + }, + ), + ), + ], + ); + }, + ); + } +} diff --git a/lib/presentation/comprehensive_checkup/widgets/cmc_hospital_list_item.dart b/lib/presentation/comprehensive_checkup/widgets/cmc_hospital_list_item.dart new file mode 100644 index 0000000..39d6d7c --- /dev/null +++ b/lib/presentation/comprehensive_checkup/widgets/cmc_hospital_list_item.dart @@ -0,0 +1,126 @@ +import 'dart:developer'; + +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/app_state.dart'; +import 'package:hmg_patient_app_new/core/dependencies.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; + +class CmcHospitalListItem extends StatelessWidget { + final HospitalsModel hospital; + final VoidCallback onPress; + final bool isLocationEnabled; + + const CmcHospitalListItem({ + super.key, + required this.hospital, + required this.onPress, + this.isLocationEnabled = false, + }); + + @override + Widget build(BuildContext context) { + final appState = getIt.get(); + final bool isArabic = appState.isArabic(); + final String hospitalName = isArabic ? (hospital.nameN ?? hospital.name ?? '') : (hospital.name ?? ''); + + return InkWell( + onTap: onPress, + child: DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: false, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 8.h, + children: [ + _buildHospitalName(hospitalName), + _buildDistanceInfo(), + ], + ), + ), + Transform.flip( + flipX: isArabic, + child: Utils.buildSvgWithAssets( + icon: AppAssets.forward_arrow_icon_small, + iconColor: AppColors.blackColor, + width: 18.h, + height: 13.h, + fit: BoxFit.contain, + ), + ), + ], + ).paddingSymmetrical(16.h, 16.h), + ), + ); + } + + Widget _buildHospitalName(String hospitalName) { + return Row( + children: [ + Utils.buildSvgWithAssets( + icon: (hospital.isHMC == true) ? AppAssets.hmc : AppAssets.hmg, + ).paddingOnly(right: 10), + Expanded( + child: Text( + hospitalName, + style: TextStyle( + fontWeight: FontWeight.w600, + fontSize: 16, + color: AppColors.blackColor, + ), + ), + ) + ], + ); + } + + Widget _buildDistanceInfo() { + log("hospital: ${hospital.distanceInKilometers}"); + final distanceText = hospital.distanceInKilometers != null ? hospital.distanceInKilometers!.toStringAsFixed(1) : "0"; + + return Row( + spacing: 4.w, + children: [ + Visibility( + visible: (hospital.distanceInKilometers != null && hospital.distanceInKilometers! > 0), + child: AppCustomChipWidget( + labelText: "$distanceText km".needTranslation, + icon: AppAssets.location_red, + iconColor: AppColors.errorColor, + backgroundColor: AppColors.secondaryLightRedColor, + textColor: AppColors.errorColor, + ), + ), + Visibility( + visible: (hospital.distanceInKilometers == null || hospital.distanceInKilometers == 0), + child: AppCustomChipWidget( + labelText: " Distance not available".needTranslation, + textColor: AppColors.blackColor, + ), + ), + // Visibility( + // visible: !isLocationEnabled, + // child: AppCustomChipWidget( + // labelText: "Location turned off".needTranslation, + // deleteIcon: AppAssets.location_unavailable, + // deleteIconSize: Size(9.w, 12.h), + // textColor: AppColors.blackColor, + // ), + // ), + ], + ); + } +} diff --git a/lib/presentation/comprehensive_checkup/widgets/cmc_ui_selection_helper.dart b/lib/presentation/comprehensive_checkup/widgets/cmc_ui_selection_helper.dart new file mode 100644 index 0000000..908aac9 --- /dev/null +++ b/lib/presentation/comprehensive_checkup/widgets/cmc_ui_selection_helper.dart @@ -0,0 +1,110 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_view_model.dart'; +import 'package:hmg_patient_app_new/presentation/comprehensive_checkup/widgets/cmc_hospital_bottom_sheet_body.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:provider/provider.dart'; +import 'package:easy_localization/easy_localization.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/route_extensions.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/req_models/order_update_req_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_all_orders_resp_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; + +class CmcUiSelectionHelper { + static void showHospitalSelectionBottomSheet({ + required BuildContext context, + required Function(dynamic) onHospitalSelected, + }) { + final hmgServicesViewModel = context.read(); + + showCommonBottomSheetWithoutHeight( + context, + title: "Select Hospital".needTranslation, + child: CmcHospitalBottomSheetBody( + onHospitalSelected: (hospital) { + hmgServicesViewModel.setSelectedHospitalForOrder(hospital); + onHospitalSelected(hospital); + }, + ), + ); + } + + static void showCancelConfirmationDialog({ + required BuildContext context, + required GetCMCAllOrdersResponseModel order, + }) { + final HmgServicesViewModel hmgServicesViewModel = context.read(); + + return showCommonBottomSheetWithoutHeight( + title: LocaleKeys.notice.tr(context: context), + context, + child: Utils.getWarningWidget( + loadingText: "Are you sure you want to cancel this order?".needTranslation, + isShowActionButtons: true, + onCancelTap: () { + Navigator.pop(context); + }, + onConfirmTap: () async { + Navigator.pop(context); + LoaderBottomSheet.showLoader(); + + final requestModel = OrderUpdateRequestModel( + presOrderID: order.iD, + rejectionReason: "Cancelled by user", + presOrderStatus: 4, // Cancelled status + editedBy: 3, + ); + + await hmgServicesViewModel.updateCmcPresOrder( + requestModel: requestModel, + onSuccess: (_) async { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Padding( + padding: EdgeInsets.all(16.w), + child: Column( + children: [ + Utils.getSuccessWidget(loadingText: "Order has been cancelled successfully".needTranslation), + SizedBox(height: 24.h), + Row( + children: [ + Expanded( + child: CustomButton( + height: 56.h, + text: LocaleKeys.ok.tr(), + onPressed: () { + context.pop(); + context.pop(); + hmgServicesViewModel.getAllCmcOrders(); + }, + textColor: AppColors.whiteColor, + ), + ), + ], + ), + ], + ), + ), + isCloseButtonVisible: false, + isDismissible: false, + isFullScreen: false, + ); + }, + onError: (error) { + LoaderBottomSheet.hideLoader(); + }, + ); + }, + ), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } +} diff --git a/lib/presentation/contact_us/contact_us.dart b/lib/presentation/contact_us/contact_us.dart index 6890fb4..d7ea9c5 100644 --- a/lib/presentation/contact_us/contact_us.dart +++ b/lib/presentation/contact_us/contact_us.dart @@ -9,7 +9,9 @@ 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'; import 'package:hmg_patient_app_new/presentation/contact_us/live_chat_page.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; @@ -51,7 +53,17 @@ class ContactUs extends StatelessWidget { AppAssets.checkin_location_icon, 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( + page: FeedbackPage(), + ), + ); + }), SizedBox(height: 16.h), checkInOptionCard( AppAssets.checkin_location_icon, @@ -59,6 +71,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/feedback_page.dart b/lib/presentation/contact_us/feedback_page.dart new file mode 100644 index 0000000..db7c218 --- /dev/null +++ b/lib/presentation/contact_us/feedback_page.dart @@ -0,0 +1,400 @@ +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, listen: false); + medicalFileViewModel = Provider.of(context, listen: false); + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + 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, + 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 { + 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, + 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: 16.r, + 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), + (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); + } + }, + ), + ); + }, + ), + ), + 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: subjectTextController, + 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: messageTextController, + isEnable: true, + prefix: null, + autoFocus: false, + isBorderAllowed: false, + isMultiline: true, + keyboardType: TextInputType.text, + padding: EdgeInsets.symmetric( + vertical: ResponsiveExtension(10).h, + horizontal: ResponsiveExtension(15).h, + ), + ), + 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); + contactUsViewModel.addFeedbackAttachment(image); + }, + ); + }, + 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, + ), + 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 { + return Container(); + } + } +} diff --git a/lib/presentation/contact_us/live_chat_page.dart b/lib/presentation/contact_us/live_chat_page.dart index aced678..7cbdee3 100644 --- a/lib/presentation/contact_us/live_chat_page.dart +++ b/lib/presentation/contact_us/live_chat_page.dart @@ -1,27 +1,176 @@ 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: 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/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/emergency_services/RRT/rrt_map_screen.dart b/lib/presentation/emergency_services/RRT/rrt_map_screen.dart new file mode 100644 index 0000000..9e11c31 --- /dev/null +++ b/lib/presentation/emergency_services/RRT/rrt_map_screen.dart @@ -0,0 +1,633 @@ +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/map.dart'; +import 'package:provider/provider.dart'; + +import '../../../widgets/common_bottom_sheet.dart'; + +class RrtMapScreen extends StatelessWidget { + @override + Widget build(BuildContext context) { + return Scaffold( + floatingActionButton: Visibility( + visible: context.watch().bottomSheetType == + BottomSheetType.FIXED, + child: 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: ExpandableBottomSheet( + bottomSheetType: + context.watch().bottomSheetType, + children: { + BottomSheetType.EXPANDED: ExpanedBottomSheet(context), + BottomSheetType.FIXED: FixedBottomSheet(context), + }, + ), + body: Stack( + children: [ + if (context.read().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(() { + context + .read() + .flushPickupInformation(); + Navigator.pop(context); + }), + ).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: [ + "Select Location".needTranslation.toText21( + weight: FontWeight.w600, + color: AppColors.textColor, + ), + "Please select the location".needTranslation.toText12( + fontWeight: FontWeight.w500, + color: AppColors.greyTextColor, + ) + ], + ), + CustomButton( + text: "Submit Request".needTranslation, + onPressed: () { + LocationViewModel locationViewModel = context.read(); + GeocodeResponse? response = locationViewModel.geocodeResponse; + 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), + ), + ), + ], + ), + ], + ), + ); + } + + Widget ExpanedBottomSheet(BuildContext context) { + return GestureDetector( + onVerticalDragUpdate: (details){ + if(details.delta.dy>0){ + context.read().updateBottomSheetState(BottomSheetType.FIXED); + } + }, + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.scaffoldBgColor, + customBorder: BorderRadius.only( + topLeft: Radius.circular(24.h), + topRight: Radius.circular(24.h), + ), + ), + child: Column( + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 16.h, + children: [ + + hospitalAndPickUpSection(context), + + + ], + ).paddingOnly(top: 24.h, bottom: 32.h,left: 24.h, right: 24.h), + + bottomPriceContent(context) + ], + ), + ), + ], + ), + ); + } + + locationsSections(BuildContext context) { + return DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + ), + child: Column(mainAxisSize: MainAxisSize.min, children: [ + ListView.separated( + separatorBuilder: (_, __) => Column( + children: [ + SizedBox(height: 16.h), + Divider( + color: AppColors.bottomNAVBorder, + height: 1, + thickness: 1, + ), + SizedBox(height: 16.h), + ], + ), + shrinkWrap: true, + itemCount: 3, + itemBuilder: (__, index) { + if (index == + 2) // todo means the end of the list so handle as per the viewmodel + { + return CustomButton( + height: 40.h, + backgroundColor: AppColors.lightRedButtonColor, + borderColor: Colors.transparent, + text: "Add new address".needTranslation, + textColor: AppColors.primaryRedColor, + iconColor: AppColors.primaryRedColor, + onPressed: () {}, + icon: AppAssets.add_icon); + } else { + return AddressItem( + isSelected: index == 0, + address: + "Flat No 301, Building No 12, Palm Spring Apartment, Sector 45, Gurugram, Haryana 122003", + title: index == 0 + ? "Home".needTranslation + : "Work".needTranslation, + onTap: () {}, + ); + } + }) + ]).paddingAll(16.h), + ); + } + + hospitalAndPickUpSection(BuildContext context) { + return DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.r, + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 16.h, + children: [ + // Row( + // children: [ + // hospitalAndPickUpItemContent( + // title: "Select Hospital".needTranslation, + // subTitle: "hospital".needTranslation, + // leadingIcon: AppAssets.hospital, + // ), + // Utils.buildSvgWithAssets(icon: AppAssets.down_cheveron, + // width: 24.h, height: 24.h) + // .paddingAll(16.h) + // ], + // ).onPress((){ + // showHospitalBottomSheet(context); + // }), + // SizedBox(height: 16.h), + // Divider( + // color: AppColors.bottomNAVBorder, + // height: 1, + // thickness: 1, + // ), + // SizedBox(height: 16.h), + + Row( + children: [ + hospitalAndPickUpItemContent( + title: "Pick".needTranslation, + subTitle: "Inside the home".needTranslation, + leadingIcon: AppAssets.pickup_bed, + ), + CustomSwitch( + value: context + .watch() + .pickupFromInsideTheLocation, + onChanged: (value){ + context + .read() + .updateThePickupPlaceFromLocation(value); + }, + ) + ], + ), + + Row( + children: [ + hospitalAndPickUpItemContent( + title: '', + subTitle: "Have any appointment".needTranslation, + leadingIcon: AppAssets.appointment_checkin_icon, + ), + CustomSwitch( + value: context + .watch() + .haveAnAppointment, + onChanged: (value) async { + // if (value) { + // openAppointmentList(context); + // } + await context.read() + .updateAppointment(value); + if (context.read().appointments?.isNotEmpty == true) { + openAppointmentList(context); + } + }, + ) + ], + ), + ], + ).paddingAll(16.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), + ); + } + + hospitalAndPickUpItemContent({ + required String title, + required String subTitle, + required String leadingIcon, + }) { + return Expanded( + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + this.leadingIcon(leadingIcon), + Expanded( + child: Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.start, + mainAxisAlignment: MainAxisAlignment.center, + children: [ + Visibility( + visible: title.isNotEmpty, + child: Column( + children: [ + title.toText12( + color: AppColors.greyTextColor, + fontWeight: FontWeight.w500, + ), + SizedBox(height: 2.h), + ], + ), + ), + subTitle.toText14( + color: AppColors.textColor, + weight: FontWeight.w500, + ), + ], + ), + ), + ], + ), + ); + } + + bottomPriceContent(BuildContext context) { + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.scaffoldBgColor, + customBorder: BorderRadius.only( + topLeft: Radius.circular(24.h), + topRight: Radius.circular(24.h), + ), + hasShadow: true + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 12.h, + children: [ + Row( + children: [ + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 4.h, + children: [ + "Total amount to pay".needTranslation.toText18( + weight: FontWeight.w600, + color: AppColors.textColor, + ), + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.warning, + height: 18.h, width: 18.h), + SizedBox(width: 4.h,), + "Amount will be paid at the hospital" + .needTranslation + .toText12( + fontWeight: FontWeight.w500, + color: AppColors.greyTextColor, + ), + ], + ) + ], + ), + ), + Utils.getPaymentAmountWithSymbol( + (Utils.formatNumberToInternationalFormat(context + .read() + .getTotalPrice() ?? + 0)) + .toText24( + fontWeight: FontWeight.w600, + color: AppColors.textColor, + letterSpacing: -2), + AppColors.blackColor, + 17.h) + + // Utils.getPaymentAmountWithSymbol2(context.read().selectedTransportOption?.priceTotal??"0", letterSpacing: -2) + ], + ), + CustomButton( + text: "Submit Request".needTranslation, + onPressed: () { + LocationViewModel locationViewModel = context.read(); + GeocodeResponse? response = locationViewModel.geocodeResponse; + PlaceDetails? placeDetails = locationViewModel.placeDetails; + PlacePrediction? placePrediction = locationViewModel.selectedPrediction; + context.read().submitAmbulanceRequest(response?.results.first, placeDetails, placePrediction); + }) + ], + ).paddingOnly(top: 24.h, bottom: 12.h, left: 24.h, right: 24.h), + ), + ], + ); + } + + showHospitalBottomSheet(BuildContext context){ + showCommonBottomSheetWithoutHeight( + title: + LocaleKeys.selectHospital.tr(), + context, + child: Consumer( + builder:(_,vm,__)=> HospitalBottomSheetBody( + searchText: vm.searchController, + displayList: vm.displayList, + onFacilityClicked: (value) { + vm.setSelectedFacility(value); + vm.getDisplayList(); + }, + onHospitalClicked: (hospital) { + Navigator.pop(context); + vm.setSelectedHospital(hospital); + }, + onHospitalSearch: (value) { + vm.searchHospitals(value ?? ""); + }, + selectedFacility: + vm.selectedFacility, + hmcCount: vm.hmcCount, + hmgCount: vm.hmgCount, + ), + ), + isFullScreen: false, + isCloseButtonVisible: true, + hasBottomPadding: false, + backgroundColor: AppColors.bottomSheetBgColor, + callBackFunc: () { + context.read().clearSearchText(); + }, + ); + } + + ///it will show the places field first and then hospital field + PlaceFirstThanHospitalField(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 16.h, + children: [ + textPlaceInput(context), + hospitalField(context), + ], + ).paddingOnly(right: 24.h, left: 24.h); + } + + HospitalFieldFirstThanPlaces(BuildContext context) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 16.h, + children: [hospitalField(context), textPlaceInput(context)], + ).paddingOnly(right: 24.h, left: 24.h); + } + + textPlaceInput(context) { + return Consumer(builder: (_, vm, __) { + print( + "the data is ${vm.geocodeResponse?.results.first.formattedAddress ?? vm.selectedPrediction?.description}"); + 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: () {}, + ); + } + + hospitalField(BuildContext context) { + return DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, borderRadius: 12.h), + child: Row( + children: [ + hospitalAndPickUpItemContent( + title: "Select Hospital".needTranslation, + subTitle: context + .read() + .getSelectedHospitalName() ?? + "Select Hospital".needTranslation, + leadingIcon: AppAssets.hospital, + ), + Utils.buildSvgWithAssets( + icon: AppAssets.down_cheveron, width: 24.h, height: 24.h) + .paddingAll(16.h) + ], + ).onPress(() { + print("the item is clicked"); + showHospitalBottomSheet(context); + }).paddingSymmetrical( + 10.w, + 12.h, + ), + ); + } + + void openAppointmentList(BuildContext context) { + showCommonBottomSheetWithoutHeight( + title: "Select Appointment".needTranslation, + context, + child: SizedBox( + height: MediaQuery.sizeOf(context).height * .5, + child: AppointmentBottomSheet( + list: context.read().appointments!, + onAppointmentSelection: (appointment) { + Navigator.pop(context); + context.read().setSelectedAppointment(appointment); + }), + ), + isFullScreen: false, + isCloseButtonVisible: true, + hasBottomPadding: false, + backgroundColor: AppColors.bottomSheetBgColor, + callBackFunc: () { + context.read().checkHasAppointment(); + }, + ); + // ); + } +} diff --git a/lib/presentation/emergency_services/RRT/rrt_request_type_select.dart b/lib/presentation/emergency_services/RRT/rrt_request_type_select.dart index 4d22610..d628c1d 100644 --- a/lib/presentation/emergency_services/RRT/rrt_request_type_select.dart +++ b/lib/presentation/emergency_services/RRT/rrt_request_type_select.dart @@ -1,9 +1,17 @@ +import 'package:easy_localization/easy_localization.dart' show tr, StringTranslateExtension; import 'package:flutter/cupertino.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/resp_model/rrt_procedures_response_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; import 'package:hmg_patient_app_new/widgets/radio/custom_radio_button.dart'; import 'package:provider/provider.dart'; @@ -13,34 +21,259 @@ class RrtRequestTypeSelect extends StatelessWidget { @override Widget build(BuildContext context) { return Consumer(builder: (context, emergencyServicesVM, child) { + print("the checkbox is ${emergencyServicesVM.agreedToTermsAndCondition}"); return Column( children: [ - Container( - padding: EdgeInsets.all(16.h), - height: 200.h, + Column( + children: [ + "Rapid Response Team (RRT) options".needTranslation.toText20(color: AppColors.textColor, isBold: true), + SizedBox( + height: 16.h, + ), + DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: Colors.white, + borderRadius: 20.r, + ), + child: OptionSelection(context)), + SizedBox( + height: 24.h, + ), + termsAndCondition(context, emergencyServicesVM), + ], + ).paddingSymmetrical(24.w, 0.h), + bottomPriceContent(context, emergencyServicesVM) + ], + ); + }); + } + + bottomPriceContent(BuildContext context, EmergencyServicesViewModel emergencyServicesVM) { + if (emergencyServicesVM.selectedRRTProcedure == null) return SizedBox.shrink(); + return Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + DecoratedBox( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, - borderRadius: 24.h, - ), + color: AppColors.scaffoldBgColor, + customBorder: BorderRadius.only( + topLeft: Radius.circular(24.h), + topRight: Radius.circular(24.h), + ), + hasShadow: true), child: Column( crossAxisAlignment: CrossAxisAlignment.start, + spacing: 12.h, children: [ Row( + mainAxisAlignment: MainAxisAlignment.end, children: [ - CustomRadioOption( - value: "", - groupValue: "", - onChanged: (value) {}, - text: "Home Visit Emergency", + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 12.h, + children: [ + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + "Total amount to pay".needTranslation.toText18( + weight: FontWeight.w600, + color: AppColors.textColor, + ), + Utils.getPaymentAmountWithSymbol( + (Utils.formatNumberToInternationalFormat(context.read().selectedRRTProcedure?.patientShare ?? 0)) + .toText24(fontWeight: FontWeight.w600, color: AppColors.textColor, letterSpacing: -2), + AppColors.blackColor, + 17.h) + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.warning, height: 18.h, width: 18.h), + SizedBox( + width: 4.h, + ), + "Amount will be paid at the hospital".needTranslation.toText11( + color: AppColors.greyTextColor, + ), + ], + ), + Row( + children: [ + "+ VAT 15%(".needTranslation.toText12( + fontWeight: FontWeight.w500, + color: AppColors.greyTextColor, + ), + "${emergencyServicesVM.selectedRRTProcedure?.patientTaxAmount})".needTranslation.toText14( + weight: FontWeight.w600, + color: AppColors.greyTextColor, + ), + ], + ), + ], + ) + ], + ), + ), + + // Utils.getPaymentAmountWithSymbol2(context.read().selectedTransportOption?.priceTotal??"0", letterSpacing: -2) + ], + ), + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Row( + spacing: 6.w, + children: [ + Image.asset(AppAssets.mada, width: 24.h, height: 24.h), + Image.asset(AppAssets.visa, width: 24.h, height: 24.h), + Image.asset(AppAssets.Mastercard, width: 24.h, height: 24.h), + Image.asset(AppAssets.apple_pay, width: 24.h, height: 24.h), + ], + ), + Column( + children: [ + Divider( + color: AppColors.dividerColor, + thickness: 1.h, + ), + Utils.getPaymentAmountWithSymbol( + (Utils.formatNumberToInternationalFormat(context.read().selectedRRTProcedure?.patientShareWithTax ?? 0)) + .toText24(fontWeight: FontWeight.w600, color: AppColors.textColor, letterSpacing: -2), + AppColors.blackColor, + 17.h), + ], ) ], ), + CustomButton(text: LocaleKeys.next.tr(), onPressed: () { + Navigator.pop(context); + emergencyServicesVM.openRRT(); + }) ], - ), + ).paddingAll(24.h), ), - SizedBox(height: 32.h), ], ); - }); + } + + Widget OptionSelection(BuildContext context) { + return Selector?, RRTProceduresResponseModel?)>( + // Select both the list and the currently selected procedure + selector: (context, viewModel) => (viewModel.RRTProceduresList, viewModel.selectedRRTProcedure), + builder: (context, data, child) { + final procedureList = data.$1; + final selectedProcedure = data.$2; + + // Handle loading or empty state + if (procedureList == null || procedureList.isEmpty) { + return const SizedBox.shrink(); + } + + return ListView.separated( + shrinkWrap: true, + padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 16.w), + // Important for ListView inside a Column + physics: const NeverScrollableScrollPhysics(), + // If the parent is scrollable + itemCount: procedureList.length, + separatorBuilder: (_, __) => Divider(thickness: 1.h, color: AppColors.dividerColor), + itemBuilder: (_, index) { + final procedure = procedureList[index]; + + final indexOfSelectedItem = selectedProcedure != null ? procedureList.indexOf(selectedProcedure) : -1; + return RadioListTile( + title: (procedure.procedureName ?? "").toText16(color: AppColors.textColor, weight: FontWeight.w500), + value: index, + fillColor: WidgetStateProperty.resolveWith((states) { + if (states.contains(WidgetState.selected)) { + return AppColors.primaryRedColor; + } + return Color(0xffEEEEEE); + }), + contentPadding: EdgeInsets.zero, + groupValue: indexOfSelectedItem, + onChanged: (int? newValue) { + context.read().setSelectedRRTProcedure(procedure); + }, + ); + // return Row( + // children: [ + // Radio( + // // Specify the type for the Radio button + // value: procedure.procedureID ?? "", + // groupValue: selectedProcedure?.procedureID, + // // Compare with the selected procedure's ID + // activeColor: AppColors.primaryRedColor, + // fillColor: MaterialStateProperty.all(AppColors.primaryRedColor), + // onChanged: (value) { + // // The onChanged of the Radio button should handle the state update + // context.read().setSelectedRRTProcedure(procedure); + // }, + // ), + // Expanded( + // // Use Expanded to allow text to wrap if it's too long + // child: (procedure.procedureName ?? "").toText12(color: AppColors.textColor), + // ) + // ], + }, + ); + }, + ); + } + + termsAndCondition( + BuildContext context, + EmergencyServicesViewModel emergencyServicesVM, + ) { + return Row( + children: [ + SizedBox( + height: 18.h, + width: 18.w, + child: Checkbox( + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + value: emergencyServicesVM.agreedToTermsAndCondition, + checkColor: AppColors.whiteColor, + fillColor: MaterialStateProperty.resolveWith((Set states) { + print("the state is ${states}"); + if (states.contains(WidgetState.selected)) { + return AppColors.errorColor; + } + return AppColors.whiteColor; + }), + onChanged: (value) { + emergencyServicesVM.setTermsAndConditions(value ?? false); + }), + ), + Row( + spacing: 4.w, + children: [ + SizedBox.shrink(), + LocaleKeys.agreeTo.tr().toText16(color: AppColors.textColor, weight: FontWeight.w500), + LocaleKeys.termsConditoins.tr().toText16(color: AppColors.errorColor, isUnderLine: true, weight: FontWeight.w500).onPress(() { + emergencyServicesVM.getTermsAndConditions(); + }), + ], + ), + ], + ); + + // CheckboxListTile( + // title: Row( + // children: [ + // LocaleKeys.agreeTo.tr().toText12(color: AppColors.textColor), + // LocaleKeys.termsConditoins.tr().toText12(color: AppColors.errorColor, isUnderLine: true), + // ], + // ), + // value: emergencyServicesVM.agreedToTermsAndCondition, + // onChanged: (value) { + // emergencyServicesVM.setTermsAndConditions(value ?? false); + // }, + // ); } } diff --git a/lib/presentation/emergency_services/RRT/terms_and_condition.dart b/lib/presentation/emergency_services/RRT/terms_and_condition.dart new file mode 100644 index 0000000..8a50792 --- /dev/null +++ b/lib/presentation/emergency_services/RRT/terms_and_condition.dart @@ -0,0 +1,33 @@ +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_widget_from_html/flutter_widget_from_html.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/emergency_services/emergency_services_view_model.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 TermsAndCondition extends StatelessWidget { + final String termsAndCondition; + const TermsAndCondition({super.key, required this.termsAndCondition}); + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Column( + children: [ + + Expanded( + child: CollapsingListView( + title: "Terms And Condition".needTranslation, + child:DecoratedBox(decoration:RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 20.h, + hasShadow: true, + ),child: HtmlWidget(termsAndCondition).paddingAll(16.h)).paddingAll(12.h)))])); + } + +} \ No newline at end of file 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 5e4b885..fea52a3 100644 --- a/lib/presentation/emergency_services/call_ambulance/call_ambulance_page.dart +++ b/lib/presentation/emergency_services/call_ambulance/call_ambulance_page.dart @@ -1,5 +1,3 @@ -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'; @@ -14,10 +12,7 @@ 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_checkin_bottom_sheet.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/requesting_services_page.dart' show RequestingServicesPage; -import 'package:hmg_patient_app_new/presentation/emergency_services/call_ambulance/tracking_screen.dart' show TrackingScreen; 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'; @@ -29,7 +24,6 @@ import 'package:hmg_patient_app_new/widgets/expandable_bottom_sheet/model/Bottom 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/routes/custom_page_route.dart'; import 'package:provider/provider.dart'; import '../../../widgets/common_bottom_sheet.dart'; @@ -40,20 +34,13 @@ class CallAmbulancePage extends StatelessWidget { Widget build(BuildContext context) { return Scaffold( floatingActionButton: Visibility( - visible: context.watch().bottomSheetType == - BottomSheetType.FIXED, + visible: context.watch().bottomSheetType == BottomSheetType.FIXED, child: 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(); + 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(); }), ), ), @@ -62,8 +49,7 @@ class CallAmbulancePage extends StatelessWidget { mainAxisSize: MainAxisSize.min, children: [ ExpandableBottomSheet( - bottomSheetType: - context.watch().bottomSheetType, + bottomSheetType: context.watch().bottomSheetType, children: { BottomSheetType.EXPANDED: ExpanedBottomSheet(context), BottomSheetType.FIXED: FixedBottomSheet(context), @@ -73,42 +59,28 @@ class CallAmbulancePage extends StatelessWidget { ), body: Stack( children: [ - if (context.read().isGMSAvailable ) + if (context.read().isGMSAvailable) GMSMap( - currentLocation: - context.read().getGMSLocation(), - onCameraMoved: (value) => context - .read() - .handleGMSMapCameraMoved(value), - onCameraIdle: - context.read().handleOnCameraIdle, + currentLocation: context.read().getGMSLocation(), + onCameraMoved: (value) => context.read().handleGMSMapCameraMoved(value), + onCameraIdle: context.read().handleOnCameraIdle, myLocationEnabled: true, - inputController: - context.read().gmsController, + inputController: context.read().gmsController, showCenterMarker: true, ) else HMSMap( - currentLocation: - context.read().getHMSLocation(), - onCameraMoved: (value) => context - .read() - .handleHMSMapCameraMoved(value), - onCameraIdle: - context.read().handleOnCameraIdle, + currentLocation: context.read().getHMSLocation(), + onCameraMoved: (value) => context.read().handleHMSMapCameraMoved(value), + onCameraIdle: context.read().handleOnCameraIdle, myLocationEnabled: false, - inputController: - context.read().hmsController, + inputController: context.read().hmsController, showCenterMarker: true, ), Align( alignment: AlignmentDirectional.topStart, - child: Utils.buildSvgWithAssets( - icon: AppAssets.closeBottomNav, width: 32.h, height: 32.h) - .onPress(() { - context - .read() - .flushPickupInformation(); + child: Utils.buildSvgWithAssets(icon: AppAssets.closeBottomNav, width: 32.h, height: 32.h).onPress(() { + context.read().flushPickupInformation(); Navigator.pop(context); }), ).paddingOnly(top: 51.h, left: 24.h), @@ -119,8 +91,7 @@ class CallAmbulancePage extends StatelessWidget { Widget FixedBottomSheet(BuildContext context) { return GestureDetector( - onVerticalDragUpdate: (details){ - + onVerticalDragUpdate: (details) { // if(details.delta.dy<0){ // // context.read().updateBottomSheetState(BottomSheetType.EXPANDED); @@ -161,7 +132,7 @@ class CallAmbulancePage extends StatelessWidget { // ), // ), // ), - // .paddingOnly(right: 24.h, bottom: 24.h), + // .paddingOnly(right: 24.h, bottom: 24.h), Column( crossAxisAlignment: CrossAxisAlignment.start, mainAxisSize: MainAxisSize.min, @@ -189,9 +160,7 @@ class CallAmbulancePage extends StatelessWidget { weight: FontWeight.w600, color: AppColors.textColor, ), - " Please select the details of pickup" - .needTranslation - .toText12( + " Please select the details of pickup".needTranslation.toText12( fontWeight: FontWeight.w500, color: AppColors.greyTextColor, ) @@ -200,9 +169,7 @@ class CallAmbulancePage extends StatelessWidget { CustomButton( text: "Select Details".needTranslation, onPressed: () { - context - .read() - .updateBottomSheetState(BottomSheetType.EXPANDED); + context.read().updateBottomSheetState(BottomSheetType.EXPANDED); }) ], ).paddingOnly(top: 24.h, bottom: 32.h, left: 24.h, right: 24.h), @@ -217,11 +184,11 @@ class CallAmbulancePage extends StatelessWidget { Widget ExpanedBottomSheet(BuildContext context) { return GestureDetector( - onVerticalDragUpdate: (details){ - if(details.delta.dy>0){ - context.read().updateBottomSheetState(BottomSheetType.FIXED); - } - }, + onVerticalDragUpdate: (details) { + if (details.delta.dy > 0) { + context.read().updateBottomSheetState(BottomSheetType.FIXED); + } + }, child: Column( mainAxisSize: MainAxisSize.min, children: [ @@ -239,22 +206,19 @@ class CallAmbulancePage extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, spacing: 16.h, children: [ - hospitalAndPickUpSection(context), - if(context.read().appointment != null) - AppointmentDoctorCard( - renderWidgetForERDisplay: true, - patientAppointmentHistoryResponseModel: context.read().appointment!, - onAskDoctorTap: () {}, - onCancelTap: () async {}, - onRescheduleTap: () async {}, - ).onPress((){ - openAppointmentList(context); - }) - + if (context.read().appointment != null) + AppointmentDoctorCard( + renderWidgetForERDisplay: true, + patientAppointmentHistoryResponseModel: context.read().appointment!, + onAskDoctorTap: () {}, + onCancelTap: () async {}, + onRescheduleTap: () async {}, + ).onPress(() { + openAppointmentList(context); + }) ], - ).paddingOnly(top: 24.h, bottom: 32.h,left: 24.h, right: 24.h), - + ).paddingOnly(top: 24.h, bottom: 32.h, left: 24.h, right: 24.h), bottomPriceContent(context) ], ), @@ -286,8 +250,7 @@ class CallAmbulancePage extends StatelessWidget { shrinkWrap: true, itemCount: 3, itemBuilder: (__, index) { - if (index == - 2) // todo means the end of the list so handle as per the viewmodel + if (index == 2) // todo means the end of the list so handle as per the viewmodel { return CustomButton( height: 40.h, @@ -301,11 +264,8 @@ class CallAmbulancePage extends StatelessWidget { } else { return AddressItem( isSelected: index == 0, - address: - "Flat No 301, Building No 12, Palm Spring Apartment, Sector 45, Gurugram, Haryana 122003", - title: index == 0 - ? "Home".needTranslation - : "Work".needTranslation, + address: "Flat No 301, Building No 12, Palm Spring Apartment, Sector 45, Gurugram, Haryana 122003", + title: index == 0 ? "Home".needTranslation : "Work".needTranslation, onTap: () {}, ); } @@ -354,13 +314,9 @@ class CallAmbulancePage extends StatelessWidget { leadingIcon: AppAssets.pickup_bed, ), CustomSwitch( - value: context - .watch() - .pickupFromInsideTheLocation, - onChanged: (value){ - context - .read() - .updateThePickupPlaceFromLocation(value); + value: context.watch().pickupFromInsideTheLocation, + onChanged: (value) { + context.read().updateThePickupPlaceFromLocation(value); }, ) ], @@ -369,20 +325,17 @@ class CallAmbulancePage extends StatelessWidget { Row( children: [ hospitalAndPickUpItemContent( - title: '', + title: 'Appointment', subTitle: "Have any appointment".needTranslation, - leadingIcon: AppAssets.appointment_checkin_icon, + leadingIcon: AppAssets.appointment_calendar_icon, ), CustomSwitch( - value: context - .watch() - .haveAnAppointment, + value: context.watch().haveAnAppointment, onChanged: (value) async { // if (value) { // openAppointmentList(context); // } - await context.read() - .updateAppointment(value); + await context.read().updateAppointment(value); if (context.read().appointments?.isNotEmpty == true) { openAppointmentList(context); } @@ -405,7 +358,7 @@ class CallAmbulancePage extends StatelessWidget { borderRadius: 12.h, color: AppColors.greyColor, ), - child: Utils.buildSvgWithAssets(icon: leadingIcon), + child: Utils.buildSvgWithAssets(icon: leadingIcon, iconColor: AppColors.greyTextColor), ); } @@ -454,55 +407,47 @@ class CallAmbulancePage extends StatelessWidget { mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.end, children: [ - DecoratedBox( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.scaffoldBgColor, - customBorder: BorderRadius.only( - topLeft: Radius.circular(24.h), - topRight: Radius.circular(24.h), - ), - hasShadow: true + 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: 12.h, + hasShadow: true), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 12.h, + children: [ + Row( children: [ - Row( - children: [ - Expanded( - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - spacing: 4.h, - children: [ - "Total amount to pay".needTranslation.toText18( + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 4.h, + children: [ + "Total amount to pay".needTranslation.toText18( weight: FontWeight.w600, color: AppColors.textColor, ), - Row( - children: [ - Utils.buildSvgWithAssets(icon: AppAssets.warning, - height: 18.h, width: 18.h), - SizedBox(width: 4.h,), - "Amount will be paid at the hospital" - .needTranslation - .toText12( + Row( + children: [ + Utils.buildSvgWithAssets(icon: AppAssets.warning, height: 18.h, width: 18.h), + SizedBox( + width: 4.h, + ), + "Amount will be paid at the hospital".needTranslation.toText12( fontWeight: FontWeight.w500, color: AppColors.greyTextColor, ), - ], - ) ], - ), - ), + ) + ], + ), + ), Utils.getPaymentAmountWithSymbol( - (Utils.formatNumberToInternationalFormat(context - .read() - .getTotalPrice() ?? - 0)) - .toText24( - fontWeight: FontWeight.w600, - color: AppColors.textColor, - letterSpacing: -2), + (Utils.formatNumberToInternationalFormat(context.read().getTotalPrice() ?? 0)) + .toText24(fontWeight: FontWeight.w600, color: AppColors.textColor, letterSpacing: -2), AppColors.blackColor, 17.h) @@ -518,20 +463,19 @@ class CallAmbulancePage extends StatelessWidget { PlacePrediction? placePrediction = locationViewModel.selectedPrediction; context.read().submitAmbulanceRequest(response?.results.first, placeDetails, placePrediction); }) - ], - ).paddingOnly(top: 24.h, bottom: 12.h, left: 24.h, right: 24.h), - ), + ], + ).paddingOnly(top: 24.h, bottom: 12.h, left: 24.h, right: 24.h), + ), ], ); } - showHospitalBottomSheet(BuildContext context){ + showHospitalBottomSheet(BuildContext context) { showCommonBottomSheetWithoutHeight( - title: - LocaleKeys.selectHospital.tr(), + title: LocaleKeys.selectHospital.tr(), context, child: Consumer( - builder:(_,vm,__)=> HospitalBottomSheetBody( + builder: (_, vm, __) => HospitalBottomSheetBody( searchText: vm.searchController, displayList: vm.displayList, onFacilityClicked: (value) { @@ -545,8 +489,7 @@ class CallAmbulancePage extends StatelessWidget { onHospitalSearch: (value) { vm.searchHospitals(value ?? ""); }, - selectedFacility: - vm.selectedFacility, + selectedFacility: vm.selectedFacility, hmcCount: vm.hmcCount, hmgCount: vm.hmgCount, ), @@ -583,16 +526,14 @@ class CallAmbulancePage extends StatelessWidget { textPlaceInput(context) { return Consumer(builder: (_, vm, __) { - print( - "the data is ${vm.geocodeResponse?.results.first.formattedAddress ?? vm.selectedPrediction?.description}"); + print("the data is ${vm.geocodeResponse?.results.first.formattedAddress ?? vm.selectedPrediction?.description}"); 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, + text: vm.geocodeResponse?.results.first.formattedAddress ?? vm.selectedPrediction?.description, ), leadingIcon: AppAssets.location_pickup, isAllowLeadingIcon: true, @@ -614,8 +555,7 @@ class CallAmbulancePage extends StatelessWidget { ///decide which field to show first based on the selected calling place inputFields(BuildContext context) { - return context.read().callingPlace == - AmbulanceCallingPlace.FROM_HOSPITAL + return context.read().callingPlace == AmbulanceCallingPlace.FROM_HOSPITAL ? HospitalFieldFirstThanPlaces(context) : PlaceFirstThanHospitalField(context); } @@ -639,21 +579,15 @@ class CallAmbulancePage extends StatelessWidget { hospitalField(BuildContext context) { return DecoratedBox( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.whiteColor, borderRadius: 12.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 12.h), child: Row( children: [ hospitalAndPickUpItemContent( title: "Select Hospital".needTranslation, - subTitle: context - .read() - .getSelectedHospitalName() ?? - "Select Hospital".needTranslation, + subTitle: context.read().getSelectedHospitalName() ?? "Select Hospital".needTranslation, leadingIcon: AppAssets.hospital, ), - Utils.buildSvgWithAssets( - icon: AppAssets.down_cheveron, width: 24.h, height: 24.h) - .paddingAll(16.h) + Utils.buildSvgWithAssets(icon: AppAssets.down_cheveron, width: 24.h, height: 24.h).paddingAll(16.h) ], ).onPress(() { print("the item is clicked"); diff --git a/lib/presentation/emergency_services/call_ambulance/tracking_screen.dart b/lib/presentation/emergency_services/call_ambulance/tracking_screen.dart index eddca11..4a9231a 100644 --- a/lib/presentation/emergency_services/call_ambulance/tracking_screen.dart +++ b/lib/presentation/emergency_services/call_ambulance/tracking_screen.dart @@ -8,6 +8,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/emergency_services/emergency_services_view_model.dart'; import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/AmbulanceRequestOrdersModel.dart'; +import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/RRTServiceData.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'; @@ -22,11 +23,13 @@ import 'package:url_launcher/url_launcher.dart' show launchUrl; class TrackingScreen extends StatelessWidget { OrderTrackingState? state ; + final bool isRRTOrder; final AmbulanceRequestOrdersModel? order; + final GetCMCAllOrdersResponseModel? rrtOrder; - TrackingScreen({super.key, OrderTrackingState? state, this.order}){ + TrackingScreen({super.key, OrderTrackingState? state, this.order, this.isRRTOrder = false, this.rrtOrder}){ if(state == null){ - switch (order?.statusId) { + switch (order?.statusId ?? rrtOrder?.statusId) { case 1: //pending case 2: //processing this.state = OrderTrackingState.waitingForCall; @@ -53,7 +56,8 @@ class TrackingScreen extends StatelessWidget { visible: state == OrderTrackingState.ended, child: SafeArea( child: CustomButton( - height: 56.h, + height: 40.h, + iconSize: 18.w, backgroundColor: AppColors.bgGreenColor, borderColor: Colors.transparent, text: "Close".needTranslation, @@ -70,7 +74,7 @@ class TrackingScreen extends StatelessWidget { animationSection(), Column( spacing: 16.h, - children: [orderStatus(context), orderTrackingId(), contactSection()], + children: [orderStatus(context), orderTrackingId(context), contactSection()], ).paddingAll(16.h), ], ))), @@ -140,6 +144,8 @@ class TrackingScreen extends StatelessWidget { borderColor: AppColors.primaryRedColor, textColor: Colors.white, icon: AppAssets.cancel, + height: 40.h, + iconSize: 18.w, ), ], ); @@ -153,6 +159,7 @@ class TrackingScreen extends StatelessWidget { mapSection(context), CustomButton( height: 40.h, + iconSize: 18.w, backgroundColor: AppColors.lightRedButtonColor, borderColor: Colors.transparent, text: "Share Your Live Location on Whatsapp".needTranslation, @@ -222,13 +229,13 @@ class TrackingScreen extends StatelessWidget { width: 36.h, child: CustomButton( text: '', - iconSize: 16.h, + iconSize: 18.h, icon: AppAssets.call_fill, onPressed: () {}, backgroundColor: AppColors.lightRedButtonColor, iconColor: AppColors.primaryRedColor, borderColor: Colors.transparent, - height: 36.h, + height: 40.h, ), ) ], @@ -328,11 +335,12 @@ class TrackingScreen extends StatelessWidget { } } - orderTrackingId() { + orderTrackingId(BuildContext context) { if(state == OrderTrackingState.failed){ return SizedBox.shrink(); } return Container( + width: MediaQuery.sizeOf(context).width-32.h, padding: EdgeInsets.all(16.h), decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.whiteColor, @@ -344,20 +352,10 @@ class TrackingScreen extends StatelessWidget { spacing: 8.h, children: [ - Visibility(visible:order != null ,child: "Req ID: ${order?.iD}".toText16(color: AppColors.textColor, weight: FontWeight.w600)), - Row( - spacing: 8.h, - children: [ - Flexible(child: - chip(order?.pickupLocation??"", AppAssets.location_pickup, AppColors.blackBgColor), + Visibility(visible:(order != null || rrtOrder != null) ,child: "Req ID: ${order?.iD?? rrtOrder?.iD}".toText16(color: AppColors.textColor, weight: FontWeight.w600)), + if(order != null) ambulanceOrderData() + // else rrtOrderData() - ), - Flexible(child: - chip(order?.dropOffLocation??"", AppAssets.hospital, AppColors.blackBgColor), - - ) - ], - ), ], ), ); @@ -373,6 +371,7 @@ class TrackingScreen extends StatelessWidget { } contactSection() { + if(isRRTOrder) return SizedBox.shrink(); return Container( padding: EdgeInsets.all(16.h), decoration: RoundedRectangleBorder().toSmoothCornerDecoration( @@ -387,8 +386,12 @@ class TrackingScreen extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, spacing: 4.h, children: [ - "Contact Rapid Response Team (RRT)".needTranslation.toText14(color: AppColors.textColor, weight: FontWeight.w600), - "0115259555".needTranslation.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500), + "Contact".needTranslation.toText14(color: AppColors.textColor, weight: FontWeight.w600), + "0115259555".needTranslation.toText12(color: AppColors.primaryRedColor, fontWeight: FontWeight.w500).onPress((){ + launchUrl( + Uri.parse("tel://0115259555"), + ); + }), SizedBox(height: 8.h), ], ), @@ -456,6 +459,44 @@ class TrackingScreen extends StatelessWidget { openCancelOrderBottomSheet(BuildContext context){ - context.read().cancelOrder(order, shouldPop: true); + if(isRRTOrder){ + context.read().cancelRRTOrder(rrtOrder?.iD??-1, shouldPop: true); + return; + }else { + context.read().cancelOrder(order, shouldPop: true); + return; + } + } + + Widget ambulanceOrderData() { + return Row( + spacing: 8.h, + children: [ + Flexible(child: + chip(order?.pickupLocation??"", AppAssets.location_pickup, AppColors.blackBgColor), + + ), + Flexible(child: + chip(order?.dropOffLocation??"", AppAssets.hospital, AppColors.blackBgColor), + + ) + ], + ); } + + // Widget rrtOrderData() { + // return Row( + // spacing: 8.h, + // children: [ + // Flexible(child: + // chip(rrtOrder?.pickupLocation??"", AppAssets.location_pickup, AppColors.blackBgColor), + // + // ), + // // Flexible(child: + // // chip(rrtOrder?.??"", AppAssets.hospital, AppColors.blackBgColor), + // // + // // ) + // ], + // ); + // } } diff --git a/lib/presentation/emergency_services/call_ambulance/widgets/pickup_location.dart b/lib/presentation/emergency_services/call_ambulance/widgets/pickup_location.dart index 2363644..9410af1 100644 --- a/lib/presentation/emergency_services/call_ambulance/widgets/pickup_location.dart +++ b/lib/presentation/emergency_services/call_ambulance/widgets/pickup_location.dart @@ -29,7 +29,7 @@ class PickupLocation extends StatelessWidget { ), "Select Direction" .needTranslation - .toText14(color: AppColors.textColor, weight: FontWeight.w600), + .toText16(color: AppColors.textColor, weight: FontWeight.w600), SizedBox( height: 12.h, ), @@ -39,56 +39,54 @@ class PickupLocation extends StatelessWidget { return Column( spacing: 12.h, children: [ - // Use a plain Row with Radios and provide required groupValue and onChanged - Row( - mainAxisAlignment: MainAxisAlignment.start, - spacing: 24.h, - children: [ - Row( - children: [ - Radio( - value: AmbulanceCallingPlace.TO_HOSPITAL, - groupValue: value, - onChanged: (AmbulanceCallingPlace? v) { - if (v != null) { - context.read().updateCallingPlace(v); - } - }, - activeColor: AppColors.primaryRedColor, - fillColor: MaterialStateProperty.all(AppColors.primaryRedColor), - ), - "To Hospital" - .needTranslation - .toText12(color: AppColors.textColor) - ], - ).onPress(() { - context - .read() - .updateCallingPlace(AmbulanceCallingPlace.TO_HOSPITAL); - }), - Row( - children: [ - Radio( - value: AmbulanceCallingPlace.FROM_HOSPITAL, - groupValue: value, - onChanged: (AmbulanceCallingPlace? v) { - if (v != null) { - context.read().updateCallingPlace(v); - } - }, - activeColor: AppColors.primaryRedColor, - fillColor: MaterialStateProperty.all(AppColors.primaryRedColor), - ), - "From Hospital" - .needTranslation - .toText12(color: AppColors.textColor) - ], - ).onPress(() { - context - .read() - .updateCallingPlace(AmbulanceCallingPlace.FROM_HOSPITAL); - }), - ], + RadioGroup( + groupValue: value, + onChanged: (value) { + context + .read() + .updateCallingPlace(value); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + spacing: 24.h, + children: [ + Row( + children: [ + Radio( + value: AmbulanceCallingPlace.TO_HOSPITAL, + groupValue: value, + activeColor: AppColors.primaryRedColor, + + fillColor: MaterialStateProperty.all(AppColors.primaryRedColor), + ), + "To Hospital" + .needTranslation + .toText14(color: AppColors.textColor, weight: FontWeight.w500) + ], + ).onPress((){ + context + .read() + .updateCallingPlace(AmbulanceCallingPlace.TO_HOSPITAL); + }), + Row( + children: [ + Radio( + value: AmbulanceCallingPlace.FROM_HOSPITAL, + activeColor: AppColors.primaryRedColor, + + fillColor: MaterialStateProperty.all(AppColors.primaryRedColor), + ), + "From Hospital" + .needTranslation + .toText14(color: AppColors.textColor, weight: FontWeight.w500) + ], + ).onPress((){ + context + .read() + .updateCallingPlace(AmbulanceCallingPlace.FROM_HOSPITAL); + }), + ], + ), ), Visibility( visible: value == AmbulanceCallingPlace.TO_HOSPITAL, @@ -98,59 +96,58 @@ class PickupLocation extends StatelessWidget { builder: (context, directionValue, _) { return Column( spacing: 12.h, + crossAxisAlignment: CrossAxisAlignment.start, children: [ "Select Way" .needTranslation - .toText14(color: AppColors.textColor, weight: FontWeight.w600), - // Use a Row and assign groupValue/onChanged to each Radio - Row( - mainAxisAlignment: MainAxisAlignment.start, - spacing: 24.h, - children: [ - Row( - children: [ - Radio( - value: AmbulanceDirection.ONE_WAY, - groupValue: directionValue, - onChanged: (AmbulanceDirection? v) { - if (v != null) { - context.read().updateDirection(v); - } - }, - activeColor: AppColors.primaryRedColor, - fillColor: MaterialStateProperty.all(AppColors.primaryRedColor), - ), - "One Way" - .needTranslation - .toText12(color: AppColors.textColor) - ], - ).onPress(() { - context - .read() - .updateDirection(AmbulanceDirection.ONE_WAY); - }), - Row( - children: [ - Radio( - value: AmbulanceDirection.TWO_WAY, - groupValue: directionValue, - onChanged: (AmbulanceDirection? v) { - if (v != null) { - context.read().updateDirection(v); - } - }, - fillColor: MaterialStateProperty.all(AppColors.primaryRedColor), - ), - "Two Way" - .needTranslation - .toText12(color: AppColors.textColor) - ], - ).onPress(() { - context - .read() - .updateDirection(AmbulanceDirection.TWO_WAY); - }), - ], + .toText16(color: AppColors.textColor, weight: FontWeight.w600), + RadioGroup( + groupValue: directionValue, + onChanged: (value) { + context + .read() + .updateDirection(value); + }, + child: Row( + mainAxisAlignment: MainAxisAlignment.start, + spacing: 24.h, + children: [ + Row( + children: [ + Radio( + value: AmbulanceDirection.ONE_WAY, + activeColor: AppColors.primaryRedColor, + + fillColor: MaterialStateProperty.all(AppColors.primaryRedColor), + ), + "One Way" + .needTranslation + .toText12(color: AppColors.textColor, fontWeight: FontWeight.w500) + ], + ).onPress((){ + context + .read() + .updateDirection(AmbulanceDirection.ONE_WAY); + }), + Row( + children: [ + Radio( + value: AmbulanceDirection.TWO_WAY, + // activeColor: AppColors.primaryRedColor, + + fillColor: MaterialStateProperty.all(AppColors.primaryRedColor), + ), + "Two Way" + .needTranslation + .toText14(color: AppColors.textColor, weight: FontWeight.w500) + ], + ).onPress((){ + context + .read() + .updateDirection(AmbulanceDirection.TWO_WAY); + }), + ], + ), ), ], ); diff --git a/lib/presentation/emergency_services/emergency_services_page.dart b/lib/presentation/emergency_services/emergency_services_page.dart index ce4ae2b..8f6fd23 100644 --- a/lib/presentation/emergency_services/emergency_services_page.dart +++ b/lib/presentation/emergency_services/emergency_services_page.dart @@ -1,13 +1,12 @@ 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/dependencies.dart'; -import 'package:hmg_patient_app_new/core/location_util.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/features/emergency_services/emergency_services_view_model.dart'; +import 'package:hmg_patient_app_new/features/emergency_services/models/OrderDisplay.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/emergency_services/RRT/rrt_request_type_select.dart'; @@ -27,17 +26,18 @@ class EmergencyServicesPage extends StatelessWidget { EmergencyServicesPage({super.key}); late EmergencyServicesViewModel emergencyServicesViewModel; - LocationUtils? locationUtils; + + _handleConfirmationBottomSheet() {} @override Widget build(BuildContext context) { emergencyServicesViewModel = Provider.of(context, listen: false); - locationUtils = getIt.get(); - locationUtils!.isShowConfirmDialog = true; + return CollapsingListView( title: LocaleKeys.emergencyServices.tr(), requests: () { - Navigator.of(context).push(CustomPageRoute(page: ErHistoryListing(), direction: AxisDirection.up)); + emergencyServicesViewModel.changeOrderDisplayItems(OrderDislpay.ALL); + Navigator.of(context).push(CustomPageRoute(page: ErHistoryListing(), direction: AxisDirection.up)); }, child: Padding( padding: EdgeInsets.all(24.h), @@ -109,7 +109,6 @@ class EmergencyServicesPage extends StatelessWidget { CustomButton( text: LocaleKeys.confirm.tr(context: context), onPressed: () async { - // Navigator.of(context).pop(); await emergencyServicesViewModel.getTransportationOption(); openTranportationSelectionBottomSheet(context); @@ -201,23 +200,7 @@ class EmergencyServicesPage extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - "".toText14(), - Utils.buildSvgWithAssets( - icon: AppAssets.cancel_circle_icon, - iconColor: AppColors.whiteColor, - width: 24.h, - height: 24.h, - fit: BoxFit.contain, - ).onPress(() { - Navigator.of(context).pop(); - }), - ], - ), - Lottie.asset(AppAnimations.ambulance_alert, - repeat: false, reverse: false, frameRate: FrameRate(60), width: 120.h, height: 120.h, fit: BoxFit.contain), + Lottie.asset(AppAnimations.ambulance_alert, repeat: false, reverse: false, frameRate: FrameRate(60), width: 120.h, height: 120.h, fit: BoxFit.contain), SizedBox(height: 8.h), LocaleKeys.confirm.tr().toText28(color: AppColors.whiteColor, isBold: true), SizedBox(height: 8.h), @@ -231,10 +214,23 @@ class EmergencyServicesPage extends StatelessWidget { Navigator.of(context).pop(); LoaderBottomSheet.showLoader(); + emergencyServicesViewModel.clearRRTData(); await emergencyServicesViewModel.getRRTProcedures(onSuccess: (val) { LoaderBottomSheet.hideLoader(); showCommonBottomSheetWithoutHeight( - title: "Rapid Response Team (RRT)".needTranslation, + padding: EdgeInsets.only(top: 24.h), + titleWidget: Transform.flip( + flipX: emergencyServicesViewModel.isArabic, + child: Utils.buildSvgWithAssets( + icon: AppAssets.arrow_back, + iconColor: Color(0xff2B353E), + fit: BoxFit.contain, + ), + ).onPress(() { + + Navigator.pop(context); + }), + // title: "Rapid Response Team (RRT)".needTranslation, context, child: RrtRequestTypeSelect(), isFullScreen: false, @@ -294,10 +290,7 @@ class EmergencyServicesPage extends StatelessWidget { showCommonBottomSheetWithoutHeight( context, child: Container( - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( - color: AppColors.primaryRedColor, - borderRadius: 24.h, - ), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.primaryRedColor, borderRadius: 24.h), child: Padding( padding: EdgeInsets.all(24.h), child: Column( @@ -365,8 +358,8 @@ class EmergencyServicesPage extends StatelessWidget { onCloseClicked: () { context.read().flushPickupInformation(); }, - titleWidget: Transform.flip( - flipX: emergencyServicesViewModel.isArabic ? true : false, + titleWidget: Transform.flip( + flipX: emergencyServicesViewModel.isArabic, child: Utils.buildSvgWithAssets( icon: AppAssets.arrow_back, iconColor: Color(0xff2B353E), diff --git a/lib/presentation/emergency_services/history/er_history_listing.dart b/lib/presentation/emergency_services/history/er_history_listing.dart index e5dd1e4..b98f2f1 100644 --- a/lib/presentation/emergency_services/history/er_history_listing.dart +++ b/lib/presentation/emergency_services/history/er_history_listing.dart @@ -1,13 +1,20 @@ import 'package:easy_localization/easy_localization.dart'; import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart' show AppAssets; 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/OrderDisplay.dart'; +import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/AmbulanceRequestOrdersModel.dart'; +import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/RRTServiceData.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; -import 'package:hmg_patient_app_new/presentation/emergency_services/history/widget/ambulance_history_item.dart'; +import 'package:hmg_patient_app_new/presentation/emergency_services/history/widget/ambulance_history_item.dart' show AmbulanceHistoryItem; +import 'package:hmg_patient_app_new/presentation/emergency_services/history/widget/rrt_item.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; import 'package:provider/provider.dart'; class ErHistoryListing extends StatelessWidget { @@ -16,30 +23,53 @@ class ErHistoryListing extends StatelessWidget { return Scaffold( body: Column( children: [ + Expanded( child: CollapsingListView( title: "History Log".needTranslation, child: SingleChildScrollView( physics: NeverScrollableScrollPhysics(), child: Column( - children: [Visibility( - visible: context - .read() - .orders - ?.isNotEmpty == true, - child: ListView.builder( - shrinkWrap: true, - physics: NeverScrollableScrollPhysics(), - itemCount: context - .read() - .orders - ?.length ?? 0, - itemBuilder: (_, index) => - Consumer( builder: (_, vm, __)=> - AmbulanceHistoryItem(order: vm.orders![index]), - ) + children: [ + + Selector( + selector: (context, vm) => (vm.orderDisplayList, vm.historyLoading), + builder: (context, data, _) { + + return Column( + children: [ + orderChips(context, data.$2, data.$1), + + Visibility( + visible:data.$1.isNotEmpty == true, + child: ListView.builder( + padding: EdgeInsets.only(top:24.h ), + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemCount:data?.$1.length ?? 0, + itemBuilder: (_, index) { + var order = data.$1[index]; + if (order is AmbulanceRequestOrdersModel) { + return AmbulanceHistoryItem(order: order).toShimmer2(isShow: data.$2); + } else { + return RRTItem(order: (order as GetCMCAllOrdersResponseModel)).toShimmer2(isShow: data.$2); + } + }), + ), + Visibility( + visible: data.$1 + ?.isEmpty == true, child: Center( + child: Utils.getNoDataWidget(context, + noDataText: "You don't have any history" + .needTranslation), + )), + ], + ); + } ), - ),] + + + ] ).paddingAll(16.h) )) @@ -47,16 +77,77 @@ class ErHistoryListing extends StatelessWidget { ), - Visibility( - visible: context - .read() - .orders - ?.isEmpty == true, - child: Utils.getNoDataWidget(context, - noDataText: "You don't have any history" - .needTranslation)), + ], ), ); } + + orderChips(BuildContext context, bool isLoading, List dataList) { + + if (dataList?.isEmpty == true) { + return SizedBox.shrink(); + } + return Selector( + selector: (context, vm) => vm.currentlyDisplayedOrder, + builder: (context, value, __) { + return Row( + spacing: 8.h, + children: [ + if(dataList?.isNotEmpty == true) + AppCustomChipWidget( + labelText: "All Facilities".needTranslation, + shape: RoundedRectangleBorder( + side: BorderSide( + color: value == OrderDislpay.ALL ? AppColors.errorColor : AppColors.chipBorderColorOpacity20, + width: 1, + ), + borderRadius: BorderRadius.circular(10)), + backgroundColor: value == OrderDislpay.ALL ? AppColors.secondaryLightRedColor : AppColors.whiteColor, + textColor: value == OrderDislpay.ALL ? AppColors.errorColor : AppColors.blackColor, + ).onPress(() { + context.read().changeOrderDisplayItems(OrderDislpay.ALL); + }).toShimmer2(isShow: isLoading), + if(context + .read() + .ambulanceOrders + ?.isNotEmpty == true) + AppCustomChipWidget( + labelText: "Ambulance".needTranslation, + icon: AppAssets.ambulance, + shape: RoundedRectangleBorder( + side: BorderSide( + color: value == OrderDislpay.AMBULANCE ? AppColors.errorColor : AppColors.chipBorderColorOpacity20, + width: 1, + ), + borderRadius: BorderRadius.circular(10)), + backgroundColor: value == OrderDislpay.AMBULANCE ? AppColors.secondaryLightRedColor : AppColors.whiteColor, + textColor: value == OrderDislpay.AMBULANCE ? AppColors.errorColor : AppColors.blackColor, + ).toShimmer2(isShow: isLoading).onPress(() { + context.read().changeOrderDisplayItems(OrderDislpay.AMBULANCE); + }), + if(context + .read() + .ordersRRT + ?.completedOrders + .isNotEmpty == true) + AppCustomChipWidget( + labelText: "Rapid Response Team".needTranslation, + icon: AppAssets.ic_rrt_vehicle, + shape: RoundedRectangleBorder( + side: BorderSide( + color: value == OrderDislpay.RRT ? AppColors.errorColor : AppColors.chipBorderColorOpacity20, + width: 1, + ), + borderRadius: BorderRadius.circular(10)), + backgroundColor: value == OrderDislpay.RRT ? AppColors.secondaryLightRedColor : AppColors.whiteColor, + textColor: value == OrderDislpay.RRT ? AppColors.errorColor : AppColors.blackColor, + ).toShimmer2(isShow: isLoading).onPress(() { + context.read().changeOrderDisplayItems(OrderDislpay.RRT); + }), + ], + ); + }); + + } } diff --git a/lib/presentation/emergency_services/history/widget/RequestStatus.dart b/lib/presentation/emergency_services/history/widget/RequestStatus.dart index de95bdc..4f39a49 100644 --- a/lib/presentation/emergency_services/history/widget/RequestStatus.dart +++ b/lib/presentation/emergency_services/history/widget/RequestStatus.dart @@ -54,8 +54,8 @@ class RequestStatus extends StatelessWidget { switch (status) { case 1: //pending case 2: - return AppColors.successColor;//processing case 3: //completed + return AppColors.successColor;//processing case 4: //cancel case 6: case 7: 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 5f4c8b2..f3ec388 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, + height: 40.h, + iconSize: 18.w, ), ], ).paddingAll(16.h), @@ -71,7 +73,7 @@ class AmbulanceHistoryItem extends StatelessWidget { labelText: title, icon: iconString, iconColor: iconColor, - iconSize: 14.h, + iconSize: 12.h, ); } diff --git a/lib/presentation/emergency_services/history/widget/rrt_item.dart b/lib/presentation/emergency_services/history/widget/rrt_item.dart new file mode 100644 index 0000000..61ecced --- /dev/null +++ b/lib/presentation/emergency_services/history/widget/rrt_item.dart @@ -0,0 +1,78 @@ +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/date_util.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/resp_model/AmbulanceRequestOrdersModel.dart'; +import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/RRTServiceData.dart'; +import 'package:hmg_patient_app_new/presentation/emergency_services/history/widget/RequestStatus.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; +import 'package:provider/provider.dart'; + +import '../../../../core/utils/utils.dart'; + + +class RRTItem extends StatelessWidget { + final GetCMCAllOrdersResponseModel order; + + const RRTItem({super.key, required this.order}); + + @override + Widget build(BuildContext context) { + return DecoratedBox( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: Colors.white, + hasShadow: true, + customBorder: BorderRadius.all( + Radius.circular(20.h), + ), + ), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + spacing: 8.h, + children: [ + RequestStatus(status: order.statusId ?? 0), + "Req ID: ${order.iD}".toText16(color: AppColors.textColor, weight: FontWeight.w600), + Row( + spacing: 4.w, + children: [ + chip( Utils.getDayMonthYearDateFormatted(DateUtil.convertStringToDate(order.time)), AppAssets.calendar, AppColors.blackBgColor), + 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, + onPressed: () async { + openCancelOrderBottomSheet(context); + }, + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: Colors.white, + icon: AppAssets.cancel, + iconSize: 20.h, + height: 40.h, + ), + ], + ).paddingAll(16.h), + ).paddingOnly(bottom: 16.h); + } + + chip(String title, String iconString, Color iconColor) { + return AppCustomChipWidget( + labelText: title, + icon: iconString, + iconColor: iconColor, + iconSize: 12.h, + ); + } + + openCancelOrderBottomSheet(BuildContext context) { + context.read().cancelRRTOrder(order.iD); + } +} 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 2242441..af576aa 100644 --- a/lib/presentation/hmg_services/services_page.dart +++ b/lib/presentation/hmg_services/services_page.dart @@ -31,13 +31,23 @@ class ServicesPage extends StatelessWidget { textColor: AppColors.blackColor, route: AppRoutes.comprehensiveCheckupPage, ), + HmgServicesComponentModel( + 12, + "Home Health Care".needTranslation, + "".needTranslation, + AppAssets.emergency_services_icon, + true, + bgColor: AppColors.bgGreenColor, + textColor: AppColors.blackColor, + route: AppRoutes.homeHealthCarePage, + ), ]; @override 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 3347902..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'; @@ -245,7 +246,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 +440,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: [ @@ -526,11 +527,13 @@ 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( - height: 280.h, + height: 340.h, child: ListView.separated( scrollDirection: Axis.horizontal, itemCount: LandingPageData.getServiceCardsList.length, @@ -567,9 +570,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/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/presentation/home/widgets/small_service_card.dart b/lib/presentation/home/widgets/small_service_card.dart index 234fad1..f54f442 100644 --- a/lib/presentation/home/widgets/small_service_card.dart +++ b/lib/presentation/home/widgets/small_service_card.dart @@ -110,6 +110,7 @@ class SmallServiceCard extends StatelessWidget { case "emergency": context.read().flushData(); context.read().getTransportationOrders(showLoader: false,); + context.read().getRRTOrders(showLoader: false,); Navigator.of(context).push( CustomPageRoute( page: EmergencyServicesPage(), diff --git a/lib/presentation/home_health_care/hhc_order_detail_page.dart b/lib/presentation/home_health_care/hhc_order_detail_page.dart new file mode 100644 index 0000000..21b0def --- /dev/null +++ b/lib/presentation/home_health_care/hhc_order_detail_page.dart @@ -0,0 +1,248 @@ +import 'dart:async'; + +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_staggered_animations/flutter_staggered_animations.dart'; +import 'package:hmg_patient_app_new/core/app_assets.dart'; +import 'package:hmg_patient_app_new/core/utils/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/hmg_services/hmg_services_view_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_all_orders_resp_model.dart'; +import 'package:hmg_patient_app_new/presentation/home_health_care/widgets/hhc_ui_selection_helper.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'; + +class HhcOrderDetailPage extends StatefulWidget { + const HhcOrderDetailPage({super.key}); + + @override + State createState() => _HhcOrderDetailPageState(); +} + +class _HhcOrderDetailPageState extends State { + @override + void initState() { + super.initState(); + final hmgServicesViewModel = context.read(); + scheduleMicrotask(() async { + await hmgServicesViewModel.getAllHhcOrders(); + }); + } + + Color _getStatusColor(int? statusId) { + switch (statusId) { + case 1: // Pending + return const Color(0xffCC9B14); + case 2: // Processing + return const Color(0xff2E303A); + case 3: // Completed + return const Color(0xff359846); + case 4: // Cancelled + case 6: // Rejected + case 7: // Rejected + return const Color(0xffD02127); + default: + return AppColors.greyColor; + } + } + + String _formatDate(String? dateString) { + if (dateString == null) return ''; + try { + final date = DateTime.parse(dateString); + return DateFormat('MMM dd, yyyy').format(date); + } catch (e) { + return dateString; + } + } + + Widget _buildLoadingShimmer() { + return ListView.separated( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: 3, + separatorBuilder: (_, __) => SizedBox(height: 12.h), + itemBuilder: (context, index) { + return _buildOrderCard(GetCMCAllOrdersResponseModel(), isLoading: true); + }, + ); + } + + Widget _buildOrderCard(GetCMCAllOrdersResponseModel order, {bool isLoading = false}) { + final statusColor = _getStatusColor(order.statusId); + final canCancel = order.statusId == 1 || order.statusId == 2; + + return AnimatedContainer( + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: true, + ), + child: Padding( + padding: EdgeInsets.all(16.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Status and Date Row + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 6.h), + decoration: BoxDecoration( + color: statusColor.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(8.r), + ), + child: (isLoading ? "Processing" : order.statusText ?? '') + .toText12( + color: statusColor, + fontWeight: FontWeight.w600, + ) + .toShimmer2(isShow: isLoading), + ), + SizedBox(width: 8.w), + (isLoading ? "Jan 15, 2024" : _formatDate(order.created)) + .toText12( + color: AppColors.textColorLight, + fontWeight: FontWeight.w500, + ) + .toShimmer2(isShow: isLoading), + ], + ), + + SizedBox(height: 16.h), + + // Request ID + Row( + children: [ + if (!isLoading) ...[ + "Request ID:".needTranslation.toText14( + color: AppColors.textColorLight, + weight: FontWeight.w500, + ), + SizedBox(width: 4.w), + ], + (isLoading ? "12345" : "${order.iD ?? '-'}").toText16(isBold: true).toShimmer2(isShow: isLoading), + ], + ), + + SizedBox(height: 12.h), + + // Chips for Hospital, Service, and Amount + Wrap( + spacing: 6.w, + runSpacing: 6.h, + children: [ + // Service + if (order.serviceText != null || isLoading) + AppCustomChipWidget( + icon: AppAssets.servicesBottom, + labelText: isLoading ? "Service Name" : order.serviceText ?? '-', + ).toShimmer2(isShow: isLoading), + ], + ), + + // Cancel Button + if (canCancel || isLoading) ...[ + SizedBox(height: 16.h), + Row( + children: [ + Expanded( + child: CustomButton( + text: "Cancel Order".needTranslation, + onPressed: isLoading ? () {} : () => HhcUiSelectionHelper.showCancelConfirmationDialog(context: context, order: order), + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 14.f, + fontWeight: FontWeight.w600, + borderRadius: 10.r, + height: 44.h, + ).toShimmer2(isShow: isLoading), + ), + ], + ), + ] + ], + ), + ), + ); + } + + Widget _buildEmptyState() { + return Center( + child: Padding( + padding: EdgeInsets.symmetric(vertical: 40.h), + child: Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 12.r, + hasShadow: false, + ), + child: Utils.getNoDataWidget( + context, + noDataText: "You don't have any Home Health Care orders yet.".needTranslation, + isSmallWidget: true, + width: 62.w, + height: 62.h, + ), + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + return CollapsingListView( + title: "HHC Orders".needTranslation, + isLeading: true, + child: SingleChildScrollView( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Consumer( + builder: (context, viewModel, child) { + if (viewModel.isHhcOrdersLoading) { + return _buildLoadingShimmer(); + } + + if (viewModel.hhcOrdersList.isEmpty) { + return _buildEmptyState(); + } + + return ListView.separated( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + itemCount: viewModel.hhcOrdersList.length, + separatorBuilder: (_, __) => SizedBox(height: 12.h), + itemBuilder: (context, index) { + final order = viewModel.hhcOrdersList.reversed.toList()[index]; + + return AnimationConfiguration.staggeredList( + position: index, + duration: const Duration(milliseconds: 500), + child: SlideAnimation( + verticalOffset: 100.0, + child: FadeInAnimation( + child: _buildOrderCard(order), + ), + ), + ); + }, + ); + }, + ), + ], + ).paddingSymmetrical(24.w, 0), + ), + ); + } +} diff --git a/lib/presentation/home_health_care/hhc_procedures_page.dart b/lib/presentation/home_health_care/hhc_procedures_page.dart new file mode 100644 index 0000000..0cf57ce --- /dev/null +++ b/lib/presentation/home_health_care/hhc_procedures_page.dart @@ -0,0 +1,414 @@ +import 'dart:async'; + +import 'package:easy_localization/easy_localization.dart'; +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/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/hmg_services/hmg_services_view_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_all_orders_resp_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_services_resp_model.dart'; +import 'package:hmg_patient_app_new/presentation/home_health_care/hhc_order_detail_page.dart'; +import 'package:hmg_patient_app_new/presentation/home_health_care/hhc_selection_review_page.dart'; +import 'package:hmg_patient_app_new/presentation/home_health_care/widgets/hhc_ui_selection_helper.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/routes/custom_page_route.dart'; +import 'package:provider/provider.dart'; +import 'package:shimmer/shimmer.dart'; + +class HhcProceduresPage extends StatefulWidget { + const HhcProceduresPage({super.key}); + + @override + State createState() => _HhcProceduresPageState(); +} + +class _HhcProceduresPageState extends State { + @override + void initState() { + super.initState(); + final HmgServicesViewModel hmgServicesViewModel = context.read(); + final AppState appState = getIt.get(); + + scheduleMicrotask(() async { + final user = appState.getAuthenticatedUser(); + if (user != null) { + // Clear previous selections when entering the page + hmgServicesViewModel.clearHhcServicesSelection(); + await hmgServicesViewModel.getAllHhcOrders(); + await hmgServicesViewModel.getAllHhcServices(patientID: user.patientId ?? 0); + } + }); + } + + GetCMCAllOrdersResponseModel? _getPendingOrder(List orders) { + if (orders.isEmpty) return null; + + // Find pending or processing orders (status 1 or 2) + for (var order in orders) { + if (order.statusId == 1 || order.statusId == 2) { + return order; + } + } + + return null; + } + + Widget _buildPendingOrderCard(GetCMCAllOrdersResponseModel order) { + int status = order.statusId ?? 0; + String statusDisp = order.statusText ?? ""; + Color statusColor; + + if (status == 1) { + // pending + statusColor = AppColors.statusPendingColor; + } else if (status == 2) { + // processing + statusColor = AppColors.statusProcessingColor; + } else if (status == 3) { + // completed + statusColor = AppColors.statusCompletedColor; + } else { + // cancel / rejected + statusColor = AppColors.statusRejectedColor; + } + + final canCancel = order.statusId == 1 || order.statusId == 2; + + return Container( + width: double.infinity, + margin: EdgeInsets.all(16.w), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: true, + ), + child: Padding( + padding: EdgeInsets.all(16.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + // Status and Date Row + Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 6.h), + decoration: BoxDecoration( + color: statusColor.withValues(alpha: 0.1), + borderRadius: BorderRadius.circular(8.r), + ), + child: statusDisp.toText12( + color: statusColor, + fontWeight: FontWeight.w600, + ), + ), + SizedBox(width: 8.w), + if (order.created != null) + DateFormat('MMM dd, yyyy').format(DateTime.parse(order.created!)).toText12( + color: AppColors.textColorLight, + fontWeight: FontWeight.w500, + ), + ], + ), + + SizedBox(height: 16.h), + + // Request ID + Row( + children: [ + "Request ID:".needTranslation.toText14(color: AppColors.textColorLight, weight: FontWeight.w500), + SizedBox(width: 4.w), + "${order.iD ?? '-'}".toText16(isBold: true), + ], + ), + + SizedBox(height: 12.h), + + // Info message + Container( + padding: EdgeInsets.all(12.w), + decoration: BoxDecoration( + color: AppColors.infoBannerBgColor, + borderRadius: BorderRadius.circular(10.r), + border: Border.all( + color: AppColors.infoBannerBorderColor, + width: 1, + ), + ), + child: Row( + children: [ + Icon( + Icons.info_outline, + size: 20.w, + color: AppColors.infoBannerIconColor, + ), + SizedBox(width: 8.w), + Expanded( + child: "You have a pending order. Please wait for it to be processed.".needTranslation.toText12( + color: AppColors.infoBannerTextColor, + fontWeight: FontWeight.w500, + ), + ), + ], + ), + ), + if (canCancel) ...[ + SizedBox(height: 16.h), + Row( + children: [ + Expanded( + child: CustomButton( + text: "Cancel Order".needTranslation, + onPressed: () => HhcUiSelectionHelper.showCancelConfirmationDialog(context: context, order: order), + backgroundColor: AppColors.primaryRedColor, + borderColor: AppColors.primaryRedColor, + textColor: AppColors.whiteColor, + fontSize: 14.f, + fontWeight: FontWeight.w600, + borderRadius: 10.r, + height: 44.h, + ), + ), + ], + ), + ] + ], + ), + ), + ); + } + + Widget _buildServiceSelectionList(List services) { + if (services.isEmpty) { + return Center( + child: Padding( + padding: EdgeInsets.all(24.h), + child: Text( + 'No services available'.needTranslation, + style: TextStyle( + fontSize: 16.h, + color: AppColors.greyTextColor, + ), + ), + ), + ); + } + + return Consumer( + builder: (context, viewModel, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 8.h), + if (viewModel.selectedHhcServices.isNotEmpty) ...[ + SizedBox(height: 16.h), + Container( + margin: EdgeInsets.symmetric(horizontal: 16.w), + padding: EdgeInsets.all(16.w), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.primaryRedColor.withValues(alpha: 0.1), + borderRadius: 16.r, + ), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Selected Services".needTranslation.toText12( + color: AppColors.textColorLight, + fontWeight: FontWeight.w600, + ), + "${viewModel.selectedHhcServices.length} service(s) selected".toText14( + isBold: true, + weight: FontWeight.bold, + ), + ], + ), + Column( + crossAxisAlignment: CrossAxisAlignment.end, + children: [ + "Total Amount".needTranslation.toText12( + color: AppColors.textColorLight, + fontWeight: FontWeight.w600, + ), + Utils.getPaymentAmountWithSymbol( + viewModel.getHhcSelectedServicesTotal().toStringAsFixed(2).toText16( + isBold: true, + weight: FontWeight.bold, + color: AppColors.primaryRedColor, + ), + AppColors.primaryRedColor, + 14, + isSaudiCurrency: true, + ), + ], + ), + ], + ), + ), + ], + + SizedBox(height: 16.h), + Text( + 'Select Services'.needTranslation, + style: TextStyle( + fontSize: 20.h, + fontWeight: FontWeight.w700, + color: AppColors.blackColor, + letterSpacing: -0.8, + ), + ).paddingOnly(left: 16.w, right: 16.w), + SizedBox(height: 12.h), + ListView.builder( + padding: EdgeInsets.symmetric(horizontal: 16.w), + itemCount: services.length, + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemBuilder: (context, index) { + final service = services[index]; + final isSelected = viewModel.isHhcServiceSelected(service); + final isArabic = getIt.get().isArabic(); + final serviceName = isArabic ? (service.textN ?? service.text ?? '') : (service.text ?? ''); + + return AnimatedContainer( + duration: Duration(milliseconds: 300), + curve: Curves.easeInOut, + margin: EdgeInsets.only(bottom: 12.h), + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 16.r, + hasShadow: true, + ), + child: Material( + color: Colors.transparent, + child: InkWell( + onTap: () => viewModel.toggleHhcServiceSelection(service), + borderRadius: BorderRadius.circular(16.r), + child: Container( + padding: EdgeInsets.all(16.w), + child: Row( + children: [ + Checkbox( + value: isSelected, + onChanged: (v) => viewModel.toggleHhcServiceSelection(service), + activeColor: AppColors.primaryRedColor, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + visualDensity: VisualDensity.compact, + ), + SizedBox(width: 12.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + serviceName.toText16( + weight: FontWeight.w400, + color: AppColors.blackColor, + maxlines: 2, + ), + ], + ), + ), + ], + ), + ), + ), + ), + ); + }, + ), + // Summary Section + ], + ); + }, + ); + } + + void _proceedWithSelectedService() { + final hmgServicesViewModel = context.read(); + if (hmgServicesViewModel.selectedHhcServices.isNotEmpty) { + hmgServicesViewModel.setSelectedServiceForHhcOrder(hmgServicesViewModel.selectedHhcServices.first); + Navigator.of(context).pushReplacement( + CustomPageRoute( + page: HhcSelectionReviewPage(selectedServices: hmgServicesViewModel.selectedHhcServices), + direction: AxisDirection.left, + ), + ); + } + } + + Widget _buildLoadingShimmer() { + return ListView.separated( + shrinkWrap: true, + physics: const NeverScrollableScrollPhysics(), + padding: EdgeInsets.all(16.w), + itemCount: 10, + separatorBuilder: (_, __) => SizedBox(height: 12.h), + itemBuilder: (context, index) { + return Shimmer.fromColors( + baseColor: Colors.grey[300]!, + highlightColor: Colors.grey[100]!, + child: Container( + height: 80.h, + decoration: BoxDecoration( + color: Colors.white, + borderRadius: BorderRadius.circular(10.r), + ), + ), + ); + }, + ); + } + + @override + Widget build(BuildContext context) { + return CollapsingListView( + title: "Home Health Care".needTranslation, + history: () => Navigator.of(context).push(CustomPageRoute(page: HhcOrderDetailPage(), direction: AxisDirection.up)), + bottomChild: Consumer( + builder: (context, hmgServicesViewModel, child) { + if (hmgServicesViewModel.isHhcOrdersLoading || hmgServicesViewModel.isHhcServicesLoading) return SizedBox.shrink(); + final pendingOrder = _getPendingOrder(hmgServicesViewModel.hhcOrdersList); + if (pendingOrder == null && hmgServicesViewModel.selectedHhcServices.isNotEmpty) { + return SafeArea( + top: false, + child: Padding( + padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 12.h), + child: CustomButton( + borderWidth: 0, + text: "Next".needTranslation, + onPressed: _proceedWithSelectedService, + textColor: AppColors.whiteColor, + borderRadius: 12.r, + borderColor: Colors.transparent, + padding: EdgeInsets.symmetric(vertical: 14.h), + ), + ), + ); + } + return SizedBox.shrink(); + }, + ), + child: Consumer( + builder: (context, hmgServicesViewModel, child) { + if (hmgServicesViewModel.isHhcOrdersLoading || hmgServicesViewModel.isHhcServicesLoading) { + return _buildLoadingShimmer(); + } + final pendingOrder = _getPendingOrder(hmgServicesViewModel.hhcOrdersList); + if (pendingOrder != null) { + return _buildPendingOrderCard(pendingOrder); + } else { + return _buildServiceSelectionList(hmgServicesViewModel.hhcServicesList); + } + }, + ), + ); + } +} diff --git a/lib/presentation/home_health_care/hhc_selection_review_page.dart b/lib/presentation/home_health_care/hhc_selection_review_page.dart new file mode 100644 index 0000000..7baeec0 --- /dev/null +++ b/lib/presentation/home_health_care/hhc_selection_review_page.dart @@ -0,0 +1,244 @@ +import 'package:easy_localization/easy_localization.dart'; +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/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/route_extensions.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/hmg_services/hmg_services_view_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/req_models/cmc_create_new_order_req_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_services_resp_model.dart'; +import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_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/loader/bottomsheet_loader.dart'; +import 'package:maps_launcher/maps_launcher.dart'; +import 'package:provider/provider.dart'; + +class HhcSelectionReviewPage extends StatefulWidget { + final List selectedServices; + + const HhcSelectionReviewPage({super.key, required this.selectedServices}); + + @override + State createState() => _HhcSelectionReviewPageState(); +} + +class _HhcSelectionReviewPageState extends State { + @override + void initState() { + super.initState(); + // Initialize ViewModel state with selected services + WidgetsBinding.instance.addPostFrameCallback((_) { + final hmgServicesViewModel = context.read(); + if (widget.selectedServices.isNotEmpty) { + hmgServicesViewModel.setSelectedServiceForHhcOrder(widget.selectedServices.first); + } + }); + } + + @override + Widget build(BuildContext context) { + final appState = getIt.get(); + final isArabic = appState.isArabic(); + + return CollapsingListView( + title: "Summary".needTranslation, + bottomChild: _buildBottomButton(), + child: SingleChildScrollView( + padding: EdgeInsets.all(16.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildSelectedServicesCard(isArabic), + SizedBox(height: 16.h), + ], + ), + ), + ); + } + + Widget _buildSelectedServicesCard(bool isArabic) { + return Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 16.r, + ), + padding: EdgeInsets.all(16.w), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + "Selected Services".needTranslation.toText14( + weight: FontWeight.w600, + color: AppColors.greyTextColor, + letterSpacing: -0.4, + ), + SizedBox(height: 12.h), + ...widget.selectedServices.map((service) { + final serviceName = isArabic ? (service.textN ?? service.text ?? '') : (service.text ?? ''); + final price = service.priceTotal ?? 0.0; + return Padding( + padding: EdgeInsets.only(bottom: 4.h), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: serviceName.toText14( + weight: FontWeight.w600, + color: AppColors.blackColor, + letterSpacing: -0.5, + maxlines: 2, + ), + ), + ], + ), + ); + }), + ], + ), + ); + } + + Widget _buildBottomButton() { + return SafeArea( + top: false, + child: Container( + padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h), + decoration: BoxDecoration( + color: AppColors.whiteColor, + boxShadow: [ + BoxShadow( + color: Color.fromARGB(13, 0, 0, 0), + blurRadius: 8, + offset: Offset(0, -2), + ), + ], + ), + child: CustomButton( + text: "Confirm".needTranslation, + onPressed: _handleConfirm, + textColor: AppColors.whiteColor, + backgroundColor: AppColors.successColor, + borderRadius: 12.r, + borderColor: Colors.transparent, + borderWidth: 0, + padding: EdgeInsets.symmetric(vertical: 14.h), + ), + ), + ); + } + + void _launchDirections(HospitalsModel selectedHospital) { + final double lat = double.parse(selectedHospital.latitude ?? "0.0"); + final double lng = double.parse(selectedHospital.longitude ?? "0.0"); + + if (lat != 0.0 && lng != 0.0) { + MapsLauncher.launchCoordinates( + lat, + lng, + selectedHospital.name ?? "Hospital", + ); + } + } + + showSuccessBottomSheet(int requestId, HmgServicesViewModel hmgServicesViewModel) { + return showCommonBottomSheetWithoutHeight( + context, + child: Padding( + padding: EdgeInsets.all(16.w), + child: Column( + children: [ + Utils.getSuccessWidget(loadingText: "Your request has been successfully submitted.".needTranslation), + Row( + children: [ + "Here is your request #: ".needTranslation.toText14( + color: AppColors.textColorLight, + weight: FontWeight.w500, + ), + SizedBox(width: 4.w), + ("$requestId").toText16(isBold: true), + ], + ), + SizedBox(height: 24.h), + Row( + children: [ + Expanded( + child: CustomButton( + height: 56.h, + text: LocaleKeys.ok.tr(), + onPressed: () { + context.pop(); + context.pop(); + hmgServicesViewModel.getAllHhcOrders(); + }, + textColor: AppColors.whiteColor, + ), + ), + ], + ), + ], + ), + ), + isCloseButtonVisible: false, + isDismissible: false, + isFullScreen: false, + ); + } + + void _handleConfirm() { + final hmgServicesViewModel = context.read(); + final appState = getIt.get(); + + return showCommonBottomSheetWithoutHeight( + title: LocaleKeys.notice.tr(context: context), + context, + child: Utils.getWarningWidget( + loadingText: "Are you sure you want to submit this request?".needTranslation, + isShowActionButtons: true, + onCancelTap: () { + Navigator.pop(context); + }, + onConfirmTap: () async { + Navigator.pop(context); + LoaderBottomSheet.showLoader(); + + // Create the services list from all selected services + final servicesList = widget.selectedServices.map((selectedService) { + return PatientERCMCInsertServicesList( + recordID: selectedService.iD, + serviceID: selectedService.serviceID, + selectedServiceName: selectedService.text, + selectedServiceNameAR: selectedService.textN, + price: selectedService.price, + vAT: selectedService.priceVAT, + totalPrice: selectedService.priceTotal, + ); + }).toList(); + + // For HHC, we don't need hospital selection, use a default projectID or 0 + await hmgServicesViewModel.addHhcOrder( + projectID: 0, + // HHC doesn't require hospital/project selection + orderServiceID: widget.selectedServices.first.orderServiceID ?? 4, + // HHC service ID + services: servicesList, + onSuccess: (requestId) { + LoaderBottomSheet.hideLoader(); + showSuccessBottomSheet(requestId, hmgServicesViewModel); + }, + onError: (err) { + LoaderBottomSheet.hideLoader(); + }, + ); + }), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } +} diff --git a/lib/presentation/home_health_care/widgets/hhc_ui_selection_helper.dart b/lib/presentation/home_health_care/widgets/hhc_ui_selection_helper.dart new file mode 100644 index 0000000..688612c --- /dev/null +++ b/lib/presentation/home_health_care/widgets/hhc_ui_selection_helper.dart @@ -0,0 +1,90 @@ +import 'package:easy_localization/easy_localization.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/core/utils/utils.dart'; +import 'package:hmg_patient_app_new/extensions/route_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_view_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/req_models/order_update_req_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_all_orders_resp_model.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart'; +import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart'; +import 'package:provider/provider.dart'; + +class HhcUiSelectionHelper { + static void showCancelConfirmationDialog({ + required BuildContext context, + required GetCMCAllOrdersResponseModel order, + }) { + final HmgServicesViewModel hmgServicesViewModel = context.read(); + + return showCommonBottomSheetWithoutHeight( + title: LocaleKeys.notice.tr(context: context), + context, + child: Utils.getWarningWidget( + loadingText: "Are you sure you want to cancel this order?".needTranslation, + isShowActionButtons: true, + onCancelTap: () { + Navigator.pop(context); + }, + onConfirmTap: () async { + Navigator.pop(context); + LoaderBottomSheet.showLoader(); + + final requestModel = OrderUpdateRequestModel( + presOrderID: order.iD, + rejectionReason: "Cancelled by user", + presOrderStatus: 4, // Cancelled status + editedBy: 3, + ); + + await hmgServicesViewModel.updateHhcPresOrder( + requestModel: requestModel, + onSuccess: (_) async { + LoaderBottomSheet.hideLoader(); + showCommonBottomSheetWithoutHeight( + context, + child: Padding( + padding: EdgeInsets.all(16.w), + child: Column( + children: [ + Utils.getSuccessWidget(loadingText: "Order has been cancelled successfully".needTranslation), + SizedBox(height: 24.h), + Row( + children: [ + Expanded( + child: CustomButton( + height: 56.h, + text: LocaleKeys.ok.tr(), + onPressed: () { + context.pop(); + hmgServicesViewModel.getAllHhcOrders(); + }, + textColor: AppColors.whiteColor, + ), + ), + ], + ), + ], + ), + ), + isCloseButtonVisible: false, + isDismissible: false, + isFullScreen: false, + ); + }, + onError: (error) { + LoaderBottomSheet.hideLoader(); + }, + ); + }, + ), + callBackFunc: () {}, + isFullScreen: false, + isCloseButtonVisible: true, + ); + } +} 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/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 193b639..883ca37 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(), @@ -533,6 +533,7 @@ class _MedicalFilePageState extends State { Navigator.of(context).push( CustomPageRoute( page: PrescriptionDetailPage( + isFromAppointments: false, prescriptionsResponseModel: prescriptionVM.patientPrescriptionOrders[index]), ), ); @@ -564,7 +565,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 +582,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/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, 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( 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 87af39b..58c5206 100644 --- a/lib/presentation/prescriptions/prescriptions_list_page.dart +++ b/lib/presentation/prescriptions/prescriptions_list_page.dart @@ -255,22 +255,20 @@ class _PrescriptionsListPageState extends State { Expanded( flex: 1, child: Container( - height: 40.h, - width: 40.h, - decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + height: 40.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, + fit: BoxFit.contain, ), ), ), @@ -278,9 +276,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/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/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, diff --git a/lib/routes/app_routes.dart b/lib/routes/app_routes.dart index 842710d..a0ee1e5 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -5,6 +5,7 @@ import 'package:hmg_patient_app_new/presentation/authentication/register_step2.d import 'package:hmg_patient_app_new/presentation/comprehensive_checkup/comprehensive_checkup_page.dart'; import 'package:hmg_patient_app_new/presentation/e_referral/e_referral_page_home.dart'; import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart'; +import 'package:hmg_patient_app_new/presentation/home_health_care/hhc_procedures_page.dart'; import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart'; import 'package:hmg_patient_app_new/splashPage.dart'; @@ -17,6 +18,7 @@ class AppRoutes { static const String medicalFilePage = '/medicalFilePage'; static const String eReferralPage = '/erReferralPage'; static const String comprehensiveCheckupPage = '/comprehensiveCheckupPage'; + static const String homeHealthCarePage = '/homeHealthCarePage'; static Map get routes => { initialRoute: (context) => SplashPage(), @@ -26,6 +28,7 @@ class AppRoutes { registerStepTwo: (context) => RegisterNewStep2(), medicalFilePage: (context) => MedicalFilePage(), eReferralPage: (context) => EReferralPage(), - comprehensiveCheckupPage: (context) => ComprehensiveCheckupPage() + comprehensiveCheckupPage: (context) => ComprehensiveCheckupPage(), + homeHealthCarePage: (context) => HhcProceduresPage() }; } diff --git a/lib/theme/colors.dart b/lib/theme/colors.dart index 6ccf8b1..b3abe6a 100644 --- a/lib/theme/colors.dart +++ b/lib/theme/colors.dart @@ -3,21 +3,12 @@ import 'package:flutter/material.dart'; class AppColors { static const transparent = Colors.transparent; static const mainPurple = Color(0xFF7954F7); - static const purpleBg = Color(0xFFAEA4FC); - static const deepPurple = Color(0xFF7C65E7); - static const logoColor = Color(0xFF7C65E7); - static const buttonColor = Color(0xFF6A46F5); - static const splashBgColor = Color(0xFF3C355D); - static const lightGray = Color(0xFFF4F5F7); - static const lightPurple = Color(0xFFB7A3E6); static const scaffoldBgColor = Color(0xFFF8F8F8); static const bottomSheetBgColor = Color(0xFFF8F8FA); static const lightGreyEFColor = Color(0xffeaeaff); static const greyF7Color = Color(0xffF7F7F7); static const lightGrayColor = Color(0xff808080); - static const buttonGrayColor = Color(0xffF1F1F1); - static const lightPurpleAlpha = Color(0x5AB7A3E6); // New UI Colors static const whiteColor = Color(0xFFffffff); @@ -37,11 +28,10 @@ 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); - static const lightGreenColor = Color(0xFF0ccedde); static const textGreenColor = Color(0xFF18C273); static const Color ratingColorYellow = Color(0xFFFFAF15); @@ -57,27 +47,38 @@ class AppColors { static const Color chipPrimaryRedBorderColor = Color(0xFFED1C2B); static const Color chipSecondaryLightRedColor = Color(0xFFFEE9EA); -static const Color successLightColor = Color(0xFF18C273); -static const Color errorLightColor = Color(0xFFED1C2B); -static const Color alertLightColor = Color(0xFFD48D05); -static const Color infoLightColor = Color(0xFF0B85F7); -static const Color warningLightColor = Color(0xFFFFCC00); -static const Color greyLightColor = Color(0xFFEFEFF0); -static const Color thumbColor = Color(0xFF18C273); -static const Color switchBackgroundColor = Color(0x2618C273); + static const Color successLightColor = Color(0xFF18C273); + static const Color errorLightColor = Color(0xFFED1C2B); + static const Color alertLightColor = Color(0xFFD48D05); + static const Color infoLightColor = Color(0xFF0B85F7); + static const Color warningLightColor = Color(0xFFFFCC00); + static const Color greyLightColor = Color(0xFFEFEFF0); + static const Color thumbColor = Color(0xFF18C273); + static const Color switchBackgroundColor = Color(0x2618C273); + + static const Color bottomNAVBorder = Color(0xFFEEEEEE); -static const Color bottomNAVBorder = Color(0xFFEEEEEE); + static const Color quickLoginColor = Color(0xFF666666); -static const Color quickLoginColor = Color(0xFF666666); + static const Color tooltipTextColor = Color(0xFF414D55); + static const Color graphGridColor = Color(0x4D18C273); + static const Color criticalLowAndHigh = Color(0xFFED1C2B); + static const Color highAndLow = Color(0xFFFFAF15); + static const Color labelTextColor = Color(0xFF838383); + static const Color calenderTextColor = Color(0xFFD0D0D0); + static const Color lightGreenButtonColor = Color(0x2618C273); -static const Color tooltipTextColor = Color(0xFF414D55); -static const Color graphGridColor = Color(0x4D18C273); -static const Color criticalLowAndHigh = Color(0xFFED1C2B); -static const Color highAndLow = Color(0xFFFFAF15); -static const Color labelTextColor = Color(0xFF838383); -static const Color calenderTextColor = Color(0xFFD0D0D0); -static const Color lightGreenButtonColor = Color(0x2618C273); + static const Color lightRedButtonColor = Color(0x1AED1C2B); -static const Color lightRedButtonColor = Color(0x1AED1C2B); + // Status Colors + static const Color statusPendingColor = Color(0xffCC9B14); + static const Color statusProcessingColor = Color(0xff2E303A); + static const Color statusCompletedColor = Color(0xff359846); + static const Color statusRejectedColor = Color(0xffD02127); + // Info Banner Colors + static const Color infoBannerBgColor = Color(0xFFFFF4E6); + static const Color infoBannerBorderColor = Color(0xFFFFE5B4); + static const Color infoBannerIconColor = Color(0xFFCC9B14); + static const Color infoBannerTextColor = Color(0xFF856404); } diff --git a/lib/widgets/CustomSwitch.dart b/lib/widgets/CustomSwitch.dart index 784e446..bca4e69 100644 --- a/lib/widgets/CustomSwitch.dart +++ b/lib/widgets/CustomSwitch.dart @@ -21,7 +21,7 @@ class _CustomSwitchState extends State { width: 48.w, height: 30.h, decoration: BoxDecoration( - color: AppColors.switchBackgroundColor , + color: widget.value ? AppColors.switchBackgroundColor : AppColors.greyTextColor, borderRadius: BorderRadius.circular(18), ), child: AnimatedAlign( @@ -32,7 +32,7 @@ class _CustomSwitchState extends State { width: 28.w, height: 28.h, decoration: BoxDecoration( - color: AppColors.thumbColor, + color: widget.value? AppColors.thumbColor : AppColors.greyColor, shape: BoxShape.circle, ), ), 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/buttons/default_button.dart b/lib/widgets/buttons/default_button.dart index eb02c38..d8d8cac 100644 --- a/lib/widgets/buttons/default_button.dart +++ b/lib/widgets/buttons/default_button.dart @@ -1,7 +1,6 @@ -import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:flutter/material.dart'; import 'package:flutter_svg/svg.dart'; -import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; extension WithContainer on Widget { Widget get insideContainer => Container( @@ -26,20 +25,22 @@ class DefaultButton extends StatelessWidget { final double height; final double borderRadius; - const DefaultButton(this.text, this.onPress, - {Key? key, - this.color, - this.isTextExpanded = true, - this.svgIcon, - this.disabledColor, - this.count = 0, - this.textColor = Colors.white, - this.iconData, - this.fontSize, - this.colors, - this.height = 50, - this.borderRadius = 100}) - : super(key: key); + const DefaultButton( + this.text, + this.onPress, { + super.key, + this.color, + this.isTextExpanded = true, + this.svgIcon, + this.disabledColor, + this.count = 0, + this.textColor = Colors.white, + this.iconData, + this.fontSize, + this.colors, + this.height = 50, + this.borderRadius = 100, + }); @override Widget build(BuildContext context) { @@ -48,30 +49,20 @@ class DefaultButton extends StatelessWidget { child: Container( height: height, decoration: BoxDecoration( - borderRadius: BorderRadius.circular(borderRadius), - gradient: onPress == null - ? LinearGradient( - colors: [ - disabledColor ?? const Color(0xffEAEAEA), - disabledColor ?? const Color(0xffEAEAEA), - ], - ) - : LinearGradient( - transform: const GradientRotation(.83), - begin: Alignment.topRight, - end: Alignment.bottomLeft, - colors: colors ?? - [ - AppColors.buttonColor, - AppColors.buttonColor, - ], - ), - ), + borderRadius: BorderRadius.circular(borderRadius), + gradient: onPress == null + ? LinearGradient( + colors: [ + disabledColor ?? const Color(0xffEAEAEA), + disabledColor ?? const Color(0xffEAEAEA), + ], + ) + : null), child: Row( mainAxisAlignment: MainAxisAlignment.center, children: [ if (iconData != null) Icon(iconData, color: textColor), - if (svgIcon != null) SvgPicture.asset(svgIcon ?? "", color: textColor), + if (svgIcon != null) SvgPicture.asset(svgIcon ?? ""), if (!isTextExpanded) Padding( padding: EdgeInsets.only( diff --git a/lib/widgets/chip/app_custom_chip_widget.dart b/lib/widgets/chip/app_custom_chip_widget.dart index b3948e2..6904edc 100644 --- a/lib/widgets/chip/app_custom_chip_widget.dart +++ b/lib/widgets/chip/app_custom_chip_widget.dart @@ -44,6 +44,7 @@ class AppCustomChipWidget extends StatelessWidget { final EdgeInsetsDirectional? labelPadding; final void Function()? onChipTap; final void Function()? onDeleteTap; + @override Widget build(BuildContext context) { final iconS = iconSize ?? 12.w; @@ -65,12 +66,18 @@ class AppCustomChipWidget extends StatelessWidget { child: icon.isNotEmpty ? Chip( avatar: icon.isNotEmpty - ? Utils.buildSvgWithAssets(icon: icon, width: iconS, height: iconS, iconColor: iconHasColor ? iconColor : null, fit: BoxFit.contain) + ? Utils.buildSvgWithAssets( + icon: icon, + width: iconS, + height: iconS, + iconColor: iconHasColor ? iconColor : null, + fit: BoxFit.contain, + ) : SizedBox.shrink(), label: richText ?? labelText!.toText10(weight: FontWeight.w500, letterSpacing: 0, color: textColor), padding: padding, materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - labelPadding: labelPadding ?? EdgeInsetsDirectional.only(start: 2.w, end: deleteIcon?.isNotEmpty == true ? 2.w : 8.w), + labelPadding: labelPadding ?? EdgeInsetsDirectional.only(end: deleteIcon?.isNotEmpty == true ? 2.w : 8.w), backgroundColor: backgroundColor, shape: shape ?? SmoothRectangleBorder( @@ -79,19 +86,21 @@ class AppCustomChipWidget extends StatelessWidget { side: BorderSide(color: AppColors.transparent, width: 1.5), ), deleteIcon: deleteIcon?.isNotEmpty == true - ? InkWell(onTap: onDeleteTap,child:Utils.buildSvgWithAssets( - icon: deleteIcon!, - width: iconS, - height: iconS, - iconColor: deleteIconHasColor ? deleteIconColor : null, - - ),) + ? InkWell( + onTap: onDeleteTap, + child: Utils.buildSvgWithAssets( + icon: deleteIcon!, + width: iconS, + height: iconS, + iconColor: deleteIconHasColor ? deleteIconColor : null, + ), + ) : null, onDeleted: deleteIcon?.isNotEmpty == true ? () {} : null, ) : 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 ?? @@ -100,9 +109,12 @@ 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,child: Utils.buildSvgWithAssets(icon: deleteIcon!, width: iconS, height: iconS, iconColor: deleteIconHasColor ? deleteIconColor : null)) + ? InkWell( + onTap: onDeleteTap, + child: Utils.buildSvgWithAssets( + icon: deleteIcon!, width: iconS, height: iconS, iconColor: deleteIconHasColor ? deleteIconColor : null)) : null, onDeleted: deleteIcon?.isNotEmpty == true ? () {} : null, ), diff --git a/lib/widgets/common_bottom_sheet.dart b/lib/widgets/common_bottom_sheet.dart index 318751f..99ff230 100644 --- a/lib/widgets/common_bottom_sheet.dart +++ b/lib/widgets/common_bottom_sheet.dart @@ -115,8 +115,9 @@ void showCommonBottomSheetWithoutHeight( Widget? titleWidget, bool useSafeArea = false, bool hasBottomPadding = true, + EdgeInsets? padding, Color backgroundColor = AppColors.bottomSheetBgColor, - VoidCallback? onCloseClicked + VoidCallback? onCloseClicked, }) { showModalBottomSheet( sheetAnimationStyle: AnimationStyle( @@ -124,7 +125,8 @@ void showCommonBottomSheetWithoutHeight( reverseDuration: Duration(milliseconds: 300), ), constraints: BoxConstraints( - maxWidth: MediaQuery.of(context).size.width, // Full width + + maxWidth: MediaQuery.sizeOf(context).width//MediaQuery.of(context).size.width, // Full width ), context: context, isScrollControlled: true, @@ -146,7 +148,7 @@ void showCommonBottomSheetWithoutHeight( physics: ClampingScrollPhysics(), child: isCloseButtonVisible ? Container( - padding: EdgeInsets.only( + padding: padding ?? const EdgeInsets.only( left: 24, top: 24, right: 24, @@ -160,25 +162,28 @@ void showCommonBottomSheetWithoutHeight( mainAxisSize: MainAxisSize.min, crossAxisAlignment: CrossAxisAlignment.center, children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - crossAxisAlignment: CrossAxisAlignment.center, - children: [ - titleWidget ?? - Expanded( - child: title.toText20(weight: FontWeight.w600), - ), - if (isCloseButtonVisible) ...[ - Utils.buildSvgWithAssets( - icon: AppAssets.close_bottom_sheet_icon, - iconColor: Color(0xff2B353E), - ).onPress(() { - onCloseClicked?.call(); - Navigator.of(context).pop(); - }),], - ], + Padding( + padding: padding != null? EdgeInsets.symmetric(horizontal: 24.w): EdgeInsets.zero, + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + titleWidget ?? + Expanded( + child: title.toText20(weight: FontWeight.w600), + ), + if (isCloseButtonVisible) ...[ + Utils.buildSvgWithAssets( + icon: AppAssets.close_bottom_sheet_icon, + iconColor: Color(0xff2B353E), + ).onPress(() { + onCloseClicked?.call(); + Navigator.of(context).pop(); + }),], + ], + ), ), - SizedBox(height: 16.h), + isCloseButtonVisible ? SizedBox(height: 16.h) : SizedBox.shrink(), child, ], ), diff --git a/lib/widgets/dialogs/confirm_dialog.dart b/lib/widgets/dialogs/confirm_dialog.dart index f753681..ce597c2 100644 --- a/lib/widgets/dialogs/confirm_dialog.dart +++ b/lib/widgets/dialogs/confirm_dialog.dart @@ -15,7 +15,7 @@ class ConfirmDialog extends StatelessWidget { final VoidCallback? onTap; final VoidCallback? onCloseTap; - const ConfirmDialog({Key? key, this.title, required this.message, this.okTitle, this.onTap, this.onCloseTap}) : super(key: key); + const ConfirmDialog({super.key, this.title, required this.message, this.okTitle, this.onTap, this.onCloseTap}); @override Widget build(BuildContext context) { diff --git a/lib/widgets/input_widget.dart b/lib/widgets/input_widget.dart index d943731..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, @@ -205,9 +214,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; }); @@ -240,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, @@ -255,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, 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 diff --git a/lib/widgets/radio_list_tile_widget.dart b/lib/widgets/radio_list_tile_widget.dart index 209866b..9025e19 100644 --- a/lib/widgets/radio_list_tile_widget.dart +++ b/lib/widgets/radio_list_tile_widget.dart @@ -46,7 +46,7 @@ class RadioListTileWidget extends StatelessWidget { decoration: BoxDecoration( shape: BoxShape.circle, border: Border.all( - color: isSelected ? AppColors.blackColor : const Color(0xff9E9E9E), + color: isSelected ? AppColors.primaryRedColor : const Color(0xff9E9E9E), width: 2, ), ), @@ -57,7 +57,7 @@ class RadioListTileWidget extends StatelessWidget { height: 10.h, decoration: BoxDecoration( shape: BoxShape.circle, - color: AppColors.blackColor, + color: AppColors.primaryRedColor, ), ), )