diff --git a/assets/images/png/cc_ar.png b/assets/images/png/cc_ar.png new file mode 100644 index 0000000..e4388ba Binary files /dev/null and b/assets/images/png/cc_ar.png differ diff --git a/assets/images/png/cc_en.png b/assets/images/png/cc_en.png new file mode 100644 index 0000000..c11cf5e Binary files /dev/null and b/assets/images/png/cc_en.png differ 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/comprehensive_checkup.svg b/assets/images/svg/comprehensive_checkup.svg new file mode 100644 index 0000000..885d9a7 --- /dev/null +++ b/assets/images/svg/comprehensive_checkup.svg @@ -0,0 +1,6 @@ + + + + + + diff --git a/assets/images/svg/e-referral.svg b/assets/images/svg/e-referral.svg new file mode 100644 index 0000000..3262779 --- /dev/null +++ b/assets/images/svg/e-referral.svg @@ -0,0 +1,7 @@ + + + + + + + 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 162f0fd..3fb1470 100644 --- a/lib/core/api/api_client.dart +++ b/lib/core/api/api_client.dart @@ -103,7 +103,7 @@ class ApiClientImp implements ApiClient { url = endPoint; } else { if (isRCService) { - url = RC_BASE_URL + endPoint; + url = ApiConsts.rcBaseUrl + endPoint; } else { url = ApiConsts.baseUrl + endPoint; } @@ -161,11 +161,10 @@ class ApiClientImp implements ApiClient { // body['VersionID'] = ApiConsts.appVersionID.toString(); if (!isExternal) { - body['VersionID'] = "50.0"; - body['Channel'] = ApiConsts.appChannelId.toString(); + body['VersionID'] = ApiConsts.appVersionID; + body['Channel'] = ApiConsts.appChannelId; body['IPAdress'] = ApiConsts.appIpAddress; body['generalid'] = ApiConsts.appGeneralId; - body['LanguageID'] = _appState.getLanguageID().toString(); body['Latitude'] = _appState.userLat.toString(); body['Longitude'] = _appState.userLong.toString(); @@ -184,9 +183,6 @@ 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); @@ -202,7 +198,10 @@ class ApiClientImp implements ApiClient { final response = await http.post(Uri.parse(url.trim()), body: json.encode(body), headers: headers); final int statusCode = response.statusCode; + 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); @@ -213,13 +212,14 @@ class ApiClientImp implements ApiClient { onSuccess(parsed, statusCode, messageStatus: 1, errorMessage: ""); } else { onSuccess(parsed, statusCode, - messageStatus: parsed.contains('MessageStatus') ? parsed['MessageStatus'] : 1, - errorMessage: parsed.contains('ErrorEndUserMessage') ? parsed['ErrorEndUserMessage'] : ""); + messageStatus: (parsed is Map && parsed.containsKey('MessageStatus')) ? parsed['MessageStatus'] : 1, + errorMessage: (parsed is Map && parsed.containsKey('ErrorEndUserMessage')) ? parsed['ErrorEndUserMessage'] : ""); } } else { if (parsed['Response_Message'] != null) { onSuccess(parsed, statusCode, - messageStatus: parsed['MessageStatus'], errorMessage: parsed['ErrorEndUserMessage'] ?? parsed['ErrorMessage']); + messageStatus: (parsed is Map && parsed.containsKey('MessageStatus')) ? parsed['MessageStatus'] : 1, + errorMessage: (parsed is Map && parsed.containsKey('ErrorEndUserMessage')) ? parsed['ErrorEndUserMessage'] : ""); } else { if (parsed['ErrorType'] == 4) { //TODO : handle app update @@ -352,9 +352,9 @@ class ApiClientImp implements ApiClient { url = endPoint; } else { if (isRCService) { - url = RC_BASE_URL + endPoint; + url = ApiConsts.rcBaseUrl + endPoint; } else { - url = BASE_URL + endPoint; + url = ApiConsts.baseUrl + endPoint; } } if (queryParams != null) { @@ -365,7 +365,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 47355e6..49b7be3 100644 --- a/lib/core/api_consts.dart +++ b/lib/core/api_consts.dart @@ -14,8 +14,8 @@ var PACKAGES_ORDERS = '/api/orders'; var PACKAGES_ORDER_HISTORY = '/api/orders/items'; var PACKAGES_TAMARA_OPT = '/api/orders/paymentoptions/tamara'; // var BASE_URL = 'http://10.50.100.198:2018/'; -// var BASE_URL = 'https://uat.hmgwebservices.com/'; -var BASE_URL = 'https://hmgwebservices.com/'; +var BASE_URL = 'https://uat.hmgwebservices.com/'; +// var BASE_URL = 'https://hmgwebservices.com/'; // var BASE_URL = 'http://10.201.204.103/'; // var BASE_URL = 'https://orash.cloudsolutions.com.sa/'; // var BASE_URL = 'https://vidauat.cloudsolutions.com.sa/'; @@ -46,8 +46,6 @@ var PHARMACY_REDIRECT_URL = 'https://bit.ly/AlhabibPharmacy'; // RC API URL // var RC_BASE_URL = 'https://rc.hmg.com/'; -var RC_BASE_URL = 'https://rc.hmg.com/uat/'; - // var RC_BASE_URL = 'https://ms.hmg.com/rc/'; var PING_SERVICE = 'Services/Weather.svc/REST/CheckConnectivity'; @@ -521,12 +519,6 @@ var ADD_HHC_ORDER_RC = "api/HHC/add"; var GET_ALL_HHC_ORDERS_RC = 'api/hhc/list'; var UPDATE_HHC_ORDER_RC = 'api/hhc/update'; -// 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'; - // RRT RC SERVICES var ADD_RRT_ORDER_RC = "api/rrt/add"; var GET_ALL_RRT_ORDERS_RC = "api/rrt/list"; @@ -725,7 +717,7 @@ class ApiConsts { static String baseUrl = 'https://hmgwebservices.com/'; // HIS API URL PROD - static String RCBaseUrl = 'https://rc.hmg.com/'; // RC API URL PROD + static String rcBaseUrl = 'https://rc.hmg.com/'; // RC API URL PROD static var payFortEnvironment = FortEnvironment.production; static var applePayMerchantId = "merchant.com.hmgwebservices"; @@ -752,7 +744,7 @@ class ApiConsts { TAMARA_URL = "https://mdlaboratories.com/tamaralive/Home/Checkout"; GET_TAMARA_INSTALLMENTS_URL = "https://mdlaboratories.com/tamaralive/Home/GetInstallments"; GET_TAMARA_PAYMENT_STATUS = 'https://mdlaboratories.com/tamaralive/api/OnlineTamara/order_status?orderid='; - RCBaseUrl = 'https://rc.hmg.com/'; + rcBaseUrl = 'https://rc.hmg.com/'; break; case AppEnvironmentTypeEnum.dev: baseUrl = "https://uat.hmgwebservices.com/"; @@ -762,7 +754,7 @@ class ApiConsts { TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout"; GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments"; GET_TAMARA_PAYMENT_STATUS = 'https://epharmacy.hmg.com/tamara/api/OnlineTamara/order_status?orderid='; - RCBaseUrl = 'https://rc.hmg.com/test/'; + rcBaseUrl = 'https://rc.hmg.com/'; break; case AppEnvironmentTypeEnum.uat: baseUrl = "https://uat.hmgwebservices.com/"; @@ -772,7 +764,7 @@ class ApiConsts { TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout"; GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments"; GET_TAMARA_PAYMENT_STATUS = 'https://epharmacy.hmg.com/tamara/api/OnlineTamara/order_status?orderid='; - RCBaseUrl = 'https://rc.hmg.com/test/'; + rcBaseUrl = 'https://rc.hmg.com/'; break; case AppEnvironmentTypeEnum.preProd: baseUrl = "https://webservices.hmg.com/"; @@ -782,7 +774,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/'; break; case AppEnvironmentTypeEnum.qa: baseUrl = "https://uat.hmgwebservices.com/"; @@ -792,7 +784,7 @@ class ApiConsts { TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout"; GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments"; GET_TAMARA_PAYMENT_STATUS = 'https://epharmacy.hmg.com/tamara/api/OnlineTamara/order_status?orderid='; - RCBaseUrl = 'https://rc.hmg.com/test/'; + rcBaseUrl = 'https://rc.hmg.com/'; break; case AppEnvironmentTypeEnum.staging: baseUrl = "https://uat.hmgwebservices.com/"; @@ -802,7 +794,7 @@ class ApiConsts { TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout"; GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments"; GET_TAMARA_PAYMENT_STATUS = 'https://epharmacy.hmg.com/tamara/api/OnlineTamara/order_status?orderid='; - RCBaseUrl = 'https://rc.hmg.com/test/'; + rcBaseUrl = 'https://rc.hmg.com/'; break; } } @@ -847,8 +839,21 @@ class ApiConsts { static final String createAdvancePayments = 'Services/Patients.svc/REST/HIS_CreateAdvancePayment'; static final String addAdvanceNumberRequest = 'Services/PayFort_Serv.svc/REST/AddAdvancedNumberRequest'; + // 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'; + + // 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'; + // ************ static values for Api **************** - static final double appVersionID = 18.7; + static final double appVersionID = 20.0; static final int appChannelId = 3; static final String appIpAddress = "10.20.10.20"; static final String appGeneralId = "Cs2020@2016\$2958"; diff --git a/lib/core/app_assets.dart b/lib/core/app_assets.dart index e8215ba..5fccc6e 100644 --- a/lib/core/app_assets.dart +++ b/lib/core/app_assets.dart @@ -171,6 +171,10 @@ class AppAssets { static const String to_arrow = '$svgBasePath/to_arrow.svg'; static const String dual_arrow = '$svgBasePath/to_arrow.svg'; 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// @@ -200,6 +204,8 @@ class AppAssets { static const String visa = '$pngBasePath/visa.png'; static const String lockIcon = '$pngBasePath/lock-icon.png'; static const String dummy_user = '$pngBasePath/dummy_user.png'; + static const String comprehensiveCheckupEn = '$pngBasePath/cc_en.png'; + static const String comprehensiveCheckupAr = '$pngBasePath/cc_er.png'; } class AppAnimations { diff --git a/lib/core/dependencies.dart b/lib/core/dependencies.dart index 89dea0b..184ac17 100644 --- a/lib/core/dependencies.dart +++ b/lib/core/dependencies.dart @@ -17,6 +17,8 @@ import 'package:hmg_patient_app_new/features/emergency_services/emergency_servic import 'package:hmg_patient_app_new/features/emergency_services/emergency_services_view_model.dart'; import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_repo.dart'; import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_repo.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_view_model.dart'; import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_repo.dart'; import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_view_model.dart'; import 'package:hmg_patient_app_new/features/insurance/insurance_repo.dart'; @@ -108,49 +110,31 @@ class AppDependencies { getIt.registerLazySingleton(() => InsuranceRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => PayfortRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton( - () => LocalAuthService(loggerService: getIt(), localAuth: getIt())); + () => LocalAuthService(loggerService: getIt(), localAuth: getIt()), + ); getIt.registerLazySingleton(() => HabibWalletRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => MedicalFileRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => ImmediateLiveCareRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => EmergencyServicesRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => TodoSectionRepoImp(loggerService: getIt(), apiClient: getIt())); - getIt.registerLazySingleton( - () => LocationRepoImpl(apiClient: getIt())); + getIt.registerLazySingleton(() => LocationRepoImpl(apiClient: getIt())); getIt.registerLazySingleton(() => ContactUsRepoImp(loggerService: getIt(), apiClient: getIt())); + getIt.registerLazySingleton(() => HmgServicesRepoImp(loggerService: getIt(), apiClient: getIt())); getIt.registerLazySingleton(() => ActivePrescriptionsRepoImp(loggerService: getIt(), apiClient: getIt())); - // ViewModels // Global/shared VMs → LazySingleton - getIt.registerLazySingleton( - () => LabViewModel(labRepo: getIt(), errorHandlerService: getIt(), navigationService: getIt()), - ); + getIt.registerLazySingleton(() => LabViewModel(labRepo: getIt(), errorHandlerService: getIt(), navigationService: getIt())); - getIt.registerLazySingleton( - () => RadiologyViewModel( - radiologyRepo: getIt(), - errorHandlerService: getIt(), - ), - ); + getIt.registerLazySingleton(() => RadiologyViewModel(radiologyRepo: getIt(), errorHandlerService: getIt())); - getIt.registerLazySingleton( - () => PrescriptionsViewModel( - prescriptionsRepo: getIt(), - errorHandlerService: getIt(), - ), - ); + getIt.registerLazySingleton(() => PrescriptionsViewModel(prescriptionsRepo: getIt(), errorHandlerService: getIt())); - getIt.registerLazySingleton( - () => InsuranceViewModel( - insuranceRepo: getIt(), - errorHandlerService: getIt(), - ), - ); + getIt.registerLazySingleton(() => InsuranceViewModel(insuranceRepo: getIt(), errorHandlerService: getIt())); getIt.registerLazySingleton( - () => MyAppointmentsViewModel(myAppointmentsRepo: getIt(), errorHandlerService: getIt(), appState: getIt()), - ); + () => MyAppointmentsViewModel(myAppointmentsRepo: getIt(), errorHandlerService: getIt(), appState: getIt())); getIt.registerLazySingleton( () => PayfortViewModel( @@ -204,53 +188,39 @@ class AppDependencies { ); getIt.registerLazySingleton(() => ProfileSettingsViewModel()); - getIt.registerLazySingleton( - () => DateRangeSelectorRangeViewModel(), - ); + getIt.registerLazySingleton(() => DateRangeSelectorRangeViewModel()); - getIt.registerLazySingleton( - () => DoctorFilterViewModel(), - ); + getIt.registerLazySingleton(() => DoctorFilterViewModel()); getIt.registerLazySingleton( - () => AppointmentViaRegionViewmodel( - navigationService: getIt(), - appState: getIt(), - ), + () => AppointmentViaRegionViewmodel(navigationService: getIt(), appState: getIt()), ); getIt.registerLazySingleton( () => EmergencyServicesViewModel( - locationUtils: getIt(), - navServices: getIt(), - emergencyServicesRepo: getIt(), - appState: getIt(), - errorHandlerService: getIt(), - appointmentRepo: getIt(), - dialogService: getIt() - ), + locationUtils: getIt(), + navServices: getIt(), + emergencyServicesRepo: getIt(), + appState: getIt(), + errorHandlerService: getIt(), + appointmentRepo: getIt(), + dialogService: getIt()), ); getIt.registerLazySingleton( - () => LocationViewModel( - locationRepo: getIt(), - errorHandlerService: getIt(), - ), + () => LocationViewModel(locationRepo: getIt(), errorHandlerService: getIt()), ); getIt.registerLazySingleton( - () => ContactUsViewModel( - contactUsRepo: getIt(), - appState: getIt(), - errorHandlerService: getIt(), - ), + () => ContactUsViewModel(contactUsRepo: getIt(), appState: getIt(), errorHandlerService: getIt()), ); getIt.registerLazySingleton( - () => TodoSectionViewModel( - todoSectionRepo: getIt(), - errorHandlerService: getIt(), - ), + () => TodoSectionViewModel(todoSectionRepo: getIt(), errorHandlerService: getIt()), + ); + + getIt.registerLazySingleton( + () => HmgServicesViewModel(bookAppointmentsRepo: getIt(), hmgServicesRepo: getIt(), errorHandlerService: getIt()), ); getIt.registerLazySingleton( diff --git a/lib/core/utils/date_util.dart b/lib/core/utils/date_util.dart index d58aef6..a918706 100644 --- a/lib/core/utils/date_util.dart +++ b/lib/core/utils/date_util.dart @@ -6,19 +6,19 @@ class DateUtil { /// convert String To Date function /// [date] String we want to convert static DateTime convertStringToDate(String? date) { - print("the date is $date"); + if (date == null) return DateTime.now(); if (date.isEmpty) return DateTime.now(); - const start = "/Date("; - const end = "+0300)"; - final startIndex = date.indexOf(start); - final endIndex = date.indexOf(end, startIndex + start.length); - return DateTime.fromMillisecondsSinceEpoch(int.parse( - date.substring(startIndex + start.length, endIndex), - )); - + const start = "/Date("; + const end = "+0300)"; + final startIndex = date.indexOf(start); + final endIndex = date.indexOf(end, startIndex + start.length); + return DateTime.fromMillisecondsSinceEpoch(int.parse( + date.substring(startIndex + start.length, endIndex), + )) + ; } static DateTime convertStringToDateSaudiTimezone(String date, int projectId) { @@ -36,10 +36,10 @@ class DateUtil { // .add(Duration(hours: 4)); // } else { return DateTime.fromMillisecondsSinceEpoch( - int.parse( - date.substring(startIndex + start.length, endIndex), - ), - isUtc: true) + int.parse( + date.substring(startIndex + start.length, endIndex), + ), + isUtc: true) .add(Duration(hours: 3)); // } } else { @@ -156,7 +156,13 @@ class DateUtil { static String getDateFormatted(String date) { DateTime dateObj = DateUtil.convertStringToDate(date); - return DateUtil.getWeekDay(dateObj.weekday) + ", " + dateObj.day.toString() + " " + DateUtil.getMonth(dateObj.month) + " " + dateObj.year.toString(); + return DateUtil.getWeekDay(dateObj.weekday) + + ", " + + dateObj.day.toString() + + " " + + DateUtil.getMonth(dateObj.month) + + " " + + dateObj.year.toString(); } static String getISODateFormat(DateTime dateTime) { @@ -352,7 +358,13 @@ class DateUtil { if (dateTime != null) { return lang == 'en' ? getWeekDayEnglish(dateTime.weekday) + ", " + getMonth(dateTime.month) + " " + dateTime.day.toString() + " " + dateTime.year.toString() - : getWeekDayArabic(dateTime.weekday) + ", " + dateTime.day.toString() + " " + getMonthArabic(dateTime.month) + " " + dateTime.year.toString(); + : getWeekDayArabic(dateTime.weekday) + + ", " + + dateTime.day.toString() + + " " + + getMonthArabic(dateTime.month) + + " " + + dateTime.year.toString(); } else { return ""; } @@ -381,7 +393,9 @@ class DateUtil { static String getMonthYearLangDateFormatted(DateTime dateTime, String lang) { if (dateTime != null) { - return lang == 'en' ? getMonth(dateTime.month) + " " + dateTime.year.toString() : getMonthArabic(dateTime.month) + " " + dateTime.year.toString(); + return lang == 'en' + ? getMonth(dateTime.month) + " " + dateTime.year.toString() + : getMonthArabic(dateTime.month) + " " + dateTime.year.toString(); } else { return ""; } @@ -488,10 +502,8 @@ class DateUtil { } } - -extension OnlyDate on DateTime{ - - DateTime provideDateOnly(){ +extension OnlyDate on DateTime { + DateTime provideDateOnly() { return DateTime(this.year, month, day); } -} \ No newline at end of file +} 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..3c1765d 100644 --- a/lib/extensions/string_extensions.dart +++ b/lib/extensions/string_extensions.dart @@ -223,6 +223,7 @@ extension EmailValidator on String { FontWeight? weight, TextOverflow? textOverflow, double? letterSpacing = -0.4, + Color decorationColor =AppColors.errorColor }) => Text( this, @@ -236,6 +237,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/emergency_services/emergency_services_repo.dart b/lib/features/emergency_services/emergency_services_repo.dart index c54c3d1..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}); @@ -44,13 +46,18 @@ abstract class EmergencyServicesRepo { Future>>> getTransportationMethods({int? id}); - Future>> submitAmbulanceRequest(PatientER_RC request); Future>>> getTransportationOrders({int? id}); Future>> cancelOrder(int? iD, int patientId); + Future>> submitRRTRequest(RRTRequestModel request); + + Future>> getRRTOrders({int? id}); + + Future>> cancelRRTOrder(int? iD); + Future>> getTermsAndCondition(); } class EmergencyServicesRepoImp implements EmergencyServicesRepo { @@ -97,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; @@ -150,7 +157,10 @@ class EmergencyServicesRepoImp implements EmergencyServicesRepo { onSuccess: (response, statusCode, {messageStatus, errorMessage}) { try { final list = response['response']['transportationservices']; - final proceduresList = list.map((item) => PatientERTransportationMethod.fromJson(item as Map)).toList().cast(); + final proceduresList = list + .map((item) => PatientERTransportationMethod.fromJson(item as Map)) + .toList() + .cast(); apiResponse = GenericApiModel>( messageStatus: messageStatus, @@ -249,7 +259,7 @@ class EmergencyServicesRepoImp implements EmergencyServicesRepo { Failure? failure; await apiClient.post( body: {}, - "$GET_ALL_TRANSPORTATIONS_ORDERS?patientID=$id", + "$GET_ALL_TRANSPORTATIONS_ORDERS?patientID=$id", isRCService: true, onFailure: (error, statusCode, {messageStatus, failureType}) { failure = failureType; @@ -257,7 +267,8 @@ class EmergencyServicesRepoImp implements EmergencyServicesRepo { onSuccess: (response, statusCode, {messageStatus, errorMessage}) { try { final list = response['response']; - final proceduresList = list.map((item) => AmbulanceRequestOrdersModel.fromJson(item as Map)).toList().cast(); + final proceduresList = + list.map((item) => AmbulanceRequestOrdersModel.fromJson(item as Map)).toList().cast(); apiResponse = GenericApiModel>( messageStatus: messageStatus, @@ -327,13 +338,11 @@ class EmergencyServicesRepoImp implements EmergencyServicesRepo { await apiClient.post( CHECK_PATIENT_ER_ADVANCE_BALANCE, body: mapDevice, - onFailure: (error, statusCode, {messageStatus, failureType}) { - failure = failureType; - }, + onFailure: (error, statusCode, {messageStatus, failureType}) => failure = failureType, onSuccess: (response, statusCode, {messageStatus, errorMessage}) { try { final bool patientHasERBalance = response['BalanceAmount'] > 0; - print(patientHasERBalance); + log(patientHasERBalance.toString()); apiResponse = GenericApiModel( messageStatus: messageStatus, statusCode: statusCode, @@ -353,7 +362,6 @@ class EmergencyServicesRepoImp implements EmergencyServicesRepo { } } - @override Future>> checkPatientERPaymentInformation({int? projectID}) async { Map mapDevice = {"ClinicID": 10, "ProjectID": projectID ?? 0}; @@ -559,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 new file mode 100644 index 0000000..254d309 --- /dev/null +++ b/lib/features/hmg_services/hmg_services_repo.dart @@ -0,0 +1,522 @@ +import 'dart:developer'; + +import 'package:dartz/dartz.dart'; +import 'package:hmg_patient_app_new/core/api/api_client.dart'; +import 'package:hmg_patient_app_new/core/api_consts.dart'; +import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart'; +import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart'; +import 'package:hmg_patient_app_new/features/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_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'; + +abstract class HmgServicesRepo { + 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, + }); +} + +class HmgServicesRepoImp implements HmgServicesRepo { + final ApiClient apiClient; + final LoggerService loggerService; + + HmgServicesRepoImp({required this.apiClient, required this.loggerService}); + + @override + Future>>> getAllComprehensiveCheckupOrders() async { + Map requestBody = {}; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + + await apiClient.post( + ApiConsts.allCMCOrdersRc, + isRCService: true, + body: requestBody, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + loggerService.logError("CMC 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 CMC 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 getAllCmcOrders: ${e.toString()}"); + return Left(UnknownFailure(e.toString())); + } + } + + @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 = {}; + + try { + GenericApiModel>? apiResponse; + Failure? failure; + + await apiClient.post( + '${ApiConsts.allCMCServicesRc}?patientID=$patientID', + isRCService: true, + isAllowAny: true, + body: requestBody, + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + loggerService.logError("CMC Services API Failed: $error, Status: $statusCode"); + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + List cmcServicesList = []; + + if (response['response'] != null && response['response'] is List) { + final servicesList = response['response'] as List; + + for (var serviceJson in servicesList) { + if (serviceJson is Map) { + cmcServicesList.add(GetCMCServicesResponseModel.fromJson(serviceJson)); + } + } + } + + apiResponse = GenericApiModel>( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: cmcServicesList, + ); + } catch (e) { + loggerService.logError("Error parsing CMC 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 getAllCmcServices: ${e.toString()}"); + return Left(UnknownFailure(e.toString())); + } + } + + @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 { + GenericApiModel? apiResponse; + Failure? failure; + + await apiClient.post( + ApiConsts.updateCMCOrder, + isRCService: true, + body: requestModel.toJson(), + onFailure: (error, statusCode, {messageStatus, failureType}) { + failure = failureType; + loggerService.logError("Update CMC Order API Failed: $error, Status: $statusCode"); + }, + onSuccess: (response, statusCode, {messageStatus, errorMessage}) { + try { + apiResponse = GenericApiModel( + messageStatus: messageStatus, + statusCode: statusCode, + errorMessage: errorMessage, + data: true, + ); + + loggerService.logInfo("CMC Order updated successfully: PresOrderID=${requestModel.presOrderID}"); + } catch (e) { + loggerService.logError("Error processing update 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 updateCmcPresOrder: ${e.toString()}"); + return Left(UnknownFailure(e.toString())); + } + } + + @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())); + } + } +} diff --git a/lib/features/hmg_services/hmg_services_view_model.dart b/lib/features/hmg_services/hmg_services_view_model.dart new file mode 100644 index 0000000..24b7fff --- /dev/null +++ b/lib/features/hmg_services/hmg_services_view_model.dart @@ -0,0 +1,515 @@ +import 'package:flutter/material.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/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_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'; + +class HmgServicesViewModel extends ChangeNotifier { + final HmgServicesRepo hmgServicesRepo; + final BookAppointmentsRepo bookAppointmentsRepo; + final ErrorHandlerService 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; + + // 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, + }) async { + isCmcOrdersLoading = true; + notifyListeners(); + + final result = await hmgServicesRepo.getAllComprehensiveCheckupOrders(); + + result.fold( + (failure) async { + isCmcOrdersLoading = false; + notifyListeners(); + await errorHandlerService.handleError(failure: failure); + if (onError != null) { + onError(failure.toString()); + } + }, + (apiResponse) { + isCmcOrdersLoading = false; + if (apiResponse.messageStatus == 1) { + cmcOrdersList = apiResponse.data ?? []; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } else { + notifyListeners(); + if (onError != null) { + onError(apiResponse.errorMessage ?? 'Unknown error'); + } + } + }, + ); + } + + Future getAllCmcServices({required int patientID, Function(dynamic)? onSuccess, Function(String)? onError}) async { + isCmcServicesLoading = true; + notifyListeners(); + + final result = await hmgServicesRepo.getAllCmcServices(patientID: patientID); + + result.fold( + (failure) async { + isCmcServicesLoading = false; + notifyListeners(); + await errorHandlerService.handleError(failure: failure); + if (onError != null) { + onError(failure.toString()); + } + }, + (apiResponse) { + isCmcServicesLoading = false; + if (apiResponse.messageStatus == 1) { + cmcServicesList = apiResponse.data ?? []; + notifyListeners(); + if (onSuccess != null) { + onSuccess(apiResponse); + } + } else { + notifyListeners(); + if (onError != null) { + onError(apiResponse.errorMessage ?? 'Unknown error'); + } + } + }, + ); + } + + Future updateCmcPresOrder({ + required OrderUpdateRequestModel requestModel, + Function(dynamic)? onSuccess, + Function(String)? onError, + }) async { + isUpdatingOrder = true; + notifyListeners(); + + final result = await hmgServicesRepo.updateCmcPresOrder(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 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(); + } +} diff --git a/lib/features/hmg_services/models/req_models/cmc_create_new_order_req_model.dart b/lib/features/hmg_services/models/req_models/cmc_create_new_order_req_model.dart new file mode 100644 index 0000000..a1fcc4b --- /dev/null +++ b/lib/features/hmg_services/models/req_models/cmc_create_new_order_req_model.dart @@ -0,0 +1,131 @@ +class CMCInsertPresOrderRequestModel { + double? versionID; + int? channel; + int? languageID; + String? iPAdress; + String? generalid; + int? patientOutSA; + String? sessionID; + bool? isDentalAllowedBackend; + int? deviceTypeID; + int? patientID; + String? tokenID; + int? patientTypeID; + int? patientType; + double? latitude; + double? longitude; + int? createdBy; + int? orderServiceID; + int? projectID; + List? patientERCMCInsertServicesList; + + CMCInsertPresOrderRequestModel( + {this.versionID, + this.channel, + this.languageID, + this.iPAdress, + this.generalid, + this.patientOutSA, + this.sessionID, + this.isDentalAllowedBackend, + this.deviceTypeID, + this.patientID, + this.tokenID, + this.patientTypeID, + this.patientType, + this.latitude, + this.longitude, + this.createdBy, + this.orderServiceID, + this.projectID, + this.patientERCMCInsertServicesList}); + + CMCInsertPresOrderRequestModel.fromJson(Map json) { + versionID = json['VersionID']; + channel = json['Channel']; + languageID = json['LanguageID']; + iPAdress = json['IPAdress']; + generalid = json['generalid']; + patientOutSA = json['PatientOutSA']; + sessionID = json['SessionID']; + isDentalAllowedBackend = json['isDentalAllowedBackend']; + deviceTypeID = json['DeviceTypeID']; + patientID = json['PatientID']; + tokenID = json['TokenID']; + patientTypeID = json['PatientTypeID']; + patientType = json['PatientType']; + latitude = json['Latitude']; + longitude = json['Longitude']; + createdBy = json['CreatedBy']; + orderServiceID = json['OrderServiceID']; + projectID = json['ProjectId']; + if (json['PatientER_CMC_InsertServicesList'] != null) { + patientERCMCInsertServicesList = []; + json['PatientER_CMC_InsertServicesList'].forEach((v) { + patientERCMCInsertServicesList!.add( + new PatientERCMCInsertServicesList.fromJson(v), + ); + }); + } + } + + Map toJson() { + final Map data = new Map(); + data['VersionID'] = this.versionID; + data['Channel'] = this.channel; + data['LanguageID'] = this.languageID; + data['IPAdress'] = this.iPAdress; + data['generalid'] = this.generalid; + data['isOutPatient'] = this.patientOutSA == 0 ? false : true; + data['SessionID'] = this.sessionID; + data['isDentalAllowedBackend'] = this.isDentalAllowedBackend; + data['DeviceTypeID'] = this.deviceTypeID; + data['TokenID'] = this.tokenID; + data['PatientTypeID'] = this.patientTypeID; + data['PatientType'] = this.patientType; + data['latitude'] = this.latitude; + data['longitude'] = this.longitude; + // data['CreatedBy'] = this.createdBy; + data['OrderServiceID'] = this.orderServiceID; + data['ProjectID'] = this.projectID; + if (this.patientERCMCInsertServicesList != null) { + data['procedures'] = this.patientERCMCInsertServicesList!.map((v) => v.toJson()).toList(); + } + return data; + } +} + +class PatientERCMCInsertServicesList { + int? recordID; + String? serviceID; + String? selectedServiceName; + String? selectedServiceNameAR; + dynamic price; + dynamic vAT; + dynamic totalPrice; + + PatientERCMCInsertServicesList( + {this.recordID, this.serviceID, this.selectedServiceName, this.selectedServiceNameAR, this.price, this.vAT, this.totalPrice}); + + PatientERCMCInsertServicesList.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 = new Map(); + data['RecordID'] = this.recordID; + data['ServiceID'] = this.serviceID; + data['selectedServiceName'] = this.selectedServiceName; + data['selectedServiceNameAR'] = this.selectedServiceNameAR; + data['Price'] = this.price; + data['VAT'] = this.vAT; + data['TotalPrice'] = this.totalPrice; + return data; + } +} 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/hmg_services/models/req_models/order_update_req_model.dart b/lib/features/hmg_services/models/req_models/order_update_req_model.dart new file mode 100644 index 0000000..0f9964f --- /dev/null +++ b/lib/features/hmg_services/models/req_models/order_update_req_model.dart @@ -0,0 +1,82 @@ +class OrderUpdateRequestModel { + double? versionID; + int? channel; + int? languageID; + String? iPAdress; + String? generalid; + int? patientOutSA; + String? sessionID; + bool? isDentalAllowedBackend; + int? deviceTypeID; + int? patientID; + String? tokenID; + int? patientTypeID; + int? patientType; + int? presOrderID; + int? presOrderStatus; + int? editedBy; + String? rejectionReason; + + OrderUpdateRequestModel({ + this.versionID, + this.channel, + this.languageID, + this.iPAdress, + this.generalid, + this.patientOutSA, + this.sessionID, + this.isDentalAllowedBackend, + this.deviceTypeID, + this.patientID, + this.tokenID, + this.patientTypeID, + this.patientType, + this.presOrderID, + this.presOrderStatus, + this.editedBy, + this.rejectionReason, + }); + + OrderUpdateRequestModel.fromJson(Map json) { + versionID = json['VersionID']; + channel = json['Channel']; + languageID = json['LanguageID']; + iPAdress = json['IPAdress']; + generalid = json['generalid']; + patientOutSA = json['PatientOutSA']; + sessionID = json['SessionID']; + isDentalAllowedBackend = json['isDentalAllowedBackend']; + deviceTypeID = json['DeviceTypeID']; + patientID = json['PatientID']; + tokenID = json['TokenID']; + patientTypeID = json['PatientTypeID']; + patientType = json['PatientType']; + presOrderID = json['PresOrderID']; + presOrderStatus = json['PresOrderStatus']; + editedBy = json['EditedBy']; + rejectionReason = json['RejectionReason']; + } + + Map toJson() { + final Map data = {}; + data['VersionID'] = versionID; + data['Channel'] = channel; + data['LanguageID'] = languageID; + data['IPAdress'] = iPAdress; + data['generalid'] = generalid; + data['PatientOutSA'] = patientOutSA; + data['SessionID'] = sessionID; + data['isDentalAllowedBackend'] = isDentalAllowedBackend; + data['DeviceTypeID'] = deviceTypeID; + data['PatientID'] = patientID; + data['TokenID'] = tokenID; + data['PatientTypeID'] = patientTypeID; + data['PatientType'] = patientType; + data['Id'] = presOrderID; + data['ClickButton'] = 14; + data['PresOrderStatus'] = presOrderStatus; + data['EditedBy'] = editedBy; + data['RejectionReason'] = rejectionReason; + return data; + } +} diff --git a/lib/features/hmg_services/models/resq_models/get_cmc_all_orders_resp_model.dart b/lib/features/hmg_services/models/resq_models/get_cmc_all_orders_resp_model.dart new file mode 100644 index 0000000..ddd91f4 --- /dev/null +++ b/lib/features/hmg_services/models/resq_models/get_cmc_all_orders_resp_model.dart @@ -0,0 +1,344 @@ +import 'dart:developer'; + +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) { + log("responseJson: $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 ? 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 ? 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 = {}; + data['ID'] = iD; + data['PatientId'] = patientId; + data['PatientOutSa'] = patientOutSa; + data['IsOutPatient'] = isOutPatient; + data['ProjectId'] = projectId; + data['NearestProjectId'] = nearestProjectId; + data['Longitude'] = longitude; + data['Latitude'] = latitude; + data['AppointmentNo'] = appointmentNo; + data['DischargeId'] = dischargeId; + data['StatusId'] = statusId; + data['ServiceId'] = serviceId; + data['Channel'] = channel; + if (orderpayment != null) { + data['orderpayment'] = orderpayment!.toJson(); + } + data['orderselectedservice'] = orderselectedservice; + + data['wforder'] = wforder; + data['orderapprovalobj'] = orderapprovalobj; + data['Created'] = created; + data['CreatedBy'] = createdBy; + data['Modified'] = modified; + data['ModifiedBy'] = modifiedBy; + data['IsDeleted'] = isDeleted; + data['StatusText'] = statusText; + data['PaymentStatus'] = paymentStatus; + data['ClientRequestid'] = clientRequestid; + data['PaymentStatusText'] = paymentStatusText; + data['ProjectName'] = projectName; + data['NearestProjectName'] = nearestProjectName; + data['PaymentAmount'] = paymentAmount; + if (wFOrder != null) { + data['WF_order'] = wFOrder!.toJson(); + } + data['ServiceText'] = serviceText; + data['isSentForApproval'] = isSentForApproval; + data['ExaCart_OrderId'] = exaCartOrderId; + data['isTimer'] = isTimer; + data['TimeSeconds'] = timeSeconds; + data['TotalPendingSeconds'] = totalPendingSeconds; + data['TimeMinute'] = timeMinute; + data['TimeHour'] = timeHour; + data['TimeTotalSeconds'] = timeTotalSeconds; + data['TimeTotalMinute'] = timeTotalMinute; + data['TimeTotalHour'] = timeTotalHour; + data['ApprovalStatus'] = approvalStatus; + data['isActive'] = isActive; + data['ClickButton'] = clickButton; + data['PickupLocation'] = pickupLocation; + data['DropOffLocation'] = dropOffLocation; + data['clinicName'] = clinicName; + data['DoctorName'] = doctorName; + data['Branch'] = branch; + data['Time'] = time; + data['Notes'] = 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 = {}; + data['ID'] = iD; + data['OrderId'] = orderId; + data['ClientRequestId'] = clientRequestId; + data['TotalAmount'] = totalAmount; + data['PaymentStatus'] = paymentStatus; + data['Order'] = order; + data['Created'] = created; + data['CreatedBy'] = createdBy; + data['Modified'] = modified; + data['ModifiedBy'] = modifiedBy; + data['IsDeleted'] = 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 = {}; + data['wf_ButtonsDTO'] = wfButtonsDTO; + data['ID'] = iD; + data['OrderId'] = orderId; + data['PreviousStep'] = previousStep; + data['NextStep'] = nextStep; + data['ServiceId'] = serviceId; + data['Order'] = order; + data['Created'] = created; + data['CreatedBy'] = createdBy; + data['Modified'] = modified; + data['ModifiedBy'] = modifiedBy; + data['IsDeleted'] = isDeleted; + return data; + } +} diff --git a/lib/features/hmg_services/models/resq_models/get_cmc_services_resp_model.dart b/lib/features/hmg_services/models/resq_models/get_cmc_services_resp_model.dart new file mode 100644 index 0000000..670a582 --- /dev/null +++ b/lib/features/hmg_services/models/resq_models/get_cmc_services_resp_model.dart @@ -0,0 +1,57 @@ +class GetCMCServicesResponseModel { + int? iD; + String? serviceID; + int? orderServiceID; + String? text; + String? textN; + dynamic price; + dynamic priceVAT; + dynamic priceTotal; + bool? isEnabled; + int? orderId; + int? quantity; + + GetCMCServicesResponseModel({ + this.iD, + this.serviceID, + this.orderServiceID, + this.text, + this.textN, + this.price, + this.priceVAT, + this.priceTotal, + this.isEnabled, + this.orderId, + this.quantity, + }); + + GetCMCServicesResponseModel.fromJson(Map json) { + iD = json['ID']; + serviceID = json['ServiceID']; + orderServiceID = json['OrderServiceID']; + text = json['Text']; + textN = json['TextN']; + price = json['Price']; + priceVAT = json['PriceVAT']; + priceTotal = json['PriceTotal']; + isEnabled = json['IsEnabled']; + orderId = json['OrderId']; + quantity = json['Quantity']; + } + + Map toJson() { + final Map data = {}; + data['ID'] = this.iD; + data['ServiceID'] = this.serviceID; + data['OrderServiceID'] = this.orderServiceID; + data['Text'] = this.text; + data['TextN'] = this.textN; + data['Price'] = this.price; + data['PriceVAT'] = this.priceVAT; + data['PriceTotal'] = this.priceTotal; + data['IsEnabled'] = this.isEnabled; + data['OrderId'] = this.orderId; + data['Quantity'] = this.quantity; + return data; + } +} diff --git a/lib/features/hmg_services/models/ui_models/hmg_services_component_model.dart b/lib/features/hmg_services/models/ui_models/hmg_services_component_model.dart new file mode 100644 index 0000000..d5180ae --- /dev/null +++ b/lib/features/hmg_services/models/ui_models/hmg_services_component_model.dart @@ -0,0 +1,25 @@ +import 'package:flutter/material.dart'; + +class HmgServicesComponentModel { + int action; + String title; + String subTitle; + String icon; + bool isLogin; + bool isLocked; + Color bgColor; + Color textColor; + String route; + + HmgServicesComponentModel( + this.action, + this.title, + this.subTitle, + this.icon, + this.isLogin, { + this.isLocked = false, + this.bgColor = Colors.white, + this.textColor = Colors.black, + this.route = '', + }); +} 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_view_model.dart b/lib/features/radiology/radiology_view_model.dart index de6a796..d39d84f 100644 --- a/lib/features/radiology/radiology_view_model.dart +++ b/lib/features/radiology/radiology_view_model.dart @@ -13,14 +13,20 @@ class RadiologyViewModel extends ChangeNotifier { ErrorHandlerService errorHandlerService; List patientRadiologyOrders = []; - + List filteredRadiologyOrders = []; + List tempRadiologyOrders = []; String radiologyImageURL = ""; String patientRadiologyReportPDFBase64 = ""; + late List _radiologySuggestionsList = []; + + List get radiologySuggestions => _radiologySuggestionsList; + RadiologyViewModel({required this.radiologyRepo, required this.errorHandlerService}); initRadiologyViewModel() { patientRadiologyOrders.clear(); + filteredRadiologyOrders.clear(); isRadiologyOrdersLoading = true; isRadiologyPDFReportLoading = true; radiologyImageURL = ""; @@ -38,7 +44,10 @@ class RadiologyViewModel extends ChangeNotifier { // dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {}); } else if (apiResponse.messageStatus == 1) { patientRadiologyOrders = apiResponse.data!; + filteredRadiologyOrders = List.from(patientRadiologyOrders); + tempRadiologyOrders = [...patientRadiologyOrders]; isRadiologyOrdersLoading = false; + filterSuggestions(); notifyListeners(); if (onSuccess != null) { onSuccess(apiResponse); @@ -98,4 +107,23 @@ class RadiologyViewModel extends ChangeNotifier { }, ); } + filterSuggestions() { + final List labels = patientRadiologyOrders + .map((detail) => detail.description) + .whereType() + .toList(); + _radiologySuggestionsList = labels.toSet().toList(); + notifyListeners(); + } + filterRadiologyReports(String query) { + if (query.isEmpty) { + patientRadiologyOrders =tempRadiologyOrders;// reset + } else { + filteredRadiologyOrders = + filteredRadiologyOrders.where((desc) => desc.description!.toLowerCase().contains(query.toLowerCase())).toList(); + patientRadiologyOrders = filteredRadiologyOrders; + } + notifyListeners(); + } + } diff --git a/lib/main.dart b/lib/main.dart index fba8523..f127400 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -15,6 +15,7 @@ import 'package:hmg_patient_app_new/features/contact_us/contact_us_view_model.da import 'package:hmg_patient_app_new/features/doctor_filter/doctor_filter_view_model.dart'; import 'package:hmg_patient_app_new/features/emergency_services/emergency_services_view_model.dart'; import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_view_model.dart'; import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_view_model.dart'; import 'package:hmg_patient_app_new/features/insurance/insurance_view_model.dart'; import 'package:hmg_patient_app_new/features/lab/history/lab_history_viewmodel.dart'; @@ -133,7 +134,8 @@ void main() async { ), ChangeNotifierProvider( create: (_) => getIt.get(), - ),ChangeNotifierProvider( + ), + ChangeNotifierProvider( create: (_) => getIt.get(), ), ChangeNotifierProvider( @@ -142,6 +144,9 @@ void main() async { ChangeNotifierProvider( create: (_) => getIt.get(), ), + ChangeNotifierProvider( + create: (_) => getIt.get(), + ) ChangeNotifierProvider( create: (_) => getIt.get(), ) diff --git a/lib/presentation/appointments/appointment_details_page.dart b/lib/presentation/appointments/appointment_details_page.dart index 79084c9..12f3589 100644 --- a/lib/presentation/appointments/appointment_details_page.dart +++ b/lib/presentation/appointments/appointment_details_page.dart @@ -30,7 +30,7 @@ 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/movies_shimmer_widget.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'; @@ -317,7 +317,7 @@ class _AppointmentDetailsPageState extends State { SizedBox(height: 16.h), Consumer(builder: (context, prescriptionVM, child) { return prescriptionVM.isPrescriptionsDetailsLoading - ? const MoviesShimmerWidget() + ? const CommonShimmerWidget() : Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: Colors.white, 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/comprehensive_checkup/cmc_order_detail_page.dart b/lib/presentation/comprehensive_checkup/cmc_order_detail_page.dart new file mode 100644 index 0000000..7547fd0 --- /dev/null +++ b/lib/presentation/comprehensive_checkup/cmc_order_detail_page.dart @@ -0,0 +1,255 @@ +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/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:provider/provider.dart'; + +class CmcOrderDetailPage extends StatefulWidget { + const CmcOrderDetailPage({super.key}); + + @override + State createState() => _CmcOrderDetailPageState(); +} + +class _CmcOrderDetailPageState extends State { + @override + void initState() { + super.initState(); + final hmgServicesViewModel = context.read(); + scheduleMicrotask(() async { + await hmgServicesViewModel.getCmcOrdersList(); + }); + } + + 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: [ + // 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), + ), + ], + ), + ] + ], + ), + ), + ); + } + + 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 CMC orders yet.".needTranslation, + isSmallWidget: true, + width: 62.w, + height: 62.h, + ), + ), + ), + ); + } + + @override + Widget build(BuildContext context) { + return CollapsingListView( + title: "CMC Orders".needTranslation, + isLeading: true, + 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 new file mode 100644 index 0000000..5529b93 --- /dev/null +++ b/lib/presentation/comprehensive_checkup/comprehensive_checkup_page.dart @@ -0,0 +1,405 @@ +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'; +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/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'; +import 'package:provider/provider.dart'; +import 'package:shimmer/shimmer.dart'; + +class ComprehensiveCheckupPage extends StatefulWidget { + const ComprehensiveCheckupPage({super.key}); + + @override + State createState() => _ComprehensiveCheckupPageState(); +} + +class _ComprehensiveCheckupPageState extends State { + int? _selectedServiceId; + GetCMCServicesResponseModel? _selectedService; + + @override + void initState() { + super.initState(); + final HmgServicesViewModel hmgServicesViewModel = context.read(); + final AppState appState = getIt.get(); + + scheduleMicrotask(() async { + final user = appState.getAuthenticatedUser(); + if (user != null) { + await hmgServicesViewModel.getAllCmcOrders(); + await hmgServicesViewModel.getAllCmcServices(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), + + // 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, + ), + ), + ], + ), + ] + ], + ), + ), + ); + } + + 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 Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 16.h), + Text( + 'Select a Service'.needTranslation, + style: TextStyle( + fontSize: 20.h, + fontWeight: FontWeight.w700, + color: AppColors.blackColor, + letterSpacing: -0.8, + ), + ).paddingOnly(left: 16.w, right: 16.w), + ListView.builder( + padding: EdgeInsets.zero, + itemCount: services.length, + shrinkWrap: true, + physics: NeverScrollableScrollPhysics(), + itemBuilder: (context, index) { + final service = services[index]; + final serviceName = service.text ?? service.textN ?? ''; + final price = service.priceTotal ?? 0.0; + return RadioListTileWidget( + value: service.iD ?? 0, + groupValue: _selectedServiceId, + title: serviceName, + subtitleWidget: Utils.getPaymentAmountWithSymbol( + isExpanded: false, + price.toString().toText14(), + AppColors.blackColor, + 14, + isSaudiCurrency: true, + ), + onChanged: (value) { + setState(() { + _selectedServiceId = value; + _selectedService = service; + }); + }, + ); + }, + ), + // Illustration image below the services list similar to the old implementation + SizedBox(height: 12.h), + Builder(builder: (context) { + final appStateLocal = getIt.get(); + final String imagePath = appStateLocal.isArabic() ? AppAssets.comprehensiveCheckupAr : AppAssets.comprehensiveCheckupEn; + return Stack( + children: [ + Image.asset( + imagePath, + width: double.infinity, + fit: BoxFit.cover, + ).paddingAll(16.w), + Align( + alignment: Alignment.topRight, + child: Container( + decoration: BoxDecoration( + color: Color.fromARGB(51, 0, 0, 0), + borderRadius: BorderRadius.circular(1000.r), + ), + margin: EdgeInsets.all(16.h), + child: IconButton( + icon: Icon( + Icons.zoom_in, + color: Colors.white, + size: 26.w, + ), + padding: EdgeInsets.all(10.h), + onPressed: () => _showFullScreenImage(context, imagePath, isSvg: false), + ), + ), + ), + ], + ); + }), + ], + ); + } + + void _proceedWithSelectedService() { + if (_selectedService != null) { + 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, + ), + ); + }, + ); + } + } + + 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: "Comprehensive Checkup".needTranslation, + history: () => Navigator.of(context).push(CustomPageRoute(page: CmcOrderDetailPage(), direction: AxisDirection.up)), + bottomChild: Consumer( + builder: (context, hmgServicesViewModel, child) { + if (hmgServicesViewModel.isCmcOrdersLoading || hmgServicesViewModel.isCmcServicesLoading) return SizedBox.shrink(); + final pendingOrder = _getPendingOrder(hmgServicesViewModel.cmcOrdersList); + if (pendingOrder == null && _selectedServiceId != null) { + 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.isCmcOrdersLoading || hmgServicesViewModel.isCmcServicesLoading) { + return _buildLoadingShimmer(); + } + final pendingOrder = _getPendingOrder(hmgServicesViewModel.cmcOrdersList); + if (pendingOrder != null) { + return _buildPendingOrderCard(pendingOrder); + } else { + return _buildServiceSelectionList(hmgServicesViewModel.cmcServicesList); + } + }, + ), + ); + } + + void _showFullScreenImage(BuildContext context, String path, {bool isSvg = false}) { + Navigator.of(context).push(MaterialPageRoute(builder: (_) => FullScreenImageViewer(isSvg: isSvg, path: path))); + } +} 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/e_referral/e_referral_page_home.dart b/lib/presentation/e_referral/e_referral_page_home.dart new file mode 100644 index 0000000..bacca47 --- /dev/null +++ b/lib/presentation/e_referral/e_referral_page_home.dart @@ -0,0 +1,97 @@ +import 'dart:ui'; + +import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_export.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart'; +import 'package:hmg_patient_app_new/presentation/e_referral/new_referral.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:provider/provider.dart'; + +class EReferralPage extends StatefulWidget { + const EReferralPage({super.key}); + + @override + _EReferralPageState createState() => _EReferralPageState(); +} + +class _EReferralPageState extends State + { + + + @override + void initState() { + super.initState(); + } + + @override + void dispose() { + super.dispose(); + } + bool isNewReferral = true; + @override + Widget build(BuildContext context) { + return Scaffold( + backgroundColor: AppColors.bgScaffoldColor, + body: CollapsingListView( + title:"E Referral".needTranslation, + child: SingleChildScrollView( + child: Consumer(builder: (context, model, child) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox(height: 16.h), + Row( + children: [ + CustomButton( + text: "New Referral".needTranslation, + onPressed: () { + isNewReferral =true; + setState(() { + + }); + }, + backgroundColor: model.isSortByClinic ? AppColors.bgRedLightColor : AppColors.whiteColor, + borderColor: model.isSortByClinic ? AppColors.primaryRedColor : AppColors.textColor.withOpacity(0.2), + textColor: model.isSortByClinic ? AppColors.primaryRedColor : AppColors.blackColor, + fontSize: 12, + fontWeight: FontWeight.w500, + borderRadius: 10, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + ), + SizedBox(width: 8.h), + CustomButton( + text: "Search Referral".needTranslation, + onPressed: () { + isNewReferral =false; + }, + backgroundColor: model.isSortByClinic ? AppColors.whiteColor : AppColors.bgRedLightColor, + borderColor: model.isSortByClinic ? AppColors.textColor.withOpacity(0.2) : AppColors.primaryRedColor, + textColor: model.isSortByClinic ? AppColors.blackColor : AppColors.primaryRedColor, + fontSize: 12, + fontWeight: FontWeight.w500, + borderRadius: 10, + padding: EdgeInsets.fromLTRB(10, 0, 10, 0), + height: 40.h, + ), + ], + ).paddingSymmetrical(24.h, 0.h), + SizedBox(height: 20.h), + isNewReferral ? NewEReferral() : SizedBox(), + ], + ); + }), + ), + ), + ); + + + + + } +} diff --git a/lib/presentation/e_referral/new_referral.dart b/lib/presentation/e_referral/new_referral.dart new file mode 100644 index 0000000..4ed9b8e --- /dev/null +++ b/lib/presentation/e_referral/new_referral.dart @@ -0,0 +1,172 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; + +class NewEReferral extends StatefulWidget { + NewEReferral(); + + @override + _NewEReferralState createState() => _NewEReferralState(); +} + +class _NewEReferralState extends State with TickerProviderStateMixin { + late PageController _controller; + int _currentIndex = 0; + int pageSelected = 2; + + // CreateEReferralRequestModel createEReferralRequestModel = new CreateEReferralRequestModel(); + + @override + void initState() { + super.initState(); + _controller = new PageController(); + } + + @override + void dispose() { + super.dispose(); + } + + changePageViewIndex(pageIndex) { + _controller.jumpToPage(pageIndex); + } + + @override + Widget build(BuildContext context) { + return Scaffold( + body: Container( + height: double.infinity, + child: Column( + children: [ + Container( + width: double.infinity, + padding: EdgeInsets.only(left: 12,right: 12,top: 12), + child: Row( + children: [ + Expanded( + child: showProgress( + title: "Requester Info".needTranslation, + status: _currentIndex == 0 + ? "InProgress".needTranslation + : _currentIndex > 0 + ? "Completed".needTranslation + : "Locked".needTranslation, + color: _currentIndex == 0 ? AppColors.infoColor : AppColors.successColor, + ), + ), + Expanded( + child: showProgress( + title:"Patient Info".needTranslation, + status: _currentIndex == 1 + ? "InProgress".needTranslation + : _currentIndex > 1 + ? "Completed".needTranslation + : "Locked".needTranslation, + color: _currentIndex == 1 + ? AppColors.infoColor + : _currentIndex > 1 + ? AppColors.successColor + : AppColors.greyColor, + ), + ), + showProgress( + title: "Other Info".needTranslation, + status: _currentIndex == 2 ? "InProgress".needTranslation :"Locked".needTranslation, + color: _currentIndex == 2 + ? AppColors.infoColor + : _currentIndex > 3 + ? AppColors.successColor + : AppColors.greyColor, + isNeedBorder: false, + ), + ], + ), + ), + Expanded( + child: PageView( + physics: NeverScrollableScrollPhysics(), + controller: _controller, + onPageChanged: (index) { + setState(() { + _currentIndex = index; + }); + }, + scrollDirection: Axis.horizontal, + children: [ + // NewEReferralStepOnePage( + // changePageViewIndex: changePageViewIndex, + // createEReferralRequestModel: createEReferralRequestModel, + // ), + // NewEReferralStepTowPage( + // changePageViewIndex: changePageViewIndex, + // createEReferralRequestModel: createEReferralRequestModel, + // ), + // NewEReferralStepThreePage( + // changePageViewIndex: changePageViewIndex, + // createEReferralRequestModel: createEReferralRequestModel, + // ), + ], + ), + ), + ], + ), + ), + ); + } + + Widget showProgress({required String title, required String status, required Color color, bool isNeedBorder = true}) { + return Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Row( + children: [ + Container( + width: 26, + height: 26, + // decoration: containerRadius(color, 200), + child: Icon( + Icons.done, + color: Colors.white, + size: 16, + ), + ), + if (isNeedBorder) + Expanded( + child: Padding( + padding: const EdgeInsets.all(8.0), + child:Divider(), + )), + ], + ), + // mHeight(8), + Text( + title, + style: TextStyle( + fontSize: 11, + fontWeight: FontWeight.w600, + letterSpacing: -0.44, + ), + ), + // mHeight(2), + Container( + padding: EdgeInsets.all(5), + // decoration: containerRadius(color.withOpacity(0.2), 4), + child: Text( + status, + style: TextStyle( + fontSize: 8, + fontWeight: FontWeight.w600, + letterSpacing: -0.32, + color: color, + ), + ), + ), + ], + ) + ], + ); + } +} 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..b38b9f2 --- /dev/null +++ b/lib/presentation/emergency_services/RRT/rrt_map_screen.dart @@ -0,0 +1,636 @@ +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), + }, + ).paddingAll(16.h), + 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), + SizedBox( + height: 200.h, + 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 Pickup Details".needTranslation.toText21( + weight: FontWeight.w600, + color: AppColors.textColor, + ), + " Please select the details of pickup" + .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 + 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 5d073d4..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, ), @@ -61,7 +61,7 @@ class PickupLocation extends StatelessWidget { ), "To Hospital" .needTranslation - .toText12(color: AppColors.textColor) + .toText14(color: AppColors.textColor, weight: FontWeight.w500) ], ).onPress((){ context @@ -78,7 +78,7 @@ class PickupLocation extends StatelessWidget { ), "From Hospital" .needTranslation - .toText12(color: AppColors.textColor) + .toText14(color: AppColors.textColor, weight: FontWeight.w500) ], ).onPress((){ context @@ -96,10 +96,11 @@ 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), + .toText16(color: AppColors.textColor, weight: FontWeight.w600), RadioGroup( groupValue: directionValue, onChanged: (value) { @@ -121,7 +122,7 @@ class PickupLocation extends StatelessWidget { ), "One Way" .needTranslation - .toText12(color: AppColors.textColor) + .toText12(color: AppColors.textColor, fontWeight: FontWeight.w500) ], ).onPress((){ context @@ -138,7 +139,7 @@ class PickupLocation extends StatelessWidget { ), "Two Way" .needTranslation - .toText12(color: AppColors.textColor) + .toText14(color: AppColors.textColor, weight: FontWeight.w500) ], ).onPress((){ context diff --git a/lib/presentation/emergency_services/emergency_services_page.dart b/lib/presentation/emergency_services/emergency_services_page.dart index 3cbce23..8f6fd23 100644 --- a/lib/presentation/emergency_services/emergency_services_page.dart +++ b/lib/presentation/emergency_services/emergency_services_page.dart @@ -1,21 +1,18 @@ 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'; -import 'package:hmg_patient_app_new/presentation/emergency_services/call_ambulance/call_ambulance_page.dart'; import 'package:hmg_patient_app_new/presentation/emergency_services/call_ambulance/widgets/ambulance_option_selection_bottomsheet.dart'; import 'package:hmg_patient_app_new/presentation/emergency_services/call_ambulance/widgets/pickup_location.dart'; import 'package:hmg_patient_app_new/presentation/emergency_services/history/er_history_listing.dart'; -import 'package:hmg_patient_app_new/presentation/emergency_services/nearest_er_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'; @@ -29,16 +26,17 @@ 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: () { + emergencyServicesViewModel.changeOrderDisplayItems(OrderDislpay.ALL); Navigator.of(context).push(CustomPageRoute(page: ErHistoryListing(), direction: AxisDirection.up)); }, child: Padding( @@ -62,7 +60,9 @@ class EmergencyServicesPage extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ "Call Ambulance".needTranslation.toText16(isBold: true, color: AppColors.blackColor), - "Request an ambulance in emergency from home or hospital".needTranslation.toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), + "Request an ambulance in emergency from home or hospital" + .needTranslation + .toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), ], ), ), @@ -70,13 +70,10 @@ class EmergencyServicesPage extends StatelessWidget { Utils.buildSvgWithAssets(icon: AppAssets.forward_chevron_icon, width: 13.h, height: 13.h), ], ).onPress(() { - - showCommonBottomSheetWithoutHeight( context, child: Container( - decoration: - RoundedRectangleBorder().toSmoothCornerDecoration( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( color: AppColors.primaryRedColor, borderRadius: 24.h, ), @@ -101,29 +98,20 @@ class EmergencyServicesPage extends StatelessWidget { ], ), Lottie.asset(AppAnimations.ambulance_alert, - repeat: false, - reverse: false, - frameRate: FrameRate(60), - width: 120.h, - height: 120.h, - fit: BoxFit.contain), + repeat: false, reverse: false, frameRate: FrameRate(60), width: 120.h, height: 120.h, fit: BoxFit.contain), SizedBox(height: 8.h), - "Confirmation".needTranslation.toText28( - color: AppColors.whiteColor, isBold: true), + "Confirmation".needTranslation.toText28(color: AppColors.whiteColor, isBold: true), SizedBox(height: 8.h), "Are you sure you want to call an ambulance?" .needTranslation - .toText14( - color: AppColors.whiteColor, - weight: FontWeight.w500), + .toText14(color: AppColors.whiteColor, weight: FontWeight.w500), SizedBox(height: 24.h), CustomButton( text: LocaleKeys.confirm.tr(context: context), onPressed: () async { - // Navigator.of(context).pop(); await emergencyServicesViewModel.getTransportationOption(); - openTranportationSelectionBottomSheet(context); + openTranportationSelectionBottomSheet(context); }, backgroundColor: AppColors.whiteColor, borderColor: AppColors.whiteColor, @@ -161,7 +149,9 @@ class EmergencyServicesPage extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ "Nearest ER Location".needTranslation.toText16(isBold: true, color: AppColors.blackColor), - "Get the details of nearest branch including directions".needTranslation.toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), + "Get the details of nearest branch including directions" + .needTranslation + .toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), ], ), ), @@ -189,7 +179,8 @@ class EmergencyServicesPage extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ "Rapid Response Team (RRT)".toText16(isBold: true, color: AppColors.blackColor), - "Comprehensive medical service for all sorts of urgent and stable cases".toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), + "Comprehensive medical service for all sorts of urgent and stable cases" + .toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), ], ), ), @@ -209,26 +200,13 @@ 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), SizedBox(height: 8.h), LocaleKeys.confirm.tr().toText28(color: AppColors.whiteColor, isBold: true), SizedBox(height: 8.h), - "Are you sure you want to call Rapid Response Team (RRT)?".needTranslation.toText14(color: AppColors.whiteColor, weight: FontWeight.w500), + "Are you sure you want to call Rapid Response Team (RRT)?" + .needTranslation + .toText14(color: AppColors.whiteColor, weight: FontWeight.w500), SizedBox(height: 24.h), CustomButton( text: LocaleKeys.confirm.tr(context: context), @@ -236,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, @@ -286,7 +277,9 @@ class EmergencyServicesPage extends StatelessWidget { crossAxisAlignment: CrossAxisAlignment.start, children: [ "Emergency Check-In".needTranslation.toText16(isBold: true, color: AppColors.blackColor), - "Prior ER Check-In to skip the line & payment at the reception.".needTranslation.toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), + "Prior ER Check-In to skip the line & payment at the reception." + .needTranslation + .toText12(color: AppColors.greyTextColor, fontWeight: FontWeight.w500), ], ), ), @@ -297,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( @@ -321,7 +311,8 @@ class EmergencyServicesPage extends StatelessWidget { }), ], ), - 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), @@ -362,60 +353,51 @@ class EmergencyServicesPage extends StatelessWidget { ); } - openPickupDetailsBottomSheet(BuildContext context){ + openPickupDetailsBottomSheet(BuildContext context) { showCommonBottomSheetWithoutHeight( - onCloseClicked: (){ - context - .read() - .flushPickupInformation(); + onCloseClicked: () { + context.read().flushPickupInformation(); }, titleWidget: Transform.flip( - flipX: emergencyServicesViewModel.isArabic ? true : false, + flipX: emergencyServicesViewModel.isArabic, child: Utils.buildSvgWithAssets( icon: AppAssets.arrow_back, iconColor: Color(0xff2B353E), fit: BoxFit.contain, ), ).onPress(() { - context - .read() - .flushPickupInformation(); + context.read().flushPickupInformation(); Navigator.pop(context); openTranportationSelectionBottomSheet(context); }), context, - child: PickupLocation( - onTap: () { - Navigator.of(context).pop(); - context.read().flushSearchPredictions(); - context - .read() - .navigateTOAmbulancePage(); - }), + child: PickupLocation(onTap: () { + Navigator.of(context).pop(); + context.read().flushSearchPredictions(); + context.read().navigateTOAmbulancePage(); + }), isFullScreen: false, isCloseButtonVisible: true, hasBottomPadding: false, - backgroundColor: AppColors.bottomSheetBgColor, callBackFunc: () {}, ); } void openTranportationSelectionBottomSheet(BuildContext context) { - if(emergencyServicesViewModel.transportationOptions.isNotEmpty) { + if (emergencyServicesViewModel.transportationOptions.isNotEmpty) { showCommonBottomSheetWithoutHeight( title: "Transport Options".needTranslation, context, child: SizedBox( height: 400.h, - child: AmbulanceOptionSelectionBottomSheet( - onTap: () { - Navigator.of(context).pop(); - openPickupDetailsBottomSheet(context); - // context - // .read() - // .navigateTOAmbulancePage(); - }), + child: AmbulanceOptionSelectionBottomSheet(onTap: () { + Navigator.of(context).pop(); + openPickupDetailsBottomSheet(context); + // context + // .read() + // .navigateTOAmbulancePage(); + }), ), isFullScreen: false, isCloseButtonVisible: true, 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..0a3058d --- /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), + ], + ), + + 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, + height: 40.h, + iconSize: 18.w, + ), + ], + ).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/hmg_services/services_page.dart b/lib/presentation/hmg_services/services_page.dart index f79aae0..65bed10 100644 --- a/lib/presentation/hmg_services/services_page.dart +++ b/lib/presentation/hmg_services/services_page.dart @@ -1,10 +1,47 @@ import 'package:flutter/material.dart'; -import 'package:hmg_patient_app_new/core/utils/size_utils.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/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/features/hmg_services/models/ui_models/hmg_services_component_model.dart'; +import 'package:hmg_patient_app_new/presentation/hmg_services/services_view.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; class ServicesPage extends StatelessWidget { - const ServicesPage({super.key}); + ServicesPage({super.key}); + + final List hmgServices = [ + HmgServicesComponentModel( + 11, + "E Referral Services".needTranslation, + "".needTranslation, + AppAssets.eReferral, + true, + bgColor: Colors.orange, + textColor: AppColors.blackColor, + route: AppRoutes.eReferralPage, + ), + HmgServicesComponentModel( + 12, + "Comprehensive Checkup".needTranslation, + "".needTranslation, + AppAssets.comprehensiveCheckup, + true, + bgColor: AppColors.bgGreenColor, + 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) { @@ -16,7 +53,26 @@ class ServicesPage extends StatelessWidget { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - "Medical & Care Services".needTranslation.toText18(isBold: true) + "Medical & Care Services".needTranslation.toText18(isBold: true), + SizedBox(height: 20.h), + Padding( + padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 0), + child: GridView.builder( + gridDelegate: SliverGridDelegateWithFixedCrossAxisCount( + crossAxisCount: 3, // 4 icons per row + crossAxisSpacing: 16.w, + mainAxisSpacing: 24.h, + childAspectRatio: 0.75, + ), + physics: NeverScrollableScrollPhysics(), + shrinkWrap: true, + itemCount: hmgServices.length, + padding: EdgeInsets.zero, + itemBuilder: (BuildContext context, int index) { + return ServiceGridViewItem(hmgServices[index], index, false); + }, + ), + ) ], ), ), diff --git a/lib/presentation/hmg_services/services_view.dart b/lib/presentation/hmg_services/services_view.dart new file mode 100644 index 0000000..225bd96 --- /dev/null +++ b/lib/presentation/hmg_services/services_view.dart @@ -0,0 +1,50 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/app_export.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/features/hmg_services/models/ui_models/hmg_services_component_model.dart'; +import 'package:hmg_patient_app_new/services/navigation_service.dart'; + +class ServiceGridViewItem extends StatelessWidget { + final HmgServicesComponentModel hmgServiceComponentModel; + final int index; + final bool isHomePage; + final bool isLocked; + + const ServiceGridViewItem(this.hmgServiceComponentModel, this.index, this.isHomePage, {super.key, this.isLocked = false}); + + @override + Widget build(BuildContext context) { + return InkWell( + onTap: () => getIt.get().pushPageRoute(hmgServiceComponentModel.route), + child: Column( + mainAxisSize: MainAxisSize.max, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Container( + height: 48.h, + width: 48.w, + padding: EdgeInsets.zero, + margin: EdgeInsets.zero, + decoration: BoxDecoration( + color: hmgServiceComponentModel.bgColor, + borderRadius: BorderRadius.circular(12.r), + ), + child: Utils.buildSvgWithAssets( + icon: hmgServiceComponentModel.icon, + height: 21.h, + width: 21.w, + fit: BoxFit.none, + ), + ), + SizedBox(height: 5.h), + hmgServiceComponentModel.title.toText12( + fontWeight: FontWeight.w500, + color: hmgServiceComponentModel.textColor, + maxLine: 1, + ), + ], + )); + } +} 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/insurance/insurance_home_page.dart b/lib/presentation/insurance/insurance_home_page.dart index bd195c3..cdd9a2e 100644 --- a/lib/presentation/insurance/insurance_home_page.dart +++ b/lib/presentation/insurance/insurance_home_page.dart @@ -20,7 +20,7 @@ import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.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/shimmer/movies_shimmer_widget.dart'; +import 'package:hmg_patient_app_new/widgets/shimmer/common_shimmer_widget.dart'; import 'package:provider/provider.dart'; import 'widgets/insurance_history.dart'; diff --git a/lib/presentation/insurance/widgets/insurance_history.dart b/lib/presentation/insurance/widgets/insurance_history.dart index 1c7b1b9..a5114dd 100644 --- a/lib/presentation/insurance/widgets/insurance_history.dart +++ b/lib/presentation/insurance/widgets/insurance_history.dart @@ -12,7 +12,7 @@ import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; -import 'package:hmg_patient_app_new/widgets/shimmer/movies_shimmer_widget.dart'; +import 'package:hmg_patient_app_new/widgets/shimmer/common_shimmer_widget.dart'; import 'package:provider/provider.dart'; class InsuranceHistory extends StatelessWidget { diff --git a/lib/presentation/insurance/widgets/insurance_update_details_card.dart b/lib/presentation/insurance/widgets/insurance_update_details_card.dart index acdf1c7..753a36c 100644 --- a/lib/presentation/insurance/widgets/insurance_update_details_card.dart +++ b/lib/presentation/insurance/widgets/insurance_update_details_card.dart @@ -12,7 +12,7 @@ import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart'; -import 'package:hmg_patient_app_new/widgets/shimmer/movies_shimmer_widget.dart'; +import 'package:hmg_patient_app_new/widgets/shimmer/common_shimmer_widget.dart'; import 'package:provider/provider.dart'; class PatientInsuranceCardUpdateCard extends StatelessWidget { diff --git a/lib/presentation/medical_file/medical_file_page.dart b/lib/presentation/medical_file/medical_file_page.dart index 007ff48..7e76532 100644 --- a/lib/presentation/medical_file/medical_file_page.dart +++ b/lib/presentation/medical_file/medical_file_page.dart @@ -58,7 +58,7 @@ import 'package:hmg_patient_app_new/widgets/custom_tab_bar.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:hmg_patient_app_new/widgets/shimmer/movies_shimmer_widget.dart'; +import 'package:hmg_patient_app_new/widgets/shimmer/common_shimmer_widget.dart'; import 'package:provider/provider.dart'; import '../../features/active_prescriptions/active_prescriptions_view_model.dart'; @@ -465,7 +465,7 @@ class _MedicalFilePageState extends State { SizedBox(height: 16.h), Consumer(builder: (context, prescriptionVM, child) { return prescriptionVM.isPrescriptionsOrdersLoading - ? const MoviesShimmerWidget().paddingSymmetrical(24.w, 0.h) + ? const CommonShimmerWidget().paddingSymmetrical(24.w, 0.h) : prescriptionVM.patientPrescriptionOrders.isNotEmpty ? Container( decoration: RoundedRectangleBorder().toSmoothCornerDecoration( diff --git a/lib/presentation/prescriptions/prescriptions_list_page.dart b/lib/presentation/prescriptions/prescriptions_list_page.dart index 1293c9c..87af39b 100644 --- a/lib/presentation/prescriptions/prescriptions_list_page.dart +++ b/lib/presentation/prescriptions/prescriptions_list_page.dart @@ -19,7 +19,7 @@ import 'package:hmg_patient_app_new/presentation/prescriptions/prescription_deta 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/routes/custom_page_route.dart'; -import 'package:hmg_patient_app_new/widgets/shimmer/movies_shimmer_widget.dart'; +import 'package:hmg_patient_app_new/widgets/shimmer/common_shimmer_widget.dart'; import 'package:hmg_patient_app_new/widgets/transitions/fade_page.dart'; import 'package:provider/provider.dart'; diff --git a/lib/presentation/radiology/radiology_orders_page.dart b/lib/presentation/radiology/radiology_orders_page.dart index 51b3343..cb925ef 100644 --- a/lib/presentation/radiology/radiology_orders_page.dart +++ b/lib/presentation/radiology/radiology_orders_page.dart @@ -9,9 +9,11 @@ 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/lab_view_model.dart'; import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart'; import 'package:hmg_patient_app_new/presentation/radiology/radiology_result_page.dart'; +import 'package:hmg_patient_app_new/presentation/radiology/search_radiology.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'; @@ -30,7 +32,7 @@ class RadiologyOrdersPage extends StatefulWidget { class _RadiologyOrdersPageState extends State { late RadiologyViewModel radiologyViewModel; - + String selectedFilterText = ''; int? expandedIndex; @override @@ -48,6 +50,24 @@ class _RadiologyOrdersPageState extends State { backgroundColor: AppColors.bgScaffoldColor, body: CollapsingListView( title: LocaleKeys.radiology.tr(context: context), + search: () async { + final lavVM = Provider.of(context, listen: false); + if (lavVM.isLabOrdersLoading) { + return; + } else { + String? value = await Navigator.of(context).push( + CustomPageRoute( + page: SearchRadiologyContent(radiologySuggestionsList: radiologyViewModel.radiologySuggestions), + fullScreenDialog: true, + direction: AxisDirection.down, + ), + ); + if (value != null) { + selectedFilterText = value; + radiologyViewModel.filterRadiologyReports(value); + } + } + }, child: SingleChildScrollView( child: Consumer( builder: (context, model, child) { @@ -56,7 +76,25 @@ class _RadiologyOrdersPageState extends State { child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ - // Expandable list + selectedFilterText!.isNotEmpty + ? AppCustomChipWidget( + padding: EdgeInsets.symmetric(horizontal: 5.h), + labelText: selectedFilterText!, + deleteIcon: 'assets/images/svg/cross_circle.svg', + backgroundColor: AppColors.alertColor, + textColor: AppColors.whiteColor, + deleteIconColor: AppColors.whiteColor, + deleteIconHasColor: true, + onDeleteTap: () { + setState(() { + selectedFilterText = ''; + model.filterRadiologyReports(''); + }); + }, + // chipType: ChipTypeEnum.alert, + // isSelected: true, + ) + : SizedBox(), ListView.builder( shrinkWrap: true, physics: NeverScrollableScrollPhysics(), diff --git a/lib/presentation/radiology/search_radiology.dart b/lib/presentation/radiology/search_radiology.dart new file mode 100644 index 0000000..98f5c90 --- /dev/null +++ b/lib/presentation/radiology/search_radiology.dart @@ -0,0 +1,154 @@ +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/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/generated/locale_keys.g.dart'; +import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; +import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart'; +import 'package:hmg_patient_app_new/widgets/input_widget.dart'; +import 'package:sizer/sizer.dart'; + +class SearchRadiologyContent extends StatefulWidget { + final List radiologySuggestionsList; + + const SearchRadiologyContent({super.key, required this.radiologySuggestionsList}); + + @override + State createState() => _SearchRadiologyContentContentState(); +} + +class _SearchRadiologyContentContentState extends State { + TextEditingController searchEditingController = TextEditingController(); + List filteredSuggestions = []; + + @override + void initState() { + super.initState(); + filteredSuggestions = List.from(widget.radiologySuggestionsList); + + // Listen for changes in the search field + searchEditingController.addListener(() { + filterSuggestions(); + }); + } + + @override + void dispose() { + searchEditingController.dispose(); + super.dispose(); + } + + void filterSuggestions() { + final query = searchEditingController.text.toLowerCase(); + + if (query.isEmpty) { + setState(() { + filteredSuggestions = List.from(widget.radiologySuggestionsList); + }); + } else { + setState(() { + filteredSuggestions = widget.radiologySuggestionsList.where((suggestion) => suggestion.toLowerCase().contains(query)).toList(); + }); + } + } + + @override + Widget build(BuildContext context) { + return CollapsingListView( + title: LocaleKeys.radiology.tr(), + isClose: true, + bottomChild: Container( + color: Colors.white, + padding: EdgeInsets.all(ResponsiveExtension(20).h), + child: CustomButton( + text: LocaleKeys.search.tr(), + icon: AppAssets.search_icon, + iconColor: Colors.white, + onPressed: () => Navigator.pop(context, searchEditingController.text), + ), + ), + child: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Padding( + padding: const EdgeInsets.only(left: 24, right: 24, top: 24), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + TextInputWidget( + labelText: "Search radiology results", + hintText: "Type test description", + controller: searchEditingController, + isEnable: true, + prefix: null, + autoFocus: false, + isBorderAllowed: false, + keyboardType: TextInputType.text, + padding: EdgeInsets.symmetric( + vertical: ResponsiveExtension(9).h, + horizontal: ResponsiveExtension(15).h, + ), + ), + SizedBox(height: ResponsiveExtension(20).h), + if (filteredSuggestions.isNotEmpty) ...[ + "Suggestions".toText16(isBold: true), + ], + ], + ), + ), + SingleChildScrollView( + physics: NeverScrollableScrollPhysics(), + padding: const EdgeInsets.only(left: 24, right: 24, bottom: 24, top: 16), + child: Wrap( + alignment: WrapAlignment.start, + spacing: 10, + runSpacing: 10, + children: filteredSuggestions + .map((label) => SuggestionChip( + label: label, + onTap: () { + searchEditingController.text = label; + }, + )) + .toList(), + ), + ), + ], + ), + ); + } +} + +class SuggestionChip extends StatelessWidget { + final String label; + final bool isSelected; + final VoidCallback? onTap; + + const SuggestionChip({ + super.key, + required this.label, + this.isSelected = false, + this.onTap, + }); + + @override + Widget build(BuildContext context) { + return GestureDetector( + onTap: onTap, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 8), + decoration: BoxDecoration( + color: isSelected ? AppColors.primaryRedColor : AppColors.whiteColor, + borderRadius: BorderRadius.circular(8), + ), + child: label.toText12( + color: isSelected ? Colors.white : Colors.black87, + fontWeight: FontWeight.w500, + ), + ), + ); + } +} 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/routes/app_routes.dart b/lib/routes/app_routes.dart index 5a93216..a0ee1e5 100644 --- a/lib/routes/app_routes.dart +++ b/lib/routes/app_routes.dart @@ -2,8 +2,10 @@ import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/presentation/authentication/login.dart'; import 'package:hmg_patient_app_new/presentation/authentication/register.dart'; import 'package:hmg_patient_app_new/presentation/authentication/register_step2.dart'; -import 'package:hmg_patient_app_new/presentation/home/landing_page.dart'; +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'; @@ -14,6 +16,9 @@ class AppRoutes { static const String registerStepTwo = '/registerStepTwo'; static const String landingScreen = '/landingScreen'; 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(), @@ -21,6 +26,9 @@ class AppRoutes { landingScreen: (context) => LandingNavigation(), register: (context) => RegisterNew(), registerStepTwo: (context) => RegisterNewStep2(), - medicalFilePage: (context) => MedicalFilePage(), + medicalFilePage: (context) => MedicalFilePage(), + eReferralPage: (context) => EReferralPage(), + comprehensiveCheckupPage: (context) => ComprehensiveCheckupPage(), + homeHealthCarePage: (context) => HhcProceduresPage() }; } diff --git a/lib/services/navigation_service.dart b/lib/services/navigation_service.dart index cb4405c..fe95140 100644 --- a/lib/services/navigation_service.dart +++ b/lib/services/navigation_service.dart @@ -13,8 +13,8 @@ class NavigationService { return navigatorKey.currentState!.push(route); } - Future pushAndRemoveUntil(Route route,RoutePredicate predicate) { - return navigatorKey.currentState!.pushAndRemoveUntil(route,predicate); + Future pushAndRemoveUntil(Route route, RoutePredicate predicate) { + return navigatorKey.currentState!.pushAndRemoveUntil(route, predicate); } void pop([T? result]) { @@ -38,11 +38,22 @@ class NavigationService { navigatorKey.currentState?.pushReplacementNamed(routeName); } + void pushPageRoute(String routeName) { + navigatorKey.currentState?.pushNamed(routeName); + } - - Future pushToOtpScreen({required String phoneNumber, required Function(int code) checkActivationCode, required Function(String phoneNumber) onResendOTPPressed, bool isFormFamilyFile = false}) { + Future pushToOtpScreen( + {required String phoneNumber, + required Function(int code) checkActivationCode, + required Function(String phoneNumber) onResendOTPPressed, + bool isFormFamilyFile = false}) { return navigatorKey.currentState!.push( - MaterialPageRoute(builder: (_) => OTPVerificationScreen(phoneNumber: phoneNumber, checkActivationCode: checkActivationCode, onResendOTPPressed: onResendOTPPressed, isFormFamilyFile : isFormFamilyFile)), + MaterialPageRoute( + builder: (_) => OTPVerificationScreen( + phoneNumber: phoneNumber, + checkActivationCode: checkActivationCode, + onResendOTPPressed: onResendOTPPressed, + isFormFamilyFile: isFormFamilyFile)), ); } diff --git a/lib/theme/colors.dart b/lib/theme/colors.dart index c8258fe..ff0ea19 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,29 +47,40 @@ 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 lightGreyTextColor = Color(0xFF959595); -static const Color labelColorYellow = Color(0xFFFBCB6E); + // 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); + static const Color lightGreyTextColor = Color(0xFF959595); + static const Color labelColorYellow = Color(0xFFFBCB6E); } 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/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 f090f4f..4e655f0 100644 --- a/lib/widgets/chip/app_custom_chip_widget.dart +++ b/lib/widgets/chip/app_custom_chip_widget.dart @@ -24,6 +24,7 @@ class AppCustomChipWidget extends StatelessWidget { this.padding = EdgeInsets.zero, this.onChipTap, this.labelPadding, + this.onDeleteTap, }); final String? labelText; @@ -42,77 +43,82 @@ class AppCustomChipWidget extends StatelessWidget { final EdgeInsets? padding; final EdgeInsetsDirectional? labelPadding; final void Function()? onChipTap; + final void Function()? onDeleteTap; @override Widget build(BuildContext context) { final iconS = iconSize ?? 12.w; return GestureDetector( onTap: onChipTap, - child: ChipTheme( - data: ChipThemeData( - padding: EdgeInsets.zero, - shape: SmoothRectangleBorder( - side: BorderSide( - width: 10.w, - color: Colors.transparent, // Crucially, set color to transparent - style: BorderStyle.none, + child: SizedBox( + child: ChipTheme( + data: ChipThemeData( + padding: EdgeInsets.zero, + shape: SmoothRectangleBorder( + side: BorderSide( + width: 10.w, + color: Colors.transparent, // Crucially, set color to transparent + style: BorderStyle.none, + ), + borderRadius: BorderRadius.circular(8.r), // Apply a border radius of 16.0 ), - borderRadius: BorderRadius.circular(isFoldable || isTablet ? 6.r : 8.r), ), - ), - child: icon.isNotEmpty - ? Chip( - avatar: icon.isNotEmpty - ? Padding( - padding: EdgeInsets.only(left: 8.w, right: 6.w), - child: Utils.buildSvgWithAssets( + child: icon.isNotEmpty + ? Chip( + avatar: icon.isNotEmpty + ? Utils.buildSvgWithAssets( icon: icon, width: iconS, height: iconS, iconColor: iconHasColor ? iconColor : null, fit: BoxFit.contain, - ), - ) - : SizedBox.shrink(), - avatarBoxConstraints: BoxConstraints(), - label: richText ?? labelText!.toText10(weight: FontWeight.w500, letterSpacing: 0, color: textColor), - padding: EdgeInsets.zero, - materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, - labelPadding: EdgeInsetsDirectional.only(end: 8.w), - backgroundColor: backgroundColor, - shape: shape ?? - SmoothRectangleBorder( - borderRadius: BorderRadius.circular(8.r), - smoothness: 10, - side: BorderSide(color: AppColors.transparent, width: 1.5), - ), - deleteIcon: deleteIcon?.isNotEmpty == true - ? 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), - padding: EdgeInsets.zero, - backgroundColor: backgroundColor, - shape: shape ?? - SmoothRectangleBorder( - borderRadius: BorderRadius.circular(8.r), - smoothness: 10, - side: BorderSide(color: AppColors.transparent, width: 1.5), - ), - labelPadding: EdgeInsetsDirectional.only(start: 8.w, end: 8.w), - deleteIcon: deleteIcon?.isNotEmpty == true - ? Utils.buildSvgWithAssets(icon: deleteIcon!, width: iconS, height: iconS, iconColor: deleteIconHasColor ? deleteIconColor : null) - : null, - onDeleted: deleteIcon?.isNotEmpty == true ? () {} : null, - ), + ) + : SizedBox.shrink(), + label: richText ?? labelText!.toText10(weight: FontWeight.w500, letterSpacing: 0, color: textColor), + padding: padding, + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + labelPadding: labelPadding ?? EdgeInsetsDirectional.only(end: deleteIcon?.isNotEmpty == true ? 2.w : 8.w), + backgroundColor: backgroundColor, + shape: shape ?? + SmoothRectangleBorder( + borderRadius: BorderRadius.circular(8.r), + smoothness: 10, + 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, + ), + ) + : null, + onDeleted: deleteIcon?.isNotEmpty == true ? () {} : null, + ) + : Chip( + materialTapTargetSize: MaterialTapTargetSize.shrinkWrap, + label: richText ?? labelText!.toText10(weight: FontWeight.w500, letterSpacing: 0, color: textColor), + padding: EdgeInsets.zero, + backgroundColor: backgroundColor, + shape: shape ?? + SmoothRectangleBorder( + borderRadius: BorderRadius.circular(8.r), + 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), + deleteIcon: deleteIcon?.isNotEmpty == true + ? 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..e5c5baa 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,23 +162,26 @@ 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), 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/media_viewer/full_screen_image_viewer.dart b/lib/widgets/media_viewer/full_screen_image_viewer.dart new file mode 100644 index 0000000..0fb92f0 --- /dev/null +++ b/lib/widgets/media_viewer/full_screen_image_viewer.dart @@ -0,0 +1,31 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_svg/flutter_svg.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; + +class FullScreenImageViewer extends StatelessWidget { + final bool isSvg; + final String path; + + const FullScreenImageViewer({super.key, required this.isSvg, required this.path}); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + backgroundColor: Colors.black, + leading: IconButton( + icon: Icon(Icons.close, color: AppColors.whiteColor), + onPressed: () => Navigator.of(context).pop(), + ), + ), + backgroundColor: Colors.black, + body: Center( + child: InteractiveViewer( + child: isSvg + ? SvgPicture.asset(path, width: double.infinity, fit: BoxFit.contain) + : Image.asset(width: double.infinity, path, fit: BoxFit.contain), + ), + ), + ); + } +} diff --git a/lib/widgets/radio_list_tile_widget.dart b/lib/widgets/radio_list_tile_widget.dart new file mode 100644 index 0000000..9025e19 --- /dev/null +++ b/lib/widgets/radio_list_tile_widget.dart @@ -0,0 +1,89 @@ +import 'package:flutter/material.dart'; +import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; +import 'package:hmg_patient_app_new/extensions/string_extensions.dart'; +import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; +import 'package:hmg_patient_app_new/theme/colors.dart'; + +class RadioListTileWidget extends StatelessWidget { + final T value; + final T? groupValue; + final String title; + final Widget? subtitleWidget; + final String? subtitle; + final ValueChanged onChanged; + final bool enabled; + + const RadioListTileWidget({ + super.key, + required this.value, + required this.groupValue, + required this.title, + this.subtitleWidget, + this.subtitle, + required this.onChanged, + this.enabled = true, + }); + + @override + Widget build(BuildContext context) { + final bool isSelected = value == groupValue; + + return InkWell( + onTap: enabled ? () => onChanged(value) : null, + child: Container( + decoration: RoundedRectangleBorder().toSmoothCornerDecoration( + color: AppColors.whiteColor, + borderRadius: 24.h, + hasShadow: false, + ), + margin: EdgeInsets.symmetric(horizontal: 16.w, vertical: 6.h), + padding: EdgeInsets.all(16.h), + child: Row( + children: [ + Container( + width: 20.h, + height: 20.h, + decoration: BoxDecoration( + shape: BoxShape.circle, + border: Border.all( + color: isSelected ? AppColors.primaryRedColor : const Color(0xff9E9E9E), + width: 2, + ), + ), + child: isSelected + ? Center( + child: Container( + width: 10.h, + height: 10.h, + decoration: BoxDecoration( + shape: BoxShape.circle, + color: AppColors.primaryRedColor, + ), + ), + ) + : null, + ), + SizedBox(width: 16.w), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + title.toText15(weight: FontWeight.w500, letterSpacing: -0.64), + if (subtitleWidget != null) ...[subtitleWidget!], + if (subtitle != null) ...[ + SizedBox(height: 4.h), + title.toText13( + weight: FontWeight.w400, + color: AppColors.greyTextColor, + letterSpacing: -0.56, + ), + ], + ], + ), + ), + ], + ), + ), + ); + } +} diff --git a/lib/widgets/shimmer/movies_shimmer_widget.dart b/lib/widgets/shimmer/common_shimmer_widget.dart similarity index 93% rename from lib/widgets/shimmer/movies_shimmer_widget.dart rename to lib/widgets/shimmer/common_shimmer_widget.dart index fb1af25..d6a2906 100644 --- a/lib/widgets/shimmer/movies_shimmer_widget.dart +++ b/lib/widgets/shimmer/common_shimmer_widget.dart @@ -1,10 +1,10 @@ +import 'package:flutter/material.dart'; import 'package:hmg_patient_app_new/extensions/int_extensions.dart'; import 'package:hmg_patient_app_new/extensions/widget_extensions.dart'; import 'package:hmg_patient_app_new/theme/colors.dart'; -import 'package:flutter/material.dart'; -class MoviesShimmerWidget extends StatelessWidget { - const MoviesShimmerWidget({super.key}); +class CommonShimmerWidget extends StatelessWidget { + const CommonShimmerWidget({super.key}); @override Widget build(BuildContext context) {