# Conflicts:
#	lib/core/api_consts.dart
#	lib/features/hmg_services/hmg_services_repo.dart
#	lib/features/hmg_services/hmg_services_view_model.dart
#	lib/presentation/emergency_services/call_ambulance/widgets/pickup_location.dart
pull/104/head
Sultan khan 2 months ago
commit 5f047e1b6f

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 543 KiB

@ -0,0 +1,5 @@
<svg width="18" height="18" viewBox="0 0 18 18" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M9.49411 2.85661C9.06887 2.71829 8.56575 2.71849 7.82926 2.71878L5.20973 2.7188C4.36011 2.71878 3.66581 2.71877 3.11767 2.79255C2.5447 2.86967 2.04795 3.03656 1.65143 3.43352C1.25491 3.83049 1.0882 4.32779 1.01117 4.90139C0.93747 5.45014 0.937484 6.14521 0.9375 6.99576V10.0798C0.937484 10.9303 0.93747 11.6254 1.01117 12.1741C1.0882 12.7477 1.25491 13.245 1.65143 13.642C1.76583 13.7565 1.89283 13.8531 2.02893 13.9345C2.22063 14.0492 2.31647 14.1065 2.39389 14.059C2.47131 14.0115 2.46224 13.8786 2.44411 13.6127C2.43973 13.5484 2.4375 13.4836 2.4375 13.4181C2.4375 11.8631 3.6967 10.6025 5.25 10.6025C6.8033 10.6025 8.0625 11.8631 8.0625 13.4181C8.0625 13.5947 8.04626 13.7675 8.01519 13.9351C7.97875 14.1317 7.96053 14.23 8.00499 14.2841C8.04945 14.3381 8.14092 14.3389 8.32387 14.3406C8.77449 14.3448 9.22551 14.3448 9.67614 14.3406C9.85908 14.3389 9.95055 14.3381 9.99501 14.2841C10.0395 14.23 10.0213 14.1317 9.98481 13.9351C9.95374 13.7675 9.9375 13.5947 9.9375 13.4181C9.9375 11.8631 11.1967 10.6025 12.75 10.6025C14.3033 10.6025 15.5625 11.8631 15.5625 13.4181C15.5625 13.4835 15.5603 13.5484 15.5559 13.6127C15.5378 13.8786 15.5287 14.0115 15.6061 14.059C15.6835 14.1065 15.7794 14.0492 15.9711 13.9345C16.1072 13.8531 16.2342 13.7565 16.3486 13.642C16.7451 13.245 16.9118 12.7477 16.9888 12.1741C17.0625 11.6254 17.0625 10.9303 17.0625 10.0798V8.55433C17.0654 8.4286 17.0367 8.30042 16.9729 8.18177L15.9988 6.37069C15.7544 5.91626 15.5451 5.52708 15.3388 5.22173C15.1184 4.89536 14.8689 4.61351 14.5195 4.40455C14.17 4.19558 13.8038 4.10932 13.4123 4.06973C13.046 4.0327 12.6044 4.03272 12.0889 4.03274L11.2524 4.03274C11.1353 4.03274 11.0767 4.03274 11.0295 4.00933C10.9823 3.98592 10.9424 3.93349 10.8626 3.82864C10.5202 3.37853 10.0455 3.03597 9.49411 2.85661ZM11.7406 5.53441C11.5945 5.53441 11.5214 5.53441 11.4773 5.57901C11.4333 5.62362 11.4342 5.69802 11.4359 5.84684C11.4377 5.99664 11.4376 6.15736 11.4375 6.33106C11.4375 6.93389 11.4444 7.20613 11.4834 7.32612C11.5761 7.61185 11.7999 7.83587 12.0853 7.92871C12.2052 7.96769 12.3728 7.97464 12.975 7.97464H14.6556C14.8798 7.97464 14.9919 7.97464 15.0355 7.90148C15.0792 7.82832 15.026 7.72952 14.9197 7.53192L14.6949 7.11386C14.429 6.61959 14.2551 6.29803 14.0963 6.06291C13.9469 5.84183 13.845 5.75037 13.7503 5.69375C13.6556 5.63714 13.5268 5.59062 13.2616 5.5638C12.9795 5.53529 12.6142 5.53441 12.0535 5.53441H11.7406ZM4.5001 5.53125C4.91431 5.53125 5.2501 5.86704 5.2501 6.28125V7.78125C5.2501 8.19546 4.91431 8.53125 4.5001 8.53125C4.08589 8.53125 3.7501 8.19546 3.7501 7.78125V6.28125C3.7501 5.86704 4.08589 5.53125 4.5001 5.53125ZM7.5001 5.53125C7.91431 5.53125 8.2501 5.86704 8.2501 6.28125V7.78125C8.2501 8.19546 7.91431 8.53125 7.5001 8.53125C7.08589 8.53125 6.7501 8.19546 6.7501 7.78125V6.28125C6.7501 5.86704 7.08589 5.53125 7.5001 5.53125Z" fill="#2E3039"/>
<path d="M5.25 11.5312C4.21447 11.5312 3.375 12.3707 3.375 13.4062C3.375 14.4418 4.21447 15.2812 5.25 15.2812C6.28553 15.2812 7.125 14.4418 7.125 13.4062C7.125 12.3707 6.28553 11.5312 5.25 11.5312Z" fill="#2E3039"/>
<path d="M12.75 11.5312C11.7145 11.5312 10.875 12.3707 10.875 13.4062C10.875 14.4418 11.7145 15.2812 12.75 15.2812C13.7855 15.2812 14.625 14.4418 14.625 13.4062C14.625 12.3707 13.7855 11.5312 12.75 11.5312Z" fill="#2E3039"/>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 10 KiB

@ -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)
}
}

@ -161,10 +161,11 @@ class ApiClientImp implements ApiClient {
// body['VersionID'] = ApiConsts.appVersionID.toString();
if (!isExternal) {
body['VersionID'] = ApiConsts.appVersionID;
body['Channel'] = ApiConsts.appChannelId;
body['VersionID'] = ApiConsts.appVersionID.toString();
body['Channel'] = ApiConsts.appChannelId.toString();
body['IPAdress'] = ApiConsts.appIpAddress;
body['generalid'] = ApiConsts.appGeneralId;
body['LanguageID'] = _appState.getLanguageID().toString();
body['Latitude'] = _appState.userLat.toString();
body['Longitude'] = _appState.userLong.toString();
@ -183,6 +184,9 @@ class ApiClientImp implements ApiClient {
}
body.removeWhere((key, value) => value == null);
log("uri: ${Uri.parse(url.trim())}");
log("body: ${json.encode(body)}");
final bool networkStatus = await Utils.checkConnection(bypassConnectionCheck: bypassConnectionCheck);
@ -201,6 +205,7 @@ class ApiClientImp implements ApiClient {
log("uri: ${Uri.parse(url.trim())}");
log("body: ${json.encode(body)}");
log("response.body: ${response.body}");
// log("response.body: ${response.body}");
if (statusCode < 200 || statusCode >= 400) {
onFailure('Error While Fetching data', statusCode, failureType: StatusCodeFailure("Error While Fetching data"));
logApiEndpointError(endPoint, 'Error While Fetching data', statusCode);
@ -364,7 +369,7 @@ class ApiClientImp implements ApiClient {
debugPrint("URL : $url");
// print("Body : ${json.encode(body)}");
if (await Utils.checkConnection()) {
if (await Utils.checkConnection(bypassConnectionCheck: true)) {
final response = await http.get(
Uri.parse(url.trim()),
headers: {'Content-Type': 'application/json', 'Accept': 'application/json'},

@ -753,7 +753,7 @@ class ApiConsts {
TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout";
GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments";
GET_TAMARA_PAYMENT_STATUS = 'https://epharmacy.hmg.com/tamara/api/OnlineTamara/order_status?orderid=';
rcBaseUrl = 'https://rc.hmg.com/';
rcBaseUrl = 'https://rc.hmg.com/uat/';
break;
case AppEnvironmentTypeEnum.uat:
baseUrl = "https://uat.hmgwebservices.com/";
@ -763,7 +763,7 @@ class ApiConsts {
TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout";
GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments";
GET_TAMARA_PAYMENT_STATUS = 'https://epharmacy.hmg.com/tamara/api/OnlineTamara/order_status?orderid=';
rcBaseUrl = 'https://rc.hmg.com/';
rcBaseUrl = 'https://rc.hmg.com/uat/';
break;
case AppEnvironmentTypeEnum.preProd:
baseUrl = "https://webservices.hmg.com/";
@ -783,7 +783,7 @@ class ApiConsts {
TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout";
GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments";
GET_TAMARA_PAYMENT_STATUS = 'https://epharmacy.hmg.com/tamara/api/OnlineTamara/order_status?orderid=';
rcBaseUrl = 'https://rc.hmg.com/';
rcBaseUrl = 'https://rc.hmg.com/uat/';
break;
case AppEnvironmentTypeEnum.staging:
baseUrl = "https://uat.hmgwebservices.com/";
@ -793,7 +793,7 @@ class ApiConsts {
TAMARA_URL = "https://epharmacy.hmg.com/tamara/Home/Checkout";
GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments";
GET_TAMARA_PAYMENT_STATUS = 'https://epharmacy.hmg.com/tamara/api/OnlineTamara/order_status?orderid=';
rcBaseUrl = 'https://rc.hmg.com/';
rcBaseUrl = 'https://rc.hmg.com/uat/';
break;
}
}
@ -837,16 +837,18 @@ class ApiConsts {
static final String createAdvancePayments = 'Services/Patients.svc/REST/HIS_CreateAdvancePayment';
static final String addAdvanceNumberRequest = 'Services/PayFort_Serv.svc/REST/AddAdvancedNumberRequest';
// RC CMC ServIces
// RC COMPREHENSIVE MEDICAL CHECKUP ServIces
static final String allCMCOrdersRc = 'api/cmc/list';
static final String allCMCServicesRc = 'api/cmc/getallcmc';
static final String updateCMCOrder = 'api/cmc/update';
static final String addCMCOrder = 'api/cmc/add';
static final String getHospitalsList = 'Services/Lists.svc/REST/GetProject';
// CMC RC SERVICES
var GET_ALL_CMC_SERVICES_RC = 'api/cmc/getallcmc';
var ADD_CMC_ORDER_RC = 'api/cmc/add';
var GET_ALL_CMC_ORDERS_RC = 'api/cmc/list';
var UPDATE_CMC_ORDER_RC = 'api/cmc/update';
// RC HOME HEALTHCARE ServIces
static final String allHHCOrdersRc = 'api/hhc/list';
static final String allHHCServicesRc = 'api/HHC/getallhhc';
static final String updateHHCOrder = 'api/hhc/update';
static final String addHHCOrder = 'api/HHC/add';
//E-REFERRAL SERVICES
@ -860,7 +862,7 @@ class ApiConsts {
// ************ static values for Api ****************
static final double appVersionID = 20.0;
static final double appVersionID = 19.3;
static final int appChannelId = 3;
static final String appIpAddress = "10.20.10.20";
static final String appGeneralId = "Cs2020@2016\$2958";

@ -173,6 +173,9 @@ class AppAssets {
static const String forward_arrow_medium = '$svgBasePath/forward_arrow_medium.svg';
static const String eReferral = '$svgBasePath/e-referral.svg';
static const String comprehensiveCheckup = '$svgBasePath/comprehensive_checkup.svg';
static const String all_payment_method = '$svgBasePath/all_payment_method.svg';
static const String ic_rrt_vehicle = '$svgBasePath/ic_rrt_vehicle.svg';
//bottom navigation//
static const String homeBottom = '$svgBasePath/home_bottom.svg';

@ -122,7 +122,13 @@ class AppDependencies {
getIt.registerLazySingleton<LabViewModel>(() => LabViewModel(labRepo: getIt(), errorHandlerService: getIt(), navigationService: getIt()));
getIt.registerLazySingleton<RadiologyViewModel>(() => RadiologyViewModel(radiologyRepo: getIt(), errorHandlerService: getIt()));
getIt.registerLazySingleton<RadiologyViewModel>(
() => RadiologyViewModel(
radiologyRepo: getIt(),
errorHandlerService: getIt(),
navigationService: getIt()
),
);
getIt.registerLazySingleton<PrescriptionsViewModel>(() => PrescriptionsViewModel(prescriptionsRepo: getIt(), errorHandlerService: getIt()));
@ -215,7 +221,7 @@ class AppDependencies {
);
getIt.registerLazySingleton<HmgServicesViewModel>(
() => HmgServicesViewModel(hmgServicesRepo: getIt(), errorHandlerService: getIt()),
() => HmgServicesViewModel(bookAppointmentsRepo: getIt(), hmgServicesRepo: getIt(), errorHandlerService: getIt()),
);
// Screen-specific VMs Factory

@ -6,6 +6,8 @@ class DateUtil {
/// convert String To Date function
/// [date] String we want to convert
static DateTime convertStringToDate(String? date) {
if (date == null) return DateTime.now();
if (date.isEmpty) return DateTime.now();

@ -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;
}
}

@ -41,12 +41,14 @@ extension EmailValidator on String {
FontWeight? weight,
bool isBold = false,
bool isUnderLine = false,
bool isCenter = false,
int? maxlines,
FontStyle? fontStyle,
TextOverflow? textOverflow,
double letterSpacing = 0}) =>
Text(
this,
textAlign: isCenter ? TextAlign.center : null,
maxLines: maxlines,
overflow: textOverflow,
style: TextStyle(
@ -223,6 +225,7 @@ extension EmailValidator on String {
FontWeight? weight,
TextOverflow? textOverflow,
double? letterSpacing = -0.4,
Color decorationColor =AppColors.errorColor
}) =>
Text(
this,
@ -236,6 +239,7 @@ extension EmailValidator on String {
overflow: textOverflow,
fontWeight: weight ?? (isBold ? FontWeight.bold : FontWeight.normal),
decoration: isUnderLine ? TextDecoration.underline : null,
decorationColor: decorationColor
),
);

@ -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,
),
),

@ -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<String> searchedRegionList = [];
List<String> facilityList = ["hmgHospitals", "hmcMedicalClinic"];
List<String> searchedHospitalList = [];
List<PatientDoctorAppointmentList>
searchedPatientDoctorAppointmentHospitalsList = [];
List<PatientDoctorAppointmentList> searchedPatientDoctorAppointmentHospitalsList = [];
List<String> searchedClinicList = [];
PatientDoctorAppointmentList? selectedHospitalForFilters;
@ -114,15 +112,14 @@ class BookAppointmentsViewModel extends ChangeNotifier {
String? selectedClinicForFilters;
bool applyFilters = false;
///variables for laser clinic
List<LaserCategoryType> femaleLaserCategory = [
List<LaserCategoryType> femaleLaserCategory = [
LaserCategoryType(1, 'bodyString'),
LaserCategoryType(2, 'face'),
LaserCategoryType(10,'bikini'),
LaserCategoryType(10, 'bikini'),
LaserCategoryType(11, 'retouch'),
];
List<LaserCategoryType> maleLaserCategory =[
List<LaserCategoryType> 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<void> 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<void> 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<void> getDoctorsList({int projectID = 0, bool isNearest = false, int doctorId = 0, String doctorName = "", Function(dynamic)? onSuccess, Function(String)? onError}) async {
Future<void> 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<void> 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<void> 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<DateTime, List> _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<void> cancelAppointment({required PatientAppointmentHistoryResponseModel patientAppointmentHistoryResponseModel, Function(dynamic)? onSuccess, Function(String)? onError}) async {
Future<void> 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<String>? selectedFacilityForFilters,
List<String>? selectedRegionForFilters,
String? selectedClinicForFilters,
PatientDoctorAppointmentList? selectedHospitalForFilters,
bool applyFilters) {
void setSelections(List<String>? selectedFacilityForFilters, List<String>? 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<void> getLaserClinic() async{
FutureOr<void> 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<LaserBodyPart> response =apiResponse.data!;
if(response.first.category == 2 || response.first.category == 10 ) response.remove(response.first);
List<LaserBodyPart> 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 ?? "";
}
}

@ -3,14 +3,18 @@ import 'package:hmg_patient_app_new/core/api/api_client.dart';
import 'package:hmg_patient_app_new/core/api_consts.dart';
import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart';
import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart';
import 'package:hmg_patient_app_new/features/contact_us/models/req_models/request_insert_coc_item.dart';
import 'package:hmg_patient_app_new/features/contact_us/models/resp_models/get_hmg_locations.dart';
import 'package:hmg_patient_app_new/features/contact_us/models/resp_models/get_patient_ic_projects.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart';
import 'package:hmg_patient_app_new/services/logger_service.dart';
abstract class ContactUsRepo {
Future<Either<Failure, GenericApiModel<List<GetHMGLocationsModel>>>> getHMGLocations();
Future<Either<Failure, GenericApiModel<List<GetPatientICProjectsModel>>>> getLiveChatProjectsList();
Future<Either<Failure, GenericApiModel<dynamic>>> insertCOCItem({required RequestInsertCOCItem requestInsertCOCItem, PatientAppointmentHistoryResponseModel? patientSelectedAppointment});
}
class ContactUsRepoImp implements ContactUsRepo {
@ -72,13 +76,57 @@ class ContactUsRepoImp implements ContactUsRepo {
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
final list = response['List_PatientICProjects'];
final hmgLocations = list.map((item) => GetPatientICProjectsModel.fromJson(item as Map<String, dynamic>)).toList().cast<GetPatientICProjectsModel>();
final liveChatProjectsList = list.map((item) => GetPatientICProjectsModel.fromJson(item as Map<String, dynamic>)).toList().cast<GetPatientICProjectsModel>();
apiResponse = GenericApiModel<List<GetPatientICProjectsModel>>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: hmgLocations,
data: liveChatProjectsList,
);
} catch (e) {
failure = DataParsingFailure(e.toString());
}
},
);
if (failure != null) return Left(failure!);
if (apiResponse == null) return Left(ServerFailure("Unknown error"));
return Right(apiResponse!);
} catch (e) {
return Left(UnknownFailure(e.toString()));
}
}
@override
Future<Either<Failure, GenericApiModel<dynamic>>> insertCOCItem({required RequestInsertCOCItem requestInsertCOCItem, PatientAppointmentHistoryResponseModel? patientSelectedAppointment}) async {
final Map<String, dynamic> body = requestInsertCOCItem.toJson();
if (patientSelectedAppointment != null) {
body['AppoinmentNo'] = patientSelectedAppointment.appointmentNo;
body['AppointmentDate'] = patientSelectedAppointment.appointmentDate;
body['ClinicID'] = patientSelectedAppointment.clinicID;
body['ClinicName'] = patientSelectedAppointment.clinicName;
body['DoctorID'] = patientSelectedAppointment.doctorID;
body['DoctorName'] = patientSelectedAppointment.doctorNameObj;
body['ProjectName'] = patientSelectedAppointment.projectName;
}
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
await apiClient.post(
SEND_FEEDBACK,
body: body,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: response,
);
} catch (e) {
failure = DataParsingFailure(e.toString());

@ -1,7 +1,15 @@
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/api_consts.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/features/contact_us/contact_us_repo.dart';
import 'package:hmg_patient_app_new/features/contact_us/models/feedback_type.dart';
import 'package:hmg_patient_app_new/features/contact_us/models/req_models/request_insert_coc_item.dart';
import 'package:hmg_patient_app_new/features/contact_us/models/resp_models/get_hmg_locations.dart';
import 'package:hmg_patient_app_new/features/contact_us/models/resp_models/get_patient_ic_projects.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart';
import 'package:hmg_patient_app_new/services/error_handler_service.dart';
class ContactUsViewModel extends ChangeNotifier {
@ -11,17 +19,43 @@ class ContactUsViewModel extends ChangeNotifier {
bool isHMGLocationsListLoading = false;
bool isHMGHospitalsListSelected = true;
bool isLiveChatProjectsListLoading = false;
bool isSendFeedbackTabSelected = true;
List<GetHMGLocationsModel> hmgHospitalsLocationsList = [];
List<GetHMGLocationsModel> hmgPharmacyLocationsList = [];
List<GetPatientICProjectsModel> liveChatProjectsList = [];
int selectedLiveChatProjectIndex = -1;
List<String> feedbackAttachmentList = [];
PatientAppointmentHistoryResponseModel? patientFeedbackSelectedAppointment;
List<FeedbackType> feedbackTypeList = [
FeedbackType(id: 1, nameEN: "Complaint for appointment", nameAR: 'شكوى على موعد'),
FeedbackType(id: 2, nameEN: "Complaint without appointment", nameAR: 'شكوى بدون موعد'),
FeedbackType(id: 3, nameEN: "Question", nameAR: 'سؤال'),
FeedbackType(id: 4, nameEN: "Appreciation", nameAR: 'تقدير'),
FeedbackType(id: 6, nameEN: "Suggestion", nameAR: 'إقتراح'),
FeedbackType(id: 5, nameEN: "Not classified", nameAR: 'غير محدد'),
];
FeedbackType selectedFeedbackType = FeedbackType(id: 5, nameEN: "Not classified", nameAR: 'غير محدد');
ContactUsViewModel({required this.contactUsRepo, required this.errorHandlerService, required this.appState});
initContactUsViewModel() {
isHMGLocationsListLoading = true;
isHMGHospitalsListSelected = true;
isLiveChatProjectsListLoading = true;
hmgHospitalsLocationsList.clear();
hmgPharmacyLocationsList.clear();
liveChatProjectsList.clear();
feedbackAttachmentList.clear();
selectedFeedbackType = FeedbackType(id: 5, nameEN: "Not classified", nameAR: 'غير محدد');
setPatientFeedbackSelectedAppointment(null);
getHMGLocations();
notifyListeners();
}
@ -31,6 +65,36 @@ class ContactUsViewModel extends ChangeNotifier {
notifyListeners();
}
setSelectedLiveChatProjectIndex(int index) {
selectedLiveChatProjectIndex = index;
notifyListeners();
}
setIsSendFeedbackTabSelected(bool isSelected) {
isSendFeedbackTabSelected = isSelected;
notifyListeners();
}
setSelectedFeedbackType(FeedbackType feedbackType) {
selectedFeedbackType = feedbackType;
notifyListeners();
}
addFeedbackAttachment(String attachmentPath) {
feedbackAttachmentList.add(attachmentPath);
notifyListeners();
}
removeFeedbackAttachment(String attachmentPath) {
feedbackAttachmentList.remove(attachmentPath);
notifyListeners();
}
setPatientFeedbackSelectedAppointment(PatientAppointmentHistoryResponseModel? appointment) {
patientFeedbackSelectedAppointment = appointment;
notifyListeners();
}
Future<void> getHMGLocations({Function(dynamic)? onSuccess, Function(String)? onError}) async {
isHMGLocationsListLoading = true;
hmgHospitalsLocationsList.clear();
@ -62,4 +126,73 @@ class ContactUsViewModel extends ChangeNotifier {
},
);
}
Future<void> getLiveChatProjectsList({Function(dynamic)? onSuccess, Function(String)? onError}) async {
isLiveChatProjectsListLoading = true;
liveChatProjectsList.clear();
notifyListeners();
final result = await contactUsRepo.getLiveChatProjectsList();
result.fold(
(failure) async => await errorHandlerService.handleError(failure: failure),
(apiResponse) {
if (apiResponse.messageStatus == 2) {
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
} else if (apiResponse.messageStatus == 1) {
liveChatProjectsList = apiResponse.data!;
liveChatProjectsList.sort((a, b) => b.distanceInKilometers.compareTo(a.distanceInKilometers));
isLiveChatProjectsListLoading = false;
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
}
}
},
);
}
Future<void> insertCOCItem({required String subject, required String message, Function(dynamic)? onSuccess, Function(String)? onError}) async {
RequestInsertCOCItem requestInsertCOCItem = RequestInsertCOCItem();
requestInsertCOCItem.attachment = feedbackAttachmentList.isNotEmpty ? feedbackAttachmentList.first : "";
requestInsertCOCItem.title = subject;
requestInsertCOCItem.details = message;
requestInsertCOCItem.cOCTypeName = selectedFeedbackType.id.toString();
requestInsertCOCItem.formTypeID = selectedFeedbackType.id.toString();
requestInsertCOCItem.mobileNo = "966${Utils.getPhoneNumberWithoutZero(appState.getAuthenticatedUser()!.mobileNumber!)}";
requestInsertCOCItem.isUserLoggedIn = true;
requestInsertCOCItem.projectID = 0;
requestInsertCOCItem.patientName = "${appState.getAuthenticatedUser()!.firstName!} ${appState.getAuthenticatedUser()!.lastName!}";
requestInsertCOCItem.fileName = "";
requestInsertCOCItem.appVersion = ApiConsts.appVersionID;
requestInsertCOCItem.uILanguage = appState.isArabic() ? "ar" : "en"; //TODO Change it to be dynamic
requestInsertCOCItem.browserInfo = Platform.localHostname;
requestInsertCOCItem.deviceInfo = Platform.localHostname;
requestInsertCOCItem.resolution = "400x847";
requestInsertCOCItem.projectID = 0;
requestInsertCOCItem.tokenID = "C0c@@dm!n?T&A&A@Barcha202029582948";
requestInsertCOCItem.identificationNo = int.parse(appState.getAuthenticatedUser()!.patientIdentificationNo!);
if (BASE_URL.contains('uat')) {
requestInsertCOCItem.forDemo = true;
} else {
requestInsertCOCItem.forDemo = false;
}
final result = await contactUsRepo.insertCOCItem(requestInsertCOCItem: requestInsertCOCItem, patientSelectedAppointment: patientFeedbackSelectedAppointment);
result.fold(
(failure) async => await errorHandlerService.handleError(failure: failure),
(apiResponse) {
if (apiResponse.messageStatus == 2) {
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
} else if (apiResponse.messageStatus == 1) {
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
}
}
},
);
}
}

@ -0,0 +1,11 @@
class FeedbackType {
final int id;
final String nameEN;
final String nameAR;
FeedbackType({
required this.id,
required this.nameEN,
required this.nameAR,
});
}

@ -0,0 +1,137 @@
class RequestInsertCOCItem {
bool? isUserLoggedIn;
String? mobileNo;
int? identificationNo;
int? patientID;
int? patientOutSA;
int? patientTypeID;
String? tokenID;
String? patientName;
int? projectID;
String? fileName;
String? attachment;
String? uILanguage;
String? browserInfo;
String? cOCTypeName;
String? formTypeID;
String? details;
String? deviceInfo;
String? deviceType;
String? title;
String? resolution;
double? versionID;
int? channel;
int? languageID;
String? iPAdress;
String? generalid;
String? sessionID;
bool? isDentalAllowedBackend;
int? deviceTypeID;
int? patientType;
double? appVersion;
bool? forDemo;
RequestInsertCOCItem(
{this.isUserLoggedIn,
this.mobileNo,
this.identificationNo,
this.patientID,
this.patientOutSA,
this.patientTypeID,
this.tokenID,
this.patientName,
this.projectID,
this.fileName,
this.attachment,
this.uILanguage,
this.browserInfo,
this.cOCTypeName,
this.formTypeID,
this.details,
this.deviceInfo,
this.deviceType,
this.title,
this.resolution,
this.versionID,
this.channel,
this.languageID,
this.iPAdress,
this.generalid,
this.sessionID,
this.isDentalAllowedBackend,
this.deviceTypeID,
this.patientType,
this.appVersion,
this.forDemo});
RequestInsertCOCItem.fromJson(Map<String, dynamic> json) {
isUserLoggedIn = json['IsUserLoggedIn'];
mobileNo = json['MobileNo'];
identificationNo = json['IdentificationNo'];
patientID = json['PatientID'];
patientOutSA = json['PatientOutSA'];
patientTypeID = json['PatientTypeID'];
tokenID = json['TokenID'];
patientName = json['PatientName'];
projectID = json['ProjectID'];
fileName = json['FileName'];
attachment = json['Attachment'];
uILanguage = json['UILanguage'];
browserInfo = json['BrowserInfo'];
cOCTypeName = json['COCTypeName'];
formTypeID = json['FormTypeID'];
details = json['Details'];
deviceInfo = json['DeviceInfo'];
deviceType = json['DeviceType'];
title = json['Title'];
resolution = json['Resolution'];
versionID = json['VersionID'];
channel = json['Channel'];
languageID = json['LanguageID'];
iPAdress = json['IPAdress'];
generalid = json['generalid'];
sessionID = json['SessionID'];
isDentalAllowedBackend = json['isDentalAllowedBackend'];
deviceTypeID = json['DeviceTypeID'];
patientType = json['PatientType'];
appVersion = json['AppVersion'];
forDemo = json['ForDemo'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['IsUserLoggedIn'] = this.isUserLoggedIn;
data['MobileNo'] = this.mobileNo;
data['IdentificationNo'] = this.identificationNo;
data['PatientID'] = this.patientID;
data['PatientOutSA'] = this.patientOutSA;
data['PatientTypeID'] = this.patientTypeID;
data['TokenID'] = this.tokenID;
data['PatientName'] = this.patientName;
data['ProjectID'] = this.projectID;
data['FileName'] = this.fileName;
data['Attachment'] = this.attachment;
data['UILanguage'] = this.uILanguage;
data['BrowserInfo'] = this.browserInfo;
data['COCTypeName'] = this.cOCTypeName;
data['FormTypeID'] = this.formTypeID;
data['Details'] = this.details;
data['DeviceInfo'] = this.deviceInfo;
data['DeviceType'] = this.deviceType;
data['Title'] = this.title;
data['Resolution'] = this.resolution;
data['VersionID'] = this.versionID;
data['Channel'] = this.channel;
data['LanguageID'] = this.languageID;
data['IPAdress'] = this.iPAdress;
data['generalid'] = this.generalid;
data['SessionID'] = this.sessionID;
data['isDentalAllowedBackend'] = this.isDentalAllowedBackend;
data['DeviceTypeID'] = this.deviceTypeID;
data['PatientType'] = this.patientType;
data['AppVersion'] = this.appVersion;
data['ForDemo'] = this.forDemo;
return data;
}
}

@ -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<Either<Failure, GenericApiModel<List<RRTProceduresResponseModel>>>> getRRTProcedures();
Future<Either<Failure, GenericApiModel<List<RRTProceduresResponseModel>>>> getRRTProcedures(int languageId);
Future<Either<Failure, GenericApiModel<List<ProjectAvgERWaitingTime>>>> getNearestEr({int? id, int? projectID});
@ -48,7 +50,14 @@ abstract class EmergencyServicesRepo {
Future<Either<Failure, GenericApiModel<List<AmbulanceRequestOrdersModel>>>> getTransportationOrders({int? id});
Future<Either<Failure, GenericApiModel<bool>>> cancelOrder(int? iD, int patientId);
Future<Either<Failure, GenericApiModel<bool>>> cancelOrder(int? iD, int patientId);
Future<Either<Failure, GenericApiModel<bool>>> submitRRTRequest(RRTRequestModel request);
Future<Either<Failure, GenericApiModel<RRTServiceData>>> getRRTOrders({int? id});
Future<Either<Failure, GenericApiModel<bool>>> cancelRRTOrder(int? iD);
Future<Either<Failure, GenericApiModel<String>>> getTermsAndCondition();
}
class EmergencyServicesRepoImp implements EmergencyServicesRepo {
@ -95,8 +104,8 @@ class EmergencyServicesRepoImp implements EmergencyServicesRepo {
}
@override
Future<Either<Failure, GenericApiModel<List<RRTProceduresResponseModel>>>> getRRTProcedures() async {
Map<String, dynamic> mapDevice = {"ProjectID": 15};
Future<Either<Failure, GenericApiModel<List<RRTProceduresResponseModel>>>> getRRTProcedures(int languageId) async {
Map<String, dynamic> mapDevice = {"ProjectID": 15, "languageID":1};
try {
GenericApiModel<List<RRTProceduresResponseModel>>? apiResponse;
@ -558,4 +567,155 @@ class EmergencyServicesRepoImp implements EmergencyServicesRepo {
return Left(UnknownFailure(e.toString()));
}
}
@override
Future<Either<Failure, GenericApiModel<bool>>> submitRRTRequest(RRTRequestModel request) async {
try {
GenericApiModel<bool>? 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<bool>(
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<Either<Failure, GenericApiModel<bool>>> cancelRRTOrder(int? iD) async{
try {
GenericApiModel<bool>? apiResponse;
Map<String, dynamic> 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<bool>(
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<Either<Failure, GenericApiModel<RRTServiceData>>> getRRTOrders({int? id}) async {
try {
GenericApiModel<RRTServiceData>? 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<RRTServiceData>(
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<Either<Failure, GenericApiModel<String>>> getTermsAndCondition() async {
try {
GenericApiModel<String>? 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<String>(
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()));
}
}
}

@ -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<ProjectAvgERWaitingTime> nearestERList = [];
List<ProjectAvgERWaitingTime> nearestERFilteredList = [];
List<RRTProceduresResponseModel> RRTProceduresList = [];
List<HospitalsModel>? hospitalList;
List<HospitalsModel>? hmgHospitalList;
@ -83,7 +94,7 @@ class EmergencyServicesViewModel extends ChangeNotifier {
int hmcCount = 0;
bool pickupFromInsideTheLocation = true;
List<PatientAppointmentHistoryResponseModel>? appointments;
List<AmbulanceRequestOrdersModel>? orders = [];
List<AmbulanceRequestOrdersModel>? ambulanceOrders = [];
//ambulance selection data section
List<PatientERTransportationMethod> 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<RRTProceduresResponseModel> 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<void> 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<AuthenticationViewModel>().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<void> 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<void> ER_CreateAdvancePayment({required String paymentMethodName, required String paymentReference, Function(dynamic)? onSuccess, Function(String)? onError}) async {
Future<void> 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<void> 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<AuthenticationViewModel>().onLoginPressed();
getIt<AuthenticationViewModel>().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<void> 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<void> 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<void> 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<void> 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<void> 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<void> 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<void> 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<AuthenticationViewModel>().onLoginPressed();
});
}
}
clearRRTData(){
selectedRRTProcedure = null;
}
FutureOr<void> 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),
);
});
}
}

@ -0,0 +1,3 @@
enum OrderDislpay{
ALL,RRT,AMBULANCE
}

@ -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>? 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<String, dynamic> 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 = <Procedures>[];
json['procedures'].forEach((v) {
procedures!.add(new Procedures.fromJson(v));
});
}
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
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<String, dynamic> json) {
serviceID = json['ServiceID'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['ServiceID'] = this.serviceID;
return data;
}
}

@ -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<String, dynamic> toJson() {
var map = <String, dynamic>{};
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;
}
}

@ -0,0 +1,406 @@
class RRTServiceData {
List<GetCMCAllOrdersResponseModel> pendingOrders = [];
List<GetCMCAllOrdersResponseModel> 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<dynamic>? 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<String, dynamic> 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<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
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<String, dynamic> 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<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
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<String, dynamic> 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<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
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<String, dynamic> toJson() {
var map = <String, dynamic>{};
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;
}
}

@ -6,10 +6,12 @@ import 'package:hmg_patient_app_new/core/api_consts.dart';
import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart';
import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart';
import 'package:hmg_patient_app_new/features/hmg_services/models/req_models/check_activation_e_referral_req_model.dart';
import 'package:hmg_patient_app_new/features/hmg_services/models/req_models/cmc_create_new_order_req_model.dart';
import 'package:hmg_patient_app_new/features/hmg_services/models/req_models/order_update_req_model.dart';
import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_all_cities_resp_model.dart';
import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_all_orders_resp_model.dart';
import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_services_resp_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart';
import 'package:hmg_patient_app_new/services/logger_service.dart';
import 'package:provider/provider.dart';
@ -18,12 +20,32 @@ import 'models/req_models/send_activation_code_ereferral_req_model.dart';
import 'models/resq_models/relationship_type_resp_mode.dart';
abstract class HmgServicesRepo {
Future<Either<Failure, GenericApiModel<List<GetCMCAllOrdersResponseModel>>>> getAllCmcOrders();
Future<Either<Failure, GenericApiModel<List<GetCMCAllOrdersResponseModel>>>> getAllComprehensiveCheckupOrders();
Future<Either<Failure, GenericApiModel<List<GetCMCAllOrdersResponseModel>>>> getAllHomeHealthCareCheckupOrders();
Future<Either<Failure, GenericApiModel<bool>>> updateCmcPresOrder(OrderUpdateRequestModel requestModel);
Future<Either<Failure, GenericApiModel<bool>>> updateHhcPresOrder(OrderUpdateRequestModel requestModel);
Future<Either<Failure, GenericApiModel<List<GetCMCServicesResponseModel>>>> getAllCmcServices({required int patientID});
Future<Either<Failure, GenericApiModel<List<GetCMCServicesResponseModel>>>> getAllHhcServices({required int patientID});
Future<Either<Failure, GenericApiModel<List<HospitalsModel>>>> getHospitalsList();
Future<Either<Failure, GenericApiModel<int>>> addCmcOrder({
required int projectID,
required int orderServiceID,
required List<PatientERCMCInsertServicesList> services,
});
Future<Either<Failure, GenericApiModel<int>>> addHhcOrder({
required int projectID,
required int orderServiceID,
required List<PatientERCMCInsertServicesList> services,
});
Future<Either<Failure, GenericApiModel<List<GetAllRelationshipTypeResponseModel>>>> getRelationshipTypes();
Future<Either<Failure, GenericApiModel<List<GetAllCitiesResponseModel>>>> getAllCities();
@ -43,7 +65,7 @@ class HmgServicesRepoImp implements HmgServicesRepo {
HmgServicesRepoImp({required this.apiClient, required this.loggerService});
@override
Future<Either<Failure, GenericApiModel<List<GetCMCAllOrdersResponseModel>>>> getAllCmcOrders() async {
Future<Either<Failure, GenericApiModel<List<GetCMCAllOrdersResponseModel>>>> getAllComprehensiveCheckupOrders() async {
Map<String, dynamic> requestBody = {};
try {
@ -101,6 +123,65 @@ class HmgServicesRepoImp implements HmgServicesRepo {
}
}
@override
Future<Either<Failure, GenericApiModel<List<GetCMCAllOrdersResponseModel>>>> getAllHomeHealthCareCheckupOrders() async {
Map<String, dynamic> requestBody = {};
try {
GenericApiModel<List<GetCMCAllOrdersResponseModel>>? 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<GetCMCAllOrdersResponseModel> 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<String, dynamic>) {
try {
cmcOrdersList.add(GetCMCAllOrdersResponseModel.fromJson(orderJson));
} catch (e) {
loggerService.logError("Error parsing individual order: ${e.toString()}");
}
}
}
}
apiResponse = GenericApiModel<List<GetCMCAllOrdersResponseModel>>(
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<Either<Failure, GenericApiModel<List<GetCMCServicesResponseModel>>>> getAllCmcServices({required int patientID}) async {
Map<String, dynamic> requestBody = {};
@ -154,6 +235,59 @@ class HmgServicesRepoImp implements HmgServicesRepo {
}
}
@override
Future<Either<Failure, GenericApiModel<List<GetCMCServicesResponseModel>>>> getAllHhcServices({required int patientID}) async {
Map<String, dynamic> requestBody = {};
try {
GenericApiModel<List<GetCMCServicesResponseModel>>? 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<GetCMCServicesResponseModel> hhcServicesList = [];
if (response['response'] != null && response['response'] is List) {
final servicesList = response['response'] as List;
for (var serviceJson in servicesList) {
if (serviceJson is Map<String, dynamic>) {
hhcServicesList.add(GetCMCServicesResponseModel.fromJson(serviceJson));
}
}
}
apiResponse = GenericApiModel<List<GetCMCServicesResponseModel>>(
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<Either<Failure, GenericApiModel<bool>>> updateCmcPresOrder(OrderUpdateRequestModel requestModel) async {
try {
@ -194,6 +328,216 @@ class HmgServicesRepoImp implements HmgServicesRepo {
}
}
@override
Future<Either<Failure, GenericApiModel<bool>>> updateHhcPresOrder(OrderUpdateRequestModel requestModel) async {
try {
GenericApiModel<bool>? 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<bool>(
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<Either<Failure, GenericApiModel<List<HospitalsModel>>>> getHospitalsList() async {
Map<String, dynamic> requestBody = {};
try {
GenericApiModel<List<HospitalsModel>>? 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<HospitalsModel> 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<String, dynamic>) {
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<List<HospitalsModel>>(
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<Either<Failure, GenericApiModel<int>>> addCmcOrder({
required int projectID,
required int orderServiceID,
required List<PatientERCMCInsertServicesList> services,
}) async {
try {
GenericApiModel<int>? 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<int>(
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<Either<Failure, GenericApiModel<int>>> addHhcOrder({
required int projectID,
required int orderServiceID,
required List<PatientERCMCInsertServicesList> services,
}) async {
try {
GenericApiModel<int>? 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<int>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: errorMessage,
data: requestId,
);
loggerService.logInfo("HHC Order added successfully: ProjectID=$projectID, OrderServiceID=$orderServiceID");
} catch (e) {
loggerService.logError("Error processing add HHC order response: ${e.toString()}");
failure = DataParsingFailure(e.toString());
}
},
);
if (failure != null) return Left(failure!);
if (apiResponse == null) return Left(ServerFailure("Unknown error"));
return Right(apiResponse!);
} catch (e) {
loggerService.logError("Unknown error in addHhcOrder: ${e.toString()}");
return Left(UnknownFailure(e.toString()));
}
}
/*e-referral functions*/

@ -1,12 +1,15 @@
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart';
import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_repo.dart';
import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_repo.dart';
import 'package:hmg_patient_app_new/features/hmg_services/models/req_models/create_e_referral_model.dart';
import 'package:hmg_patient_app_new/features/hmg_services/models/req_models/cmc_create_new_order_req_model.dart';
import 'package:hmg_patient_app_new/features/hmg_services/models/req_models/order_update_req_model.dart';
import 'package:hmg_patient_app_new/features/hmg_services/models/req_models/send_activation_code_ereferral_req_model.dart';
import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_all_cities_resp_model.dart';
import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_all_orders_resp_model.dart';
import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_services_resp_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart';
import 'package:hmg_patient_app_new/services/error_handler_service.dart';
import 'models/req_models/check_activation_e_referral_req_model.dart';
@ -14,28 +17,68 @@ import 'models/resq_models/relationship_type_resp_mode.dart';
class HmgServicesViewModel extends ChangeNotifier {
final HmgServicesRepo hmgServicesRepo;
final BookAppointmentsRepo bookAppointmentsRepo;
final ErrorHandlerService errorHandlerService;
HmgServicesViewModel({required this.hmgServicesRepo, required this.errorHandlerService});
HmgServicesViewModel({required this.bookAppointmentsRepo, required this.hmgServicesRepo, required this.errorHandlerService});
bool isCmcOrdersLoading = false;
bool isCmcServicesLoading = false;
bool isUpdatingOrder = false;
bool isHospitalListLoading = false;
// HHC specific loading states
bool isHhcOrdersLoading = false;
bool isHhcServicesLoading = false;
List<GetCMCAllOrdersResponseModel> cmcOrdersList = [];
List<GetCMCServicesResponseModel> cmcServicesList = [];
List<HospitalsModel> hospitalsList = [];
List<HospitalsModel> filteredHospitalsList = [];
HospitalsModel? selectedHospital;
// HHC specific lists
List<GetCMCAllOrdersResponseModel> hhcOrdersList = [];
List<GetCMCServicesResponseModel> hhcServicesList = [];
// CMC order creation state
HospitalsModel? selectedHospitalForOrder;
GetCMCServicesResponseModel? selectedServiceForOrder;
// HHC order creation state (no hospital selection needed for home healthcare)
GetCMCServicesResponseModel? selectedServiceForHhcOrder;
List<GetAllRelationshipTypeResponseModel> relationTypes =[];
List<GetAllCitiesResponseModel> getAllCitiesList =[];
Future<void> getOrdersList() async {
// HHC multiple services selection
List<GetCMCServicesResponseModel> selectedHhcServices = [];
Future<void> getCmcOrdersList() async {
cmcOrdersList.clear();
isCmcOrdersLoading = true;
notifyListeners();
await getAllCmcOrders();
}
// Helper to sort hospitals by distance (ascending). Safely converts distanceinkMS to double.
void _sortHospitalsByDistance(List<HospitalsModel> 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<void> getAllCmcOrders({
Function(dynamic)? onSuccess,
Function(String)? onError,
@ -43,7 +86,7 @@ class HmgServicesViewModel extends ChangeNotifier {
isCmcOrdersLoading = true;
notifyListeners();
final result = await hmgServicesRepo.getAllCmcOrders();
final result = await hmgServicesRepo.getAllComprehensiveCheckupOrders();
result.fold(
(failure) async {
@ -146,6 +189,342 @@ class HmgServicesViewModel extends ChangeNotifier {
return success;
}
Future<void> 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<int?> addCmcOrder({
required int projectID,
required int orderServiceID,
required List<PatientERCMCInsertServicesList> 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<void> getHhcOrdersList() async {
hhcOrdersList.clear();
isHhcOrdersLoading = true;
notifyListeners();
await getAllHhcOrders();
}
Future<void> 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<void> 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<bool> 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<int?> addHhcOrder({
required int projectID,
required int orderServiceID,
required List<PatientERCMCInsertServicesList> services,
Function(int)? onSuccess,
Function(String)? onError,
}) async {
isUpdatingOrder = true;
notifyListeners();
final result = await hmgServicesRepo.addHhcOrder(
projectID: projectID,
orderServiceID: orderServiceID,
services: services,
);
int requestId = 0;
result.fold(
(failure) async {
isUpdatingOrder = false;
notifyListeners();
await errorHandlerService.handleError(failure: failure);
if (onError != null) {
onError(failure.toString());
}
},
(apiResponse) {
isUpdatingOrder = false;
if (apiResponse.messageStatus == 1) {
requestId = apiResponse.data ?? 0;
notifyListeners();
if (onSuccess != null) {
onSuccess(requestId);
}
} else {
notifyListeners();
if (onError != null) {
onError(apiResponse.errorMessage ?? 'Unknown error');
}
}
},
);
return requestId;
}
// HHC Order management methods (no hospital selection for home healthcare)
void setSelectedServiceForHhcOrder(GetCMCServicesResponseModel? service) {
selectedServiceForHhcOrder = service;
notifyListeners();
}
void clearHhcOrderSelection() {
selectedServiceForHhcOrder = null;
selectedHhcServices.clear();
notifyListeners();
}
bool get isHhcOrderReadyToConfirm => selectedServiceForHhcOrder != null;
// Multiple HHC services selection methods
void toggleHhcServiceSelection(GetCMCServicesResponseModel service) {
final index = selectedHhcServices.indexWhere((s) => s.iD == service.iD);
if (index != -1) {
selectedHhcServices.removeAt(index);
} else {
selectedHhcServices.add(service);
}
notifyListeners();
}
bool isHhcServiceSelected(GetCMCServicesResponseModel service) {
return selectedHhcServices.any((s) => s.iD == service.iD);
}
double getHhcSelectedServicesTotal() {
double total = 0.0;
for (var service in selectedHhcServices) {
total += (service.priceTotal ?? 0);
}
return total;
}
void clearHhcServicesSelection() {
selectedHhcServices.clear();
notifyListeners();
}
/*e-referral functions*/
Future<void> getRelationshipType({

@ -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<String, dynamic> json) {
recordID = json['RecordID'];
serviceID = json['ServiceID'];
selectedServiceName = json['selectedServiceName'];
selectedServiceNameAR = json['selectedServiceNameAR'];
price = json['Price'];
vAT = json['VAT'];
totalPrice = json['TotalPrice'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = <String, dynamic>{};
data['RecordID'] = recordID;
data['ServiceID'] = serviceID;
data['selectedServiceName'] = selectedServiceName;
data['selectedServiceNameAR'] = selectedServiceNameAR;
data['Price'] = price;
data['VAT'] = vAT;
data['TotalPrice'] = totalPrice;
return data;
}
}

@ -21,6 +21,7 @@ abstract class LabRepo {
Future<Either<Failure, GenericApiModel<String>>> getLabResultReportPDF({required PatientLabOrdersResponseModel labOrder});
Future<Either<Failure, GenericApiModel<dynamic>>> getLabResultsByAppointmentNo({required num appointmentNo, required num projectID, required num clinicID});
}
class LabRepoImp implements LabRepo {
@ -135,6 +136,7 @@ class LabRepoImp implements LabRepo {
request['SetupID'] = laborder!.setupID;
request['ProjectID'] = laborder.projectID;
request['ClinicID'] = laborder.clinicID;
request['InvoiceType'] = laborder.invoiceType ?? "";
try {
GenericApiModel<List<LabResult>>? apiResponse;
Failure? failure;
@ -184,6 +186,7 @@ class LabRepoImp implements LabRepo {
request['SetupID'] = laborder!.setupID;
request['ProjectID'] = laborder.projectID;
request['ClinicID'] = laborder.clinicID;
request['InvoiceType'] = laborder.invoiceType ?? "";
try {
GenericApiModel<List<PatientLabSpecialResult>>? apiResponse;
Failure? failure;
@ -278,4 +281,41 @@ class LabRepoImp implements LabRepo {
return Left(UnknownFailure(e.toString()));
}
}
@override
Future<Either<Failure, GenericApiModel>> getLabResultsByAppointmentNo({required num appointmentNo, required num projectID, required num clinicID}) async {
Map<String, dynamic> request = {};
request['AppointmentNo'] = appointmentNo;
request['ProjectID'] = projectID;
request['ClinicID'] = clinicID;
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
await apiClient.post(
GET_PATIENT_LAB_ORDERS_BY_APPOINTMENT,
body: request,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: response['ListLabResultsByAppNo'],
);
} catch (e) {
failure = DataParsingFailure(e.toString());
}
},
);
if (failure != null) return Left(failure!);
if (apiResponse == null) return Left(ServerFailure("Unknown error"));
return Right(apiResponse!);
} catch (e) {
return Left(UnknownFailure(e.toString()));
}
}
}

@ -9,11 +9,13 @@ import 'package:hmg_patient_app_new/core/utils/utils.dart' show Utils;
import 'package:hmg_patient_app_new/features/lab/lab_repo.dart';
import 'package:hmg_patient_app_new/features/lab/models/resp_models/lab_result.dart';
import 'package:hmg_patient_app_new/features/lab/models/resp_models/patient_lab_orders_response_model.dart';
import 'package:hmg_patient_app_new/presentation/lab/lab_result_via_clinic/LabResultByClinic.dart';
import 'package:hmg_patient_app_new/presentation/lab/lab_results/lab_result_details.dart';
import 'package:hmg_patient_app_new/services/error_handler_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'package:intl/intl.dart' show DateFormat;
import 'package:logger/logger.dart';
@ -75,14 +77,14 @@ class LabViewModel extends ChangeNotifier {
required this.navigationService});
initLabProvider() {
if (isLabNeedToLoad) {
// if (isLabNeedToLoad) {
patientLabOrders.clear();
filteredLabOrders.clear();
labOrderTests.clear();
isLabOrdersLoading = true;
isLabResultsLoading = true;
getPatientLabOrders();
}
// }
notifyListeners();
}
@ -92,7 +94,7 @@ class LabViewModel extends ChangeNotifier {
}
Future<void> getPatientLabOrders({Function(dynamic)? onSuccess, Function(String)? onError}) async {
if (!isLabNeedToLoad) return;
// if (!isLabNeedToLoad) return;
isLabOrdersLoading = true;
patientLabOrders.clear();
@ -158,7 +160,7 @@ class LabViewModel extends ChangeNotifier {
filterSuggestions() {
final List<String> labels = patientLabOrders
.expand((order) => order.testDetails!)
.expand((order) => order.testDetails ?? [])
.map((detail) => detail.description)
.whereType<String>()
.toList();
@ -198,6 +200,69 @@ class LabViewModel extends ChangeNotifier {
}
}
Future<void> getLabResultsByAppointmentNo(
{required num appointmentNo,
required num projectID,
required num clinicID,
required int doctorID,
required String clinicName,
required String doctorName,
required String projectName,
required String appointmentDate,
Function(dynamic)? onSuccess,
Function(String)? onError}) async {
bool isVidaPlus = Utils.isVidaPlusProject(projectID.toInt());
final result = await labRepo.getLabResultsByAppointmentNo(appointmentNo: appointmentNo, projectID: projectID, clinicID: clinicID);
result.fold(
(failure) async {
// await errorHandlerService.handleError(failure: failure);
if (onError != null) {
onError(failure.message);
}
},
(apiResponse) {
if (apiResponse.messageStatus == 2) {
if (onError != null) {
onError(apiResponse.errorMessage!);
}
} else if (apiResponse.messageStatus == 1) {
if (apiResponse.data != null && apiResponse.data!.isNotEmpty) {
PatientLabOrdersResponseModel labOrder = PatientLabOrdersResponseModel();
labOrder.invoiceNoVP = isVidaPlus ? apiResponse.data[0]['InvoiceNo'].toString() : "0";
labOrder.invoiceNo = isVidaPlus ? "0" : apiResponse.data[0]['InvoiceNo'].toString();
labOrder.orderNo = apiResponse.data[0]['OrderNo'].toString();
labOrder.invoiceType = apiResponse.data[0]['InvoiceType'].toString();
labOrder.setupID = apiResponse.data[0]['SetupID'].toString();
labOrder.projectID = projectID.toString();
labOrder.clinicID = clinicID.toInt();
labOrder.doctorID = doctorID;
labOrder.clinicDescription = clinicName;
labOrder.doctorName = doctorName;
labOrder.projectName = projectName;
labOrder.orderDate = appointmentDate;
currentlySelectedPatientOrder = labOrder;
getPatientLabResultByHospital(labOrder);
getPatientSpecialResult(labOrder);
if (onSuccess != null) {
onSuccess(apiResponse);
}
navigationService.push(
CustomPageRoute(
page: LabResultByClinic(labOrder: labOrder),
),
);
} else {}
notifyListeners();
}
},
);
}
Future<void> getPatientLabResultByHospital(
PatientLabOrdersResponseModel laborder) async {
isLabResultByHospitalLoading = true;

@ -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<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
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;
}
}

@ -15,6 +15,8 @@ abstract class RadiologyRepo {
Future<Either<Failure, GenericApiModel<String>>> getRadiologyReportPDF(
{required PatientRadiologyResponseModel patientRadiologyResponseModel, required AuthenticatedUser authenticatedUser});
Future<Either<Failure, GenericApiModel<List<PatientRadiologyResponseModel>>>> getPatientRadiologyOrderByAppointment({required num appointmentNo, required num projectID});
}
class RadiologyRepoImp implements RadiologyRepo {
@ -168,4 +170,50 @@ class RadiologyRepoImp implements RadiologyRepo {
return Left(UnknownFailure(e.toString()));
}
}
@override
Future<Either<Failure, GenericApiModel<List<PatientRadiologyResponseModel>>>> getPatientRadiologyOrderByAppointment({required num appointmentNo, required num projectID}) async {
Map<String, dynamic> mapDevice = {
"AppointmentNo": appointmentNo,
"ProjectID": projectID,
};
try {
GenericApiModel<List<PatientRadiologyResponseModel>>? apiResponse;
Failure? failure;
await apiClient.post(
GET_PATIENT_ORDERS,
body: mapDevice,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
final radOrders;
try {
if (response['FinalRadiologyList'] != null && response['FinalRadiologyList'].length != 0) {
final list = response['FinalRadiologyList'];
radOrders = list.map((item) => PatientRadiologyResponseModel.fromJson(item as Map<String, dynamic>)).toList().cast<PatientRadiologyResponseModel>();
} else {
final list = response['FinalRadiologyListAPI'];
radOrders = list.map((item) => PatientRadiologyResponseModel.fromJson(item as Map<String, dynamic>)).toList().cast<PatientRadiologyResponseModel>();
}
apiResponse = GenericApiModel<List<PatientRadiologyResponseModel>>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: radOrders,
);
} catch (e) {
failure = DataParsingFailure(e.toString());
}
},
);
if (failure != null) return Left(failure!);
if (apiResponse == null) return Left(ServerFailure("Unknown error"));
return Right(apiResponse!);
} catch (e) {
return Left(UnknownFailure(e.toString()));
}
}
}

@ -1,7 +1,11 @@
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/features/authentication/models/resp_models/authenticated_user_resp_model.dart';
import 'package:hmg_patient_app_new/features/radiology/radiology_repo.dart';
import 'package:hmg_patient_app_new/presentation/radiology/radiology_result_page.dart';
import 'package:hmg_patient_app_new/services/error_handler_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'models/resp_models/patient_radiology_response_model.dart';
@ -11,6 +15,7 @@ class RadiologyViewModel extends ChangeNotifier {
RadiologyRepo radiologyRepo;
ErrorHandlerService errorHandlerService;
NavigationService navigationService;
List<PatientRadiologyResponseModel> patientRadiologyOrders = [];
List<PatientRadiologyResponseModel> filteredRadiologyOrders = [];
@ -22,7 +27,9 @@ class RadiologyViewModel extends ChangeNotifier {
List<String> get radiologySuggestions => _radiologySuggestionsList;
RadiologyViewModel({required this.radiologyRepo, required this.errorHandlerService});
late PatientRadiologyResponseModel patientRadiologyOrderByAppointment;
RadiologyViewModel({required this.radiologyRepo, required this.errorHandlerService, required this.navigationService});
initRadiologyViewModel() {
patientRadiologyOrders.clear();
@ -57,6 +64,35 @@ class RadiologyViewModel extends ChangeNotifier {
);
}
Future<void> getPatientRadiologyOrdersByAppointment({required num appointmentNo, required num projectID, Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result = await radiologyRepo.getPatientRadiologyOrderByAppointment(appointmentNo: appointmentNo, projectID: projectID);
result.fold(
(failure) async => await errorHandlerService.handleError(failure: failure),
(apiResponse) {
if (apiResponse.messageStatus == 2) {
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
} else if (apiResponse.messageStatus == 1) {
notifyListeners();
if (apiResponse.data!.isNotEmpty) {
if (onSuccess != null) {
onSuccess(apiResponse);
}
navigationService.push(
CustomPageRoute(
page: RadiologyResultPage(patientRadiologyResponseModel: apiResponse.data!.first),
),
);
} else {
if (onError != null) {
onError("No Radiology Orders Found".needTranslation);
}
}
}
},
);
}
Future<void> getRadiologyImage(
{required PatientRadiologyResponseModel patientRadiologyResponseModel, Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result = await radiologyRepo.getRadiologyImage(patientRadiologyResponseModel: patientRadiologyResponseModel);
@ -107,23 +143,20 @@ class RadiologyViewModel extends ChangeNotifier {
},
);
}
filterSuggestions() {
final List<String> labels = patientRadiologyOrders
.map((detail) => detail.description)
.whereType<String>()
.toList();
final List<String> labels = patientRadiologyOrders.map((detail) => detail.description).whereType<String>().toList();
_radiologySuggestionsList = labels.toSet().toList();
notifyListeners();
}
filterRadiologyReports(String query) {
if (query.isEmpty) {
patientRadiologyOrders =tempRadiologyOrders;// reset
patientRadiologyOrders = tempRadiologyOrders; // reset
} else {
filteredRadiologyOrders =
filteredRadiologyOrders.where((desc) => desc.description!.toLowerCase().contains(query.toLowerCase())).toList();
filteredRadiologyOrders = filteredRadiologyOrders.where((desc) => desc.description!.toLowerCase().contains(query.toLowerCase())).toList();
patientRadiologyOrders = filteredRadiologyOrders;
}
notifyListeners();
}
}

@ -11,26 +11,32 @@ import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctors_list_response_model.dart';
import 'package:hmg_patient_app_new/features/contact_us/contact_us_view_model.dart';
import 'package:hmg_patient_app_new/features/lab/lab_view_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/utils/appointment_type.dart';
import 'package:hmg_patient_app_new/features/prescriptions/models/resp_models/patient_prescriptions_response_model.dart';
import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart';
import 'package:hmg_patient_app_new/features/radiology/radiology_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/appointments/appointment_payment_page.dart';
import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_checkin_bottom_sheet.dart';
import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_doctor_card.dart';
import 'package:hmg_patient_app_new/presentation/appointments/widgets/ask_doctor_request_type_select.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/widgets/appointment_calendar.dart';
import 'package:hmg_patient_app_new/presentation/contact_us/feedback_page.dart';
import 'package:hmg_patient_app_new/presentation/lab/lab_orders_page.dart';
import 'package:hmg_patient_app_new/presentation/medical_file/widgets/lab_rad_card.dart';
import 'package:hmg_patient_app_new/presentation/prescriptions/prescription_detail_page.dart';
import 'package:hmg_patient_app_new/presentation/prescriptions/prescriptions_list_page.dart';
import 'package:hmg_patient_app_new/presentation/radiology/radiology_orders_page.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'package:hmg_patient_app_new/widgets/shimmer/common_shimmer_widget.dart';
import 'package:maps_launcher/maps_launcher.dart';
import 'package:provider/provider.dart';
@ -50,14 +56,17 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
late MyAppointmentsViewModel myAppointmentsViewModel;
late PrescriptionsViewModel prescriptionsViewModel;
late BookAppointmentsViewModel bookAppointmentsViewModel;
late ContactUsViewModel contactUsViewModel;
late LabViewModel labViewModel;
late RadiologyViewModel radiologyViewModel;
@override
void initState() {
scheduleMicrotask(() {
if (AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)) {
prescriptionsViewModel.setPrescriptionsDetailsLoading();
prescriptionsViewModel.getPrescriptionDetails(getPrescriptionRequestModel());
}
// if (AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)) {
// prescriptionsViewModel.setPrescriptionsDetailsLoading();
// prescriptionsViewModel.getPrescriptionDetails(getPrescriptionRequestModel());
// }
});
super.initState();
}
@ -68,6 +77,9 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
myAppointmentsViewModel = Provider.of<MyAppointmentsViewModel>(context, listen: false);
prescriptionsViewModel = Provider.of<PrescriptionsViewModel>(context, listen: false);
bookAppointmentsViewModel = Provider.of<BookAppointmentsViewModel>(context, listen: false);
contactUsViewModel = Provider.of<ContactUsViewModel>(context, listen: false);
labViewModel = Provider.of<LabViewModel>(context, listen: false);
radiologyViewModel = Provider.of<RadiologyViewModel>(context, listen: false);
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: Column(
@ -75,7 +87,16 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
Expanded(
child: CollapsingListView(
title: "Appointment Details".needTranslation,
report: AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) ? () {} : null,
report: AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)
? () {
contactUsViewModel.setPatientFeedbackSelectedAppointment(widget.patientAppointmentHistoryResponseModel);
Navigator.of(context).push(
CustomPageRoute(
page: FeedbackPage(),
),
);
}
: null,
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
@ -263,219 +284,345 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
SizedBox(height: 16.h),
],
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"Lab & Radiology".needTranslation.toText18(isBold: true),
SizedBox(height: 16.h),
GridView(
padding: EdgeInsets.zero,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: isTablet || isFoldable ? 3 : 2,
crossAxisSpacing: 13.w,
mainAxisSpacing: 13.w,
),
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
children: [
MedicalFileCard(
label: LocaleKeys.labResults.tr(context: context),
textColor: AppColors.blackColor,
backgroundColor: AppColors.whiteColor,
svgIcon: AppAssets.lab_result_icon,
iconSize: 40.w,
isLargeText: true,
),
MedicalFileCard(
label: "Radiology Results".needTranslation,
textColor: AppColors.blackColor,
backgroundColor: AppColors.whiteColor,
svgIcon: AppAssets.radiology_icon,
iconSize: 40.w,
isLargeText: true,
),
MedicalFileCard(
label: LocaleKeys.labResults.tr(context: context),
textColor: AppColors.blackColor,
backgroundColor: AppColors.whiteColor,
svgIcon: AppAssets.lab_result_icon,
iconSize: 40.w,
isLargeText: true,
),
MedicalFileCard(
label: "Radiology Results".needTranslation,
textColor: AppColors.blackColor,
backgroundColor: AppColors.whiteColor,
svgIcon: AppAssets.radiology_icon,
iconSize: 40.w,
isLargeText: true,
),
],
),
SizedBox(height: 16.h),
LocaleKeys.prescriptions.tr().toText18(isBold: true),
SizedBox(height: 16.h),
Consumer<PrescriptionsViewModel>(builder: (context, prescriptionVM, child) {
return prescriptionVM.isPrescriptionsDetailsLoading
? const CommonShimmerWidget()
: Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: Colors.white,
borderRadius: 20.r,
),
padding: EdgeInsets.all(16.w),
child: Column(
children: [
ListView.separated(
itemCount: prescriptionVM.prescriptionDetailsList.length,
shrinkWrap: true,
padding: EdgeInsets.only(right: 8.w),
physics: NeverScrollableScrollPhysics(),
itemBuilder: (context, index) {
return AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 500),
child: SlideAnimation(
verticalOffset: 100.0,
child: FadeInAnimation(
child: Row(
children: [
Utils.buildSvgWithAssets(
icon: AppAssets.prescription_item_icon,
width: 40.h,
height: 40.h,
),
SizedBox(width: 8.h),
Row(
mainAxisSize: MainAxisSize.max,
children: [
Column(
children: [
prescriptionVM.prescriptionDetailsList[index].itemDescription!
.toText12(isBold: true, maxLine: 1),
"Prescribed By: ${widget.patientAppointmentHistoryResponseModel.doctorTitle} ${widget.patientAppointmentHistoryResponseModel.doctorNameObj}"
.needTranslation
.toText10(
weight: FontWeight.w500,
color: AppColors.greyTextColor,
letterSpacing: -0.4),
],
),
SizedBox(width: 68.w),
Transform.flip(
flipX: appState.isArabic(),
child: Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon,
iconColor: AppColors.blackColor,
width: 18.w,
height: 13.h,
fit: BoxFit.contain,
),
),
],
),
],
),
),
),
);
},
separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h),
).onPress(() {
prescriptionVM.setPrescriptionsDetailsLoading();
Navigator.of(context).push(
CustomPageRoute(
page: PrescriptionDetailPage(prescriptionsResponseModel: getPrescriptionRequestModel()),
),
);
}),
SizedBox(height: 16.h),
const Divider(color: AppColors.dividerColor),
SizedBox(height: 16.h),
Wrap(
runSpacing: 6.w,
children: [
// Expanded(
// child: CustomButton(
// text: widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported! ? LocaleKeys.resendOrder.tr(context: context) : LocaleKeys.prescriptionDeliveryError.tr(context: context),
// onPressed: () {},
// backgroundColor: AppColors.secondaryLightRedColor,
// borderColor: AppColors.secondaryLightRedColor,
// textColor: AppColors.primaryRedColor,
// fontSize: 14,
// fontWeight: FontWeight.w500,
// borderRadius: 12.h,
// height: 40.h,
// icon: AppAssets.appointment_calendar_icon,
// iconColor: AppColors.primaryRedColor,
// iconSize: 16.h,
// ),
// ),
// SizedBox(width: 16.h),
Expanded(
child: CustomButton(
text: "Refill & Delivery".needTranslation,
onPressed: () {
Navigator.of(context)
.push(
CustomPageRoute(
page: PrescriptionsListPage(),
),
)
.then((val) {
prescriptionsViewModel.setPrescriptionsDetailsLoading();
prescriptionsViewModel.getPrescriptionDetails(getPrescriptionRequestModel());
});
},
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: 14.f,
fontWeight: FontWeight.w500,
borderRadius: 12.r,
height: 40.h,
icon: AppAssets.requests,
iconColor: AppColors.primaryRedColor,
iconSize: 16.h,
),
),
SizedBox(width: 16.w),
Expanded(
child: CustomButton(
text: "All Prescriptions".needTranslation,
onPressed: () {
Navigator.of(context)
.push(
CustomPageRoute(
page: PrescriptionsListPage(),
),
)
.then((val) {
prescriptionsViewModel.setPrescriptionsDetailsLoading();
prescriptionsViewModel.getPrescriptionDetails(getPrescriptionRequestModel());
});
},
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: 14.f,
fontWeight: FontWeight.w500,
borderRadius: 12.r,
height: 40.h,
icon: AppAssets.requests,
iconColor: AppColors.primaryRedColor,
iconSize: 16.h,
),
),
],
),
],
// : SizedBox.shrink()
: GridView(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 3,
crossAxisSpacing: 16.h,
mainAxisSpacing: 16.w,
mainAxisExtent: 115.h,
),
physics: NeverScrollableScrollPhysics(),
padding: EdgeInsets.zero,
shrinkWrap: true,
children: [
MedicalFileCard(
label: LocaleKeys.labResults.tr(context: context),
textColor: AppColors.blackColor,
backgroundColor: AppColors.whiteColor,
svgIcon: AppAssets.lab_result_icon,
isLargeText: true,
iconSize: 36.w,
).onPress(() async {
LoaderBottomSheet.showLoader(loadingText: "Fetching Lab Results...".needTranslation);
await labViewModel.getLabResultsByAppointmentNo(
appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo,
projectID: widget.patientAppointmentHistoryResponseModel.projectID,
clinicID: widget.patientAppointmentHistoryResponseModel.clinicID,
doctorID: widget.patientAppointmentHistoryResponseModel.doctorID,
doctorName: widget.patientAppointmentHistoryResponseModel.doctorNameObj!,
clinicName: widget.patientAppointmentHistoryResponseModel.clinicName!,
projectName: widget.patientAppointmentHistoryResponseModel.projectName!,
appointmentDate: widget.patientAppointmentHistoryResponseModel.appointmentDate!,
onSuccess: (val) {
LoaderBottomSheet.hideLoader();
},
onError: (err) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: err),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
},
);
}),
MedicalFileCard(
label: "${LocaleKeys.radiology.tr(context: context)} ${LocaleKeys.radiologySubtitle.tr(context: context)}",
textColor: AppColors.blackColor,
backgroundColor: AppColors.whiteColor,
svgIcon: AppAssets.allergy_info_icon,
isLargeText: true,
iconSize: 36.w,
).onPress(() async {
LoaderBottomSheet.showLoader(loadingText: "Fetching Radiology Results...".needTranslation);
await radiologyViewModel.getPatientRadiologyOrdersByAppointment(
appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo,
projectID: widget.patientAppointmentHistoryResponseModel.projectID,
onSuccess: (val) {
LoaderBottomSheet.hideLoader();
},
onError: (err) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: err),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
},
);
}),
MedicalFileCard(
label: LocaleKeys.prescriptions.tr(context: context),
textColor: AppColors.blackColor,
backgroundColor: AppColors.whiteColor,
svgIcon: AppAssets.prescription_item_icon,
isLargeText: true,
iconSize: 36.w,
).onPress(() async {
LoaderBottomSheet.showLoader(loadingText: "Fetching Appointment Prescriptions...".needTranslation);
await prescriptionsViewModel.getPrescriptionDetails(
getPrescriptionRequestModel(),
onSuccess: (val) {
LoaderBottomSheet.hideLoader();
if (val.data.isNotEmpty) {
PatientPrescriptionsResponseModel patientPrescriptionsResponseModel = PatientPrescriptionsResponseModel(
doctorImageURL: widget.patientAppointmentHistoryResponseModel.doctorImageURL,
doctorName: widget.patientAppointmentHistoryResponseModel.doctorNameObj,
appointmentDate: widget.patientAppointmentHistoryResponseModel.appointmentDate,
clinicDescription: widget.patientAppointmentHistoryResponseModel.clinicName,
decimalDoctorRate: widget.patientAppointmentHistoryResponseModel.decimalDoctorRate,
name: widget.patientAppointmentHistoryResponseModel.projectName,
isHomeMedicineDeliverySupported: false,
projectID: widget.patientAppointmentHistoryResponseModel.projectID,
clinicID: widget.patientAppointmentHistoryResponseModel.clinicID,
doctorID: widget.patientAppointmentHistoryResponseModel.doctorID,
setupID: widget.patientAppointmentHistoryResponseModel.setupID,
);
Navigator.of(context).push(
CustomPageRoute(
page: PrescriptionDetailPage(isFromAppointments: true, prescriptionsResponseModel: patientPrescriptionsResponseModel),
),
);
}),
],
),
} else {
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: "You don't have any prescriptions for this appointment.".needTranslation),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
}
},
onError: (err) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: err),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
},
);
// Navigator.of(context).push(
// CustomPageRoute(
// page: VaccineListPage(),
// ),
// );
}),
],
),
// Column(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// "Lab & Radiology".needTranslation.toText18(isBold: true),
// SizedBox(height: 16.h),
// Row(
// children: [
// Expanded(
// child: LabRadCard(
// icon: AppAssets.lab_result_icon,
// labelText: LocaleKeys.labResults.tr(context: context),
// // labOrderTests: ["Complete blood count", "Creatinine", "Blood Sugar"],
// // labOrderTests: labViewModel.isLabOrdersLoading ? [] : labViewModel.labOrderTests,
// labOrderTests: [],
// // isLoading: labViewModel.isLabOrdersLoading,
// isLoading: false,
// ).onPress(() {
// Navigator.of(context).push(
// CustomPageRoute(
// page: LabOrdersPage(),
// ),
// );
// }),
// ),
// SizedBox(width: 16.h),
// Expanded(
// child: LabRadCard(
// icon: AppAssets.radiology_icon,
// labelText: LocaleKeys.radiology.tr(context: context),
// // labOrderTests: ["Chest X-ray", "Abdominal Ultrasound", "Dental X-ray"],
// labOrderTests: [],
// isLoading: false,
// ).onPress(() {
// Navigator.of(context).push(
// CustomPageRoute(
// page: RadiologyOrdersPage(),
// ),
// );
// }),
// ),
// ],
// ),
// SizedBox(height: 16.h),
// LocaleKeys.prescriptions.tr().toText18(isBold: true),
// SizedBox(height: 16.h),
// Consumer<PrescriptionsViewModel>(builder: (context, prescriptionVM, child) {
// return prescriptionVM.isPrescriptionsDetailsLoading
// ? const MoviesShimmerWidget()
// : Container(
// decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
// color: Colors.white,
// borderRadius: 20.r,
// ),
// padding: EdgeInsets.all(16.w),
// child: Column(
// children: [
// // ListView.separated(
// // itemCount: prescriptionVM.prescriptionDetailsList.length,
// // shrinkWrap: true,
// // padding: EdgeInsets.only(right: 8.w),
// // physics: NeverScrollableScrollPhysics(),
// // itemBuilder: (context, index) {
// // return AnimationConfiguration.staggeredList(
// // position: index,
// // duration: const Duration(milliseconds: 500),
// // child: SlideAnimation(
// // verticalOffset: 100.0,
// // child: FadeInAnimation(
// // child: Row(
// // children: [
// // Utils.buildSvgWithAssets(
// // icon: AppAssets.prescription_item_icon,
// // width: 40.h,
// // height: 40.h,
// // ),
// // SizedBox(width: 8.h),
// // Row(
// // mainAxisSize: MainAxisSize.max,
// // children: [
// // Column(
// // children: [
// // prescriptionVM.prescriptionDetailsList[index].itemDescription!
// // .toText12(isBold: true, maxLine: 1),
// // "Prescribed By: ${widget.patientAppointmentHistoryResponseModel.doctorTitle} ${widget.patientAppointmentHistoryResponseModel.doctorNameObj}"
// // .needTranslation
// // .toText10(
// // weight: FontWeight.w500,
// // color: AppColors.greyTextColor,
// // letterSpacing: -0.4),
// // ],
// // ),
// // SizedBox(width: 68.w),
// // Transform.flip(
// // flipX: appState.isArabic(),
// // child: Utils.buildSvgWithAssets(
// // icon: AppAssets.forward_arrow_icon,
// // iconColor: AppColors.blackColor,
// // width: 18.w,
// // height: 13.h,
// // fit: BoxFit.contain,
// // ),
// // ),
// // ],
// // ),
// // ],
// // ),
// // ),
// // ),
// // );
// // },
// // separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h),
// // ).onPress(() {
// // prescriptionVM.setPrescriptionsDetailsLoading();
// // Navigator.of(context).push(
// // CustomPageRoute(
// // page: PrescriptionDetailPage(prescriptionsResponseModel: getPrescriptionRequestModel()),
// // ),
// // );
// // }),
// SizedBox(height: 16.h),
// const Divider(color: AppColors.dividerColor),
// SizedBox(height: 16.h),
// // Wrap(
// // runSpacing: 6.w,
// // children: [
// // // Expanded(
// // // child: CustomButton(
// // // text: widget.prescriptionsResponseModel.isHomeMedicineDeliverySupported! ? LocaleKeys.resendOrder.tr(context: context) : LocaleKeys.prescriptionDeliveryError.tr(context: context),
// // // onPressed: () {},
// // // backgroundColor: AppColors.secondaryLightRedColor,
// // // borderColor: AppColors.secondaryLightRedColor,
// // // textColor: AppColors.primaryRedColor,
// // // fontSize: 14,
// // // fontWeight: FontWeight.w500,
// // // borderRadius: 12.h,
// // // height: 40.h,
// // // icon: AppAssets.appointment_calendar_icon,
// // // iconColor: AppColors.primaryRedColor,
// // // iconSize: 16.h,
// // // ),
// // // ),
// // // SizedBox(width: 16.h),
// // Expanded(
// // child: CustomButton(
// // text: "Refill & Delivery".needTranslation,
// // onPressed: () {
// // Navigator.of(context)
// // .push(
// // CustomPageRoute(
// // page: PrescriptionsListPage(),
// // ),
// // )
// // .then((val) {
// // prescriptionsViewModel.setPrescriptionsDetailsLoading();
// // prescriptionsViewModel.getPrescriptionDetails(getPrescriptionRequestModel());
// // });
// // },
// // backgroundColor: AppColors.secondaryLightRedColor,
// // borderColor: AppColors.secondaryLightRedColor,
// // textColor: AppColors.primaryRedColor,
// // fontSize: 14.f,
// // fontWeight: FontWeight.w500,
// // borderRadius: 12.r,
// // height: 40.h,
// // icon: AppAssets.requests,
// // iconColor: AppColors.primaryRedColor,
// // iconSize: 16.h,
// // ),
// // ),
// //
// // SizedBox(width: 16.w),
// // Expanded(
// // child: CustomButton(
// // text: "All Prescriptions".needTranslation,
// // onPressed: () {
// // Navigator.of(context)
// // .push(
// // CustomPageRoute(
// // page: PrescriptionsListPage(),
// // ),
// // )
// // .then((val) {
// // prescriptionsViewModel.setPrescriptionsDetailsLoading();
// // prescriptionsViewModel.getPrescriptionDetails(getPrescriptionRequestModel());
// // });
// // },
// // backgroundColor: AppColors.secondaryLightRedColor,
// // borderColor: AppColors.secondaryLightRedColor,
// // textColor: AppColors.primaryRedColor,
// // fontSize: 14.f,
// // fontWeight: FontWeight.w500,
// // borderRadius: 12.r,
// // height: 40.h,
// // icon: AppAssets.requests,
// // iconColor: AppColors.primaryRedColor,
// // iconSize: 16.h,
// // ),
// // ),
// // ],
// // ),
// ],
// ),
// );
// }),
// ],
// ),
],
).paddingAll(24.w),
),

@ -10,6 +10,7 @@ import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/doctors_list_response_model.dart';
import 'package:hmg_patient_app_new/features/contact_us/contact_us_view_model.dart';
import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart';
@ -33,7 +34,9 @@ class AppointmentCard extends StatelessWidget {
final bool isFromHomePage;
final bool isFromMedicalReport;
final bool isForEyeMeasurements;
final bool isForFeedback;
final MedicalFileViewModel? medicalFileViewModel;
final ContactUsViewModel? contactUsViewModel;
final BookAppointmentsViewModel bookAppointmentsViewModel;
const AppointmentCard({
@ -45,7 +48,9 @@ class AppointmentCard extends StatelessWidget {
this.isFromHomePage = false,
this.isFromMedicalReport = false,
this.isForEyeMeasurements = false,
this.isForFeedback = false,
this.medicalFileViewModel,
this.contactUsViewModel,
});
@override
@ -179,7 +184,11 @@ class AppointmentCard extends StatelessWidget {
return CustomButton(
text: 'Select appointment'.needTranslation,
onPressed: () {
medicalFileViewModel!.setSelectedMedicalReportAppointment(patientAppointmentHistoryResponseModel);
if (isForFeedback) {
contactUsViewModel!.setPatientFeedbackSelectedAppointment(patientAppointmentHistoryResponseModel);
} else {
medicalFileViewModel!.setSelectedMedicalReportAppointment(patientAppointmentHistoryResponseModel);
}
Navigator.pop(context, false);
},
backgroundColor: AppColors.secondaryLightRedColor,
@ -313,6 +322,7 @@ class AppointmentCard extends StatelessWidget {
}
void _goToDetails(BuildContext context) {
if (isFromMedicalReport) return;
if (isForEyeMeasurements) {
Navigator.of(context).push(
CustomPageRoute(

@ -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),
)
],

@ -1,11 +1,14 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_export.dart';
import 'package:hmg_patient_app_new/core/cache_consts.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/services/cache_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
@ -21,18 +24,13 @@ class QuickLogin extends StatefulWidget {
}
class QuickLoginState extends State<QuickLogin> {
final CacheService cacheService = GetIt.instance<CacheService>();
@override
Widget build(BuildContext context) {
NavigationService navigationService = getIt.get<NavigationService>();
return Container(
decoration: const BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.only(
topLeft: Radius.circular(24),
topRight: Radius.circular(24),
),
),
padding: const EdgeInsets.all(24),
return Padding(
padding: EdgeInsets.all(24.h),
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
@ -104,11 +102,12 @@ class QuickLoginState extends State<QuickLogin> {
borderColor: Color(0xffED1C2B),
textColor: Colors.white,
icon: AppAssets.apple_finder,
height: 56.h,
)),
],
),
SizedBox(
height: 16,
height: 16.h,
),
Row(
mainAxisAlignment: MainAxisAlignment.end,
@ -117,11 +116,13 @@ class QuickLoginState extends State<QuickLogin> {
child: CustomButton(
text: LocaleKeys.notNow.tr(),
onPressed: () {
cacheService.saveBool(key: CacheConst.quickLoginEnabled, value: false);
Navigator.pop(context, "true");
},
backgroundColor: Color(0xffFEE9EA),
borderColor: Color(0xffFEE9EA),
textColor: Colors.red,
height: 56.h,
// icon: "assets/images/svg/apple-finder.svg",
)),
],

@ -37,7 +37,7 @@ class ClinicItem extends StatelessWidget {
Transform.flip(
flipX: isArabic,
child: Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon,
icon: AppAssets.forward_arrow_icon_small,
width: 15.h,
height: 15.h,
fit: BoxFit.contain,

@ -140,8 +140,9 @@ class _AppointmentCalendarState extends State<AppointmentCalendar> {
},
),
),
SizedBox(height: 10.h),
Transform.translate(
offset: const Offset(0.0, -20.0),
offset: const Offset(0.0, -10.0),
child: selectedDateDisplay.toText16(weight: FontWeight.w500),
),
//TODO: Add Next Day Span here

@ -65,25 +65,28 @@ class DoctorCard extends StatelessWidget {
.toString()
.toText16(isBold: true, maxlines: 1),
).toShimmer2(isShow: isLoading),
],
),
SizedBox(height: 2.h),
Row(
children: [
(isLoading
? "Consultant Cardiologist"
: doctorsListResponseModel.speciality!.isNotEmpty
? doctorsListResponseModel.speciality!.first
: "")
.toString()
.toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor, maxLine: 1)
.toShimmer2(isShow: isLoading),
SizedBox(width: 6.w),
Image.network(
isLoading
? "https://hmgwebservices.com/Images/flag/SYR.png"
: doctorsListResponseModel.nationalityFlagURL ?? "https://hmgwebservices.com/Images/flag/SYR.png",
isLoading ? "https://hmgwebservices.com/Images/flag/SYR.png" : doctorsListResponseModel.nationalityFlagURL ?? "https://hmgwebservices.com/Images/flag/SYR.png",
width: 20.h,
height: 15.h,
fit: BoxFit.fill,
).toShimmer2(isShow: isLoading),
],
),
SizedBox(height: 2.h),
(isLoading
? "Consultant Cardiologist"
: doctorsListResponseModel.speciality!.isNotEmpty
? doctorsListResponseModel.speciality!.first
: "")
.toString()
.toText12(fontWeight: FontWeight.w500, color: AppColors.greyTextColor, maxLine: 1)
.toShimmer2(isShow: isLoading),
],
),
),

@ -1,18 +1,21 @@
import 'dart:async';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:fluttertoast/fluttertoast.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_view_model.dart';
import 'package:hmg_patient_app_new/features/hmg_services/models/req_models/order_update_req_model.dart';
import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_all_orders_resp_model.dart';
import 'package:hmg_patient_app_new/services/dialog_service.dart';
import 'package:hmg_patient_app_new/presentation/comprehensive_checkup/widgets/cmc_ui_selection_helper.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:intl/intl.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
import 'package:provider/provider.dart';
import 'package:shimmer/shimmer.dart';
class CmcOrderDetailPage extends StatefulWidget {
const CmcOrderDetailPage({super.key});
@ -22,14 +25,12 @@ class CmcOrderDetailPage extends StatefulWidget {
}
class _CmcOrderDetailPageState extends State<CmcOrderDetailPage> {
bool _isLoading = false;
@override
void initState() {
super.initState();
final hmgServicesViewModel = context.read<HmgServicesViewModel>();
scheduleMicrotask(() async {
await hmgServicesViewModel.getOrdersList();
await hmgServicesViewModel.getCmcOrdersList();
});
}
@ -54,234 +55,129 @@ class _CmcOrderDetailPageState extends State<CmcOrderDetailPage> {
if (dateString == null) return '';
try {
final date = DateTime.parse(dateString);
return DateFormat('dd MMM yyyy').format(date);
return DateFormat('MMM dd, yyyy').format(date);
} catch (e) {
return dateString;
}
}
Future<void> _showCancelConfirmationDialog({
required BuildContext context,
required HmgServicesViewModel viewModel,
required GetCMCAllOrdersResponseModel order,
}) async {
final dialogService = context.read<DialogService>();
await dialogService.showCommonBottomSheetWithoutH(
label: "Confirm Cancellation".needTranslation,
message: "Are you sure you want to cancel this order?".needTranslation,
onOkPressed: () async {
Navigator.of(context).pop();
// Show loading state
setState(() {
_isLoading = true;
});
final requestModel = OrderUpdateRequestModel(
presOrderID: order.iD,
rejectionReason: "",
presOrderStatus: 4, // Cancelled status
editedBy: 3,
);
final success = await viewModel.updateCmcPresOrder(
requestModel: requestModel,
onSuccess: (_) async {
setState(() {
_isLoading = false;
});
Fluttertoast.showToast(
msg: "Order cancelled successfully".needTranslation,
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM,
backgroundColor: Colors.green,
textColor: Colors.white,
);
await viewModel.getAllCmcOrders();
},
onError: (error) {
setState(() {
_isLoading = false;
});
Fluttertoast.showToast(
msg: error,
toastLength: Toast.LENGTH_SHORT,
gravity: ToastGravity.BOTTOM,
backgroundColor: Colors.red,
textColor: Colors.white,
);
},
);
if (!success) {
setState(() {
_isLoading = false;
});
}
},
onCancelPressed: () {
Navigator.of(context).pop();
},
);
}
Widget _buildLoadingShimmer() {
return ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
padding: EdgeInsets.all(21.w),
itemCount: 3,
separatorBuilder: (_, __) => SizedBox(height: 12.h),
itemBuilder: (context, index) {
return Shimmer.fromColors(
baseColor: Colors.grey[300]!,
highlightColor: Colors.grey[100]!,
child: Container(
height: 120.h,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10.r),
),
),
);
return _buildOrderCard(GetCMCAllOrdersResponseModel(), isLoading: true);
},
);
}
Widget _buildOrderCard(GetCMCAllOrdersResponseModel order) {
Widget _buildOrderCard(GetCMCAllOrdersResponseModel order, {bool isLoading = false}) {
final statusColor = _getStatusColor(order.statusId);
final canCancel = order.statusId == 1 || order.statusId == 2;
return Container(
decoration: BoxDecoration(
color: statusColor,
borderRadius: BorderRadius.circular(10.r),
boxShadow: [
BoxShadow(
color: const Color(0xff000000).withValues(alpha: 0.05),
blurRadius: 27,
offset: const Offset(0, -3),
),
],
return AnimatedContainer(
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.h,
hasShadow: true,
),
child: Container(
margin: EdgeInsets.only(left: 6.w),
padding: EdgeInsets.symmetric(vertical: 14.h, horizontal: 12.w),
decoration: BoxDecoration(
color: Colors.white,
border: Border.all(color: Colors.white, width: 1),
borderRadius: BorderRadius.only(
bottomRight: Radius.circular(10.r),
topRight: Radius.circular(10.r),
),
),
child: Row(
child: Padding(
padding: EdgeInsets.all(16.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
order.statusText ?? '',
style: TextStyle(
fontSize: 12.f,
fontWeight: FontWeight.w600,
color: statusColor,
letterSpacing: -0.4,
height: 16 / 10,
),
),
SizedBox(height: 6.h),
Text(
'${"Request ID".needTranslation}: ${order.iD}',
style: TextStyle(
fontSize: 16.f,
fontWeight: FontWeight.w600,
color: const Color(0xff2E303A),
letterSpacing: -0.64,
height: 25 / 16,
),
),
SizedBox(height: 4.h),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"${"Hospital".needTranslation}: ",
style: TextStyle(
fontSize: 12.f,
fontWeight: FontWeight.w600,
color: const Color(0xff575757),
letterSpacing: -0.4,
height: 16 / 10,
),
),
Expanded(
child: Text(
order.projectName?.trim() ?? '',
style: TextStyle(
fontSize: 14.f,
fontWeight: FontWeight.w600,
color: const Color(0xff2B353E),
letterSpacing: -0.56,
),
),
),
],
),
],
),
),
Column(
// Status and Date Row
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.end,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
_formatDate(order.created),
style: TextStyle(
fontSize: 12.f,
fontWeight: FontWeight.w600,
color: const Color(0xff2B353E),
letterSpacing: -0.4,
height: 16 / 10,
Container(
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 6.h),
decoration: BoxDecoration(
color: statusColor.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(8.r),
),
child: (isLoading ? "Processing" : order.statusText ?? '')
.toText12(
color: statusColor,
fontWeight: FontWeight.w600,
)
.toShimmer2(isShow: isLoading),
),
if (canCancel) ...[
SizedBox(height: 12.h),
InkWell(
onTap: _isLoading
? null
: () {
_showCancelConfirmationDialog(
context: context,
viewModel: context.read<HmgServicesViewModel>(),
order: order,
);
},
child: Container(
padding: EdgeInsets.symmetric(vertical: 8.h, horizontal: 14.w),
decoration: BoxDecoration(
color: _isLoading ? Colors.grey : const Color(0xffD02127),
border: Border.all(color: Colors.white, width: 1),
borderRadius: BorderRadius.circular(10.r),
),
child: Text(
"Cancel".needTranslation,
style: TextStyle(
fontSize: 12.f,
fontWeight: FontWeight.w600,
color: Colors.white,
letterSpacing: -0.4,
),
SizedBox(width: 8.w),
(isLoading ? "Jan 15, 2024" : _formatDate(order.created))
.toText12(
color: AppColors.textColorLight,
fontWeight: FontWeight.w500,
)
.toShimmer2(isShow: isLoading),
],
),
SizedBox(height: 16.h),
// Request ID
Row(
children: [
if (!isLoading) ...[
"Request ID:".needTranslation.toText14(
color: AppColors.textColorLight,
weight: FontWeight.w500,
),
),
),
SizedBox(width: 4.w),
],
(isLoading ? "12345" : "${order.iD ?? '-'}").toText16(isBold: true).toShimmer2(isShow: isLoading),
],
),
SizedBox(height: 12.h),
// Chips for Hospital, Service, and Amount
Wrap(
spacing: 6.w,
runSpacing: 6.h,
children: [
// Hospital
if (order.projectName != null || isLoading)
AppCustomChipWidget(
icon: AppAssets.hospital,
labelText: isLoading ? "Hospital Name" : order.projectName ?? '-',
).toShimmer2(isShow: isLoading),
// Service
if (order.serviceText != null || isLoading)
AppCustomChipWidget(
icon: AppAssets.servicesBottom,
labelText: isLoading ? "Service Name" : order.serviceText ?? '-',
).toShimmer2(isShow: isLoading),
],
),
// Cancel Button
if (canCancel || isLoading) ...[
SizedBox(height: 16.h),
Row(
children: [
Expanded(
child: CustomButton(
text: "Cancel Order".needTranslation,
onPressed: isLoading ? () {} : () => CmcUiSelectionHelper.showCancelConfirmationDialog(context: context, order: order),
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,
textColor: AppColors.whiteColor,
fontSize: 14.f,
fontWeight: FontWeight.w600,
borderRadius: 10.r,
height: 44.h,
).toShimmer2(isShow: isLoading),
),
],
),
]
],
),
),
@ -290,20 +186,22 @@ class _CmcOrderDetailPageState extends State<CmcOrderDetailPage> {
Widget _buildEmptyState() {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.inbox_outlined, size: 80.w, color: AppColors.greyColor),
SizedBox(height: 16.h),
Text(
"No orders found".needTranslation,
style: TextStyle(
fontSize: 16.f,
color: AppColors.greyTextColor,
fontWeight: FontWeight.w500,
),
child: Padding(
padding: EdgeInsets.symmetric(vertical: 40.h),
child: Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 12.r,
hasShadow: false,
),
],
child: Utils.getNoDataWidget(
context,
noDataText: "You don't have any CMC orders yet.".needTranslation,
isSmallWidget: true,
width: 62.w,
height: 62.h,
),
),
),
);
}
@ -313,31 +211,44 @@ class _CmcOrderDetailPageState extends State<CmcOrderDetailPage> {
return CollapsingListView(
title: "CMC Orders".needTranslation,
isLeading: true,
child: Consumer<HmgServicesViewModel>(
builder: (context, viewModel, child) {
if (viewModel.isCmcOrdersLoading) {
return _buildLoadingShimmer();
}
if (viewModel.cmcOrdersList.isEmpty) {
return SizedBox(
height: MediaQuery.of(context).size.height * 0.6,
child: _buildEmptyState(),
);
}
return ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
padding: EdgeInsets.all(21.w),
itemCount: viewModel.cmcOrdersList.length,
separatorBuilder: (_, __) => SizedBox(height: 12.h),
itemBuilder: (context, index) {
final order = viewModel.cmcOrdersList.reversed.toList()[index];
return _buildOrderCard(order);
},
);
},
child: SingleChildScrollView(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Consumer<HmgServicesViewModel>(
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),
),
);
}

@ -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<CmcSelectionReviewPage> createState() => _CmcSelectionReviewPageState();
}
class _CmcSelectionReviewPageState extends State<CmcSelectionReviewPage> {
@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>();
hmgServicesViewModel.setSelectedHospitalForOrder(widget.preSelectedHospital);
hmgServicesViewModel.setSelectedServiceForOrder(widget.selectedService);
});
}
}
@override
Widget build(BuildContext context) {
final appState = getIt.get<AppState>();
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<HmgServicesViewModel>(
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<PayfortViewModel>(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<HmgServicesViewModel>(
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<HmgServicesViewModel>();
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,
);
}
}

@ -1,5 +1,6 @@
import 'dart:async';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
@ -12,9 +13,12 @@ import 'package:hmg_patient_app_new/features/hmg_services/hmg_services_view_mode
import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_all_orders_resp_model.dart';
import 'package:hmg_patient_app_new/features/hmg_services/models/resq_models/get_cmc_services_resp_model.dart';
import 'package:hmg_patient_app_new/presentation/comprehensive_checkup/cmc_order_detail_page.dart';
import 'package:hmg_patient_app_new/presentation/comprehensive_checkup/cmc_selection_review_page.dart';
import 'package:hmg_patient_app_new/presentation/comprehensive_checkup/widgets/cmc_ui_selection_helper.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
import 'package:hmg_patient_app_new/widgets/media_viewer/full_screen_image_viewer.dart';
import 'package:hmg_patient_app_new/widgets/radio_list_tile_widget.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
@ -67,120 +71,142 @@ class _ComprehensiveCheckupPageState extends State<ComprehensiveCheckupPage> {
if (status == 1) {
// pending
statusColor = const Color(0xffCC9B14);
statusColor = AppColors.statusPendingColor;
} else if (status == 2) {
// processing
statusColor = const Color(0xff2E303A);
statusColor = AppColors.statusProcessingColor;
} else if (status == 3) {
// completed
statusColor = const Color(0xff359846);
statusColor = AppColors.statusCompletedColor;
} else {
// cancel / rejected
statusColor = const Color(0xffD02127);
statusColor = AppColors.statusRejectedColor;
}
final canCancel = order.statusId == 1 || order.statusId == 2;
return Container(
width: double.infinity,
margin: EdgeInsets.all(16.h),
decoration: BoxDecoration(
margin: EdgeInsets.all(16.w),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: BorderRadius.circular(12.h),
boxShadow: [
BoxShadow(
color: Color.fromARGB(13, 0, 0, 0),
blurRadius: 4,
offset: const Offset(0, 2),
),
],
borderRadius: 24.h,
hasShadow: true,
),
child: Container(
padding: EdgeInsets.all(14.h),
child: Padding(
padding: EdgeInsets.all(16.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Status and Date Row
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
statusDisp,
style: TextStyle(
fontSize: 12.h,
fontWeight: FontWeight.w600,
color: statusColor,
letterSpacing: -0.4,
),
),
SizedBox(height: 6.h),
Text(
'${"Request ID".needTranslation}: ${order.iD}',
style: TextStyle(
fontSize: 16.h,
fontWeight: FontWeight.w600,
color: AppColors.blackColor,
letterSpacing: -0.64,
),
),
SizedBox(height: 4.h),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"${"Hospital".needTranslation}: ",
style: TextStyle(
fontSize: 12.h,
fontWeight: FontWeight.w600,
color: AppColors.greyTextColor,
letterSpacing: -0.4,
),
),
Expanded(
child: Text(
order.projectName ?? "",
style: TextStyle(
fontSize: 14.h,
fontWeight: FontWeight.w600,
color: const Color(0xff2B353E),
letterSpacing: -0.56,
),
),
),
],
),
SizedBox(height: 4.h),
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
"${"Service Name".needTranslation}: ",
style: TextStyle(
fontSize: 12.h,
fontWeight: FontWeight.w600,
color: AppColors.greyTextColor,
letterSpacing: -0.4,
),
),
Expanded(
child: Text(
order.serviceText ?? "",
style: TextStyle(
fontSize: 14.h,
fontWeight: FontWeight.w600,
color: const Color(0xff2B353E),
letterSpacing: -0.56,
),
),
),
],
),
],
Container(
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 6.h),
decoration: BoxDecoration(
color: statusColor.withValues(alpha: 0.1),
borderRadius: BorderRadius.circular(8.r),
),
child: statusDisp.toText12(
color: statusColor,
fontWeight: FontWeight.w600,
),
),
SizedBox(width: 8.w),
if (order.created != null)
DateFormat('MMM dd, yyyy').format(DateTime.parse(order.created!)).toText12(
color: AppColors.textColorLight,
fontWeight: FontWeight.w500,
),
],
),
SizedBox(height: 16.h),
// Request ID
Row(
children: [
"Request ID:".needTranslation.toText14(color: AppColors.textColorLight, weight: FontWeight.w500),
SizedBox(width: 4.w),
"${order.iD ?? '-'}".toText16(isBold: true),
],
),
SizedBox(height: 12.h),
// Chips for Hospital, Service, and Amount
Wrap(
spacing: 6.w,
runSpacing: 6.h,
children: [
// Hospital
if (order.projectName != null)
AppCustomChipWidget(
icon: AppAssets.hospital,
labelText: order.projectName ?? '-',
),
// Service
if (order.serviceText != null)
AppCustomChipWidget(
icon: AppAssets.file_icon,
labelText: order.serviceText ?? '-',
),
],
),
SizedBox(height: 16.h),
// Info message
Container(
padding: EdgeInsets.all(12.w),
decoration: BoxDecoration(
color: AppColors.infoBannerBgColor,
borderRadius: BorderRadius.circular(10.r),
border: Border.all(
color: AppColors.infoBannerBorderColor,
width: 1,
),
),
child: Row(
children: [
Icon(
Icons.info_outline,
size: 20.w,
color: AppColors.infoBannerIconColor,
),
SizedBox(width: 8.w),
Expanded(
child: "You have a pending order. Please wait for it to be processed.".needTranslation.toText12(
color: AppColors.infoBannerTextColor,
fontWeight: FontWeight.w500,
),
),
],
),
),
if (canCancel) ...[
SizedBox(height: 16.h),
Row(
children: [
Expanded(
child: CustomButton(
text: "Cancel Order".needTranslation,
onPressed: () => CmcUiSelectionHelper.showCancelConfirmationDialog(context: context, order: order),
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,
textColor: AppColors.whiteColor,
fontSize: 14.f,
fontWeight: FontWeight.w600,
borderRadius: 10.r,
height: 44.h,
),
),
],
),
]
],
),
),
@ -264,10 +290,14 @@ class _ComprehensiveCheckupPageState extends State<ComprehensiveCheckupPage> {
color: Color.fromARGB(51, 0, 0, 0),
borderRadius: BorderRadius.circular(1000.r),
),
margin: EdgeInsets.all(12.h),
margin: EdgeInsets.all(16.h),
child: IconButton(
icon: Icon(Icons.zoom_in, color: Colors.white),
padding: EdgeInsets.all(12.h),
icon: Icon(
Icons.zoom_in,
color: Colors.white,
size: 26.w,
),
padding: EdgeInsets.all(10.h),
onPressed: () => _showFullScreenImage(context, imagePath, isSvg: false),
),
),
@ -281,13 +311,22 @@ class _ComprehensiveCheckupPageState extends State<ComprehensiveCheckupPage> {
void _proceedWithSelectedService() {
if (_selectedService != null) {
// TODO: Navigate to next step or create order
// This will be implemented based on your flow
// For now, just show a message
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('Selected: ${_selectedService!.text}'),
),
final hmgServicesViewModel = context.read<HmgServicesViewModel>();
// Store selected service in ViewModel
hmgServicesViewModel.setSelectedServiceForOrder(_selectedService);
hmgServicesViewModel.getHospitalsList();
// Show hospital selection bottom sheet using common helper
CmcUiSelectionHelper.showHospitalSelectionBottomSheet(
context: context,
onHospitalSelected: (hospital) {
Navigator.of(context).pushReplacement(
CustomPageRoute(
page: CmcSelectionReviewPage(selectedService: _selectedService!, preSelectedHospital: hospital),
direction: AxisDirection.left,
),
);
},
);
}
}
@ -296,15 +335,15 @@ class _ComprehensiveCheckupPageState extends State<ComprehensiveCheckupPage> {
return ListView.separated(
shrinkWrap: true,
physics: const NeverScrollableScrollPhysics(),
padding: EdgeInsets.all(21.w),
itemCount: 3,
padding: EdgeInsets.all(16.w),
itemCount: 10,
separatorBuilder: (_, __) => SizedBox(height: 12.h),
itemBuilder: (context, index) {
return Shimmer.fromColors(
baseColor: Colors.grey[300]!,
highlightColor: Colors.grey[100]!,
child: Container(
height: 120.h,
height: 80.h,
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.circular(10.r),
@ -319,23 +358,16 @@ class _ComprehensiveCheckupPageState extends State<ComprehensiveCheckupPage> {
Widget build(BuildContext context) {
return CollapsingListView(
title: "Comprehensive Checkup".needTranslation,
requests: () {
Navigator.of(context).push(CustomPageRoute(page: CmcOrderDetailPage(), direction: AxisDirection.up));
},
// bottomChild sticks to the bottom of the scaffold; make it reactive using Consumer
history: () => Navigator.of(context).push(CustomPageRoute(page: CmcOrderDetailPage(), direction: AxisDirection.up)),
bottomChild: Consumer<HmgServicesViewModel>(
builder: (context, hmgServicesViewModel, child) {
// if still loading, don't show the bottom button
if (hmgServicesViewModel.isCmcOrdersLoading || hmgServicesViewModel.isCmcServicesLoading) return SizedBox.shrink();
final pendingOrder = _getPendingOrder(hmgServicesViewModel.cmcOrdersList);
// show button only when there is no pending order and a service is selected
if (pendingOrder == null && _selectedServiceId != null) {
return SafeArea(
top: false,
child: Padding(
padding: EdgeInsets.symmetric(horizontal: 16.w, vertical: 12.h),
padding: EdgeInsets.only(left: 16.w, right: 16.w, top: 12.h),
child: CustomButton(
borderWidth: 0,
text: "Next".needTranslation,
@ -348,7 +380,6 @@ class _ComprehensiveCheckupPageState extends State<ComprehensiveCheckupPage> {
),
);
}
return SizedBox.shrink();
},
),
@ -357,12 +388,8 @@ class _ComprehensiveCheckupPageState extends State<ComprehensiveCheckupPage> {
if (hmgServicesViewModel.isCmcOrdersLoading || hmgServicesViewModel.isCmcServicesLoading) {
return _buildLoadingShimmer();
}
final pendingOrder = _getPendingOrder(hmgServicesViewModel.cmcOrdersList);
if (pendingOrder != null) {
// Return the pending order card directly. The outer CollapsingListView's
// CustomScrollView/SliverList will provide proper scroll constraints.
return _buildPendingOrderCard(pendingOrder);
} else {
return _buildServiceSelectionList(hmgServicesViewModel.cmcServicesList);

@ -1,195 +0,0 @@
// import 'package:hmg_patient_app/core/enum/viewstate.dart';
// import 'package:hmg_patient_app/core/model/AlHabibMedicalService/ComprehensiveMedicalCheckup/cmc_insert_pres_order_request_model.dart';
// import 'package:hmg_patient_app/core/viewModels/AlHabibMedicalService/cmc_view_model.dart';
// import 'package:hmg_patient_app/core/viewModels/project_view_model.dart';
// import 'package:hmg_patient_app/pages/AlHabibMedicalService/ComprehensiveMedicalCheckup/NewCMC/new_cmc_step_three_page.dart';
// import 'package:hmg_patient_app/uitl/translations_delegate_base.dart';
// import 'package:hmg_patient_app/uitl/utils.dart';
// import 'package:hmg_patient_app/uitl/utils_new.dart';
// import 'package:hmg_patient_app/widgets/buttons/defaultButton.dart';
// import 'package:hmg_patient_app/widgets/dragable_sheet.dart';
// import 'package:hmg_patient_app/widgets/others/app_scaffold_widget.dart';
// import 'package:hmg_patient_app/widgets/photo_view_page.dart';
// import 'package:flutter/cupertino.dart';
// import 'package:flutter/material.dart';
// import 'package:flutter_svg/svg.dart';
// import 'package:provider/provider.dart';
//
// import 'new_cmc_step_tow_page.dart';
//
// class NewCMCStepOnePage extends StatefulWidget {
// final CMCInsertPresOrderRequestModel cMCInsertPresOrderRequestModel;
// final Function changePageViewIndex;
// final CMCViewModel model;
//
// final double latitude;
// final double longitude;
//
// const NewCMCStepOnePage({Key? key, required this.cMCInsertPresOrderRequestModel, required this.model, required this.changePageViewIndex, required this.latitude, required this.longitude})
// : super(key: key);
//
// @override
// _NewCMCStepOnePageState createState() => _NewCMCStepOnePageState();
// }
//
// class _NewCMCStepOnePageState extends State<NewCMCStepOnePage> {
// int selectedItem = 0;
//
// @override
// void initState() {
// super.initState();
// }
//
// @override
// Widget build(BuildContext context) {
// ProjectViewModel projectViewModel = Provider.of(context);
//
// return AppScaffold(
// isShowAppBar: false,
// baseViewModel: widget.model,
// body: Column(
// children: [
// Expanded(
// child: SingleChildScrollView(
// physics: BouncingScrollPhysics(),
// padding: EdgeInsets.all(21),
// child: Column(
// children: [
// ListView.separated(
// physics: NeverScrollableScrollPhysics(),
// shrinkWrap: true,
// itemBuilder: (context, index) {
// return Row(
// children: [
// Radio(
// value: num.tryParse(widget.model.cmcAllServicesList[index].serviceID!),
// activeColor: Colors.red[800],
// onChanged: (newValue) async {
// selectedItem = index;
// PatientERCMCInsertServicesList patientERCMCInsertServicesList = PatientERCMCInsertServicesList(
// price: widget.model.cmcAllServicesList[index].price,
// serviceID: widget.model.cmcAllServicesList[index].serviceID,
// selectedServiceName: widget.model.cmcAllServicesList[index].text,
// selectedServiceNameAR: widget.model.cmcAllServicesList[index].textN,
// recordID: 1,
// totalPrice: widget.model.cmcAllServicesList[index].priceTotal,
// vAT: widget.model.cmcAllServicesList[index].priceVAT);
// setState(() {
// widget.cMCInsertPresOrderRequestModel.patientERCMCInsertServicesList = [patientERCMCInsertServicesList];
// });
// // CMCGetItemsRequestModel cMCGetItemsRequestModel = new CMCGetItemsRequestModel(checkupType: newValue);
// // await widget.model.getCheckupItems(cMCGetItemsRequestModel: cMCGetItemsRequestModel);
// },
// groupValue: widget.cMCInsertPresOrderRequestModel.patientERCMCInsertServicesList!.length > 0
// ? int.parse(widget.cMCInsertPresOrderRequestModel.patientERCMCInsertServicesList![0].serviceID!)
// : 1),
// Expanded(
// child: Padding(
// padding: const EdgeInsets.only(
// left: 10,
// right: 10,
// top: 20,
// bottom: 20,
// ),
// child: Text(
// projectViewModel.isArabic ? widget.model.cmcAllServicesList[index].textN! : widget.model.cmcAllServicesList[index].text!,
// style: TextStyle(
// fontSize: 14,
// fontWeight: FontWeight.w600,
// letterSpacing: -0.45,
// ),
// ),
// ),
// ),
// ],
// );
// },
// separatorBuilder: (context, index) {
// return mDivider(Colors.grey);
// },
// itemCount: widget.model.cmcAllServicesList.length),
// Stack(
// children: [
// Image.asset(
// projectViewModel.isArabic ? "assets/images/cc_ar.png" : "assets/images/cc_en.png",
// width: double.infinity,
// ),
// Align(
// alignment: Alignment.topRight,
// child: Container(
// decoration: containerColorRadiusBorder(
// Colors.black.withOpacity(0.2),
// 1000,
// Colors.white,
// ),
// margin: EdgeInsets.all(12),
// child: IconButton(
// icon: SvgPicture.asset(
// "assets/images/new/ic_zoom.svg",
// color: Colors.white,
// ),
// padding: EdgeInsets.all(12),
// onPressed: () {
// showDraggableDialog(context, PhotoViewPage(projectViewModel.isArabic ? "assets/images/cc_ar.png" : "assets/images/cc_en.png"));
// },
// ),
// ),
// ),
// ],
// ),
// ],
// ),
// ),
// ),
// Container(
// color: Colors.white,
// padding: EdgeInsets.only(top: 16, bottom: 16, right: 21, left: 21),
// child: DefaultButton(
// TranslationBase.of(context).next,
// () async {
// if (widget.cMCInsertPresOrderRequestModel.patientERCMCInsertServicesList!.length != 0 || widget.cMCInsertPresOrderRequestModel.patientERCMCInsertServicesList == null) {
// // int index = widget.model.cmcAllServicesList.length;
// PatientERCMCInsertServicesList patientERCMCInsertServicesList = new PatientERCMCInsertServicesList(
// price: widget.model.cmcAllServicesList[selectedItem].price,
// serviceID: widget.model.cmcAllServicesList[selectedItem].serviceID.toString(),
// selectedServiceName: widget.model.cmcAllServicesList[selectedItem].text,
// selectedServiceNameAR: widget.model.cmcAllServicesList[selectedItem].textN,
// recordID: 1,
// totalPrice: widget.model.cmcAllServicesList[selectedItem].priceTotal,
// vAT: widget.model.cmcAllServicesList[selectedItem].priceVAT,
// );
//
// widget.cMCInsertPresOrderRequestModel.patientID = projectViewModel.user!.patientID;
// widget.cMCInsertPresOrderRequestModel.patientOutSA = projectViewModel.user!.outSA;
//
// widget.cMCInsertPresOrderRequestModel.patientERCMCInsertServicesList = [patientERCMCInsertServicesList];
// navigateTo(
// context,
// NewCMCStepThreePage(
// cmcInsertPresOrderRequestModel: widget.cMCInsertPresOrderRequestModel,
// model: widget.model,
// ),
// );
// // await widget.model.getCustomerInfo();
// if (widget.model.state == ViewState.ErrorLocal) {
// Utils.showErrorToast();
// } else {
// // navigateTo(
// // context,
// // NewCMCStepTowPage(
// // longitude: widget.longitude,
// // latitude: widget.latitude,
// // cmcInsertPresOrderRequestModel: widget.cMCInsertPresOrderRequestModel,
// // model: widget.model,
// // ),
// // );
// }
// }
// },
// ),
// ),
// ],
// ),
// );
// }
// }

@ -1,183 +0,0 @@
// import 'package:hmg_patient_app/core/enum/viewstate.dart';
// import 'package:hmg_patient_app/core/model/AlHabibMedicalService/ComprehensiveMedicalCheckup/GetCMCAllOrdersResponseModel.dart';
// import 'package:hmg_patient_app/core/model/AlHabibMedicalService/HomeHealthCare/get_hhc_all_pres_orders_response_model.dart';
// import 'package:hmg_patient_app/core/model/AlHabibMedicalService/HomeHealthCare/update_pres_oreder_request_model.dart';
// import 'package:hmg_patient_app/core/viewModels/AlHabibMedicalService/cmc_view_model.dart';
// import 'package:hmg_patient_app/core/viewModels/project_view_model.dart';
// import 'package:hmg_patient_app/uitl/app_toast.dart';
// import 'package:hmg_patient_app/uitl/date_uitl.dart';
// import 'package:hmg_patient_app/uitl/gif_loader_dialog_utils.dart';
// import 'package:hmg_patient_app/uitl/translations_delegate_base.dart';
// import 'package:hmg_patient_app/uitl/utils.dart';
// import 'package:hmg_patient_app/uitl/utils_new.dart';
// import 'package:hmg_patient_app/widgets/buttons/defaultButton.dart';
// import 'package:hmg_patient_app/widgets/buttons/secondary_button.dart';
// import 'package:hmg_patient_app/widgets/data_display/text.dart';
// import 'package:hmg_patient_app/widgets/dialogs/ConfirmWithMessageDialog.dart';
// import 'package:hmg_patient_app/widgets/others/app_scaffold_widget.dart';
// import 'package:flutter/material.dart';
// import 'package:provider/provider.dart';
//
// import 'Dialog/confirm_cancel_order_dialog.dart';
//
// class OrdersLogDetailsPage extends StatelessWidget {
// final CMCViewModel model;
//
// const OrdersLogDetailsPage({Key ?key, required this.model}) : super(key: key);
//
// @override
// Widget build(BuildContext context) {
// ProjectViewModel projectViewModel = Provider.of(context);
//
// void showConfirmMessage(CMCViewModel model, GetCMCAllOrdersResponseModel order) {
// showDialog(
// context: context,
// builder: (cxt) => ConfirmWithMessageDialog(
// message: TranslationBase.of(context).cancelOrderMsg,
// onTap: () {
// UpdatePresOrderRequestModel updatePresOrderRequestModel = UpdatePresOrderRequestModel(presOrderID: order.iD, rejectionReason: "", presOrderStatus: 4, editedBy: 3);
// Future.delayed(new Duration(milliseconds: 300)).then((value) async {
// GifLoaderDialogUtils.showMyDialog(context);
// await model.updateCmcPresOrder(updatePresOrderRequestModel);
// if (model.state == ViewState.ErrorLocal) {
// Utils.showErrorToast(model.error);
// GifLoaderDialogUtils.hideDialog(context);
// } else {
// AppToast.showSuccessToast(message: TranslationBase.of(context).processDoneSuccessfully);
// await model.getCmcAllPresOrders();
// GifLoaderDialogUtils.hideDialog(context);
// }
// });
// },
// ));
// return;
// }
//
// return AppScaffold(
// isShowAppBar: false,
// baseViewModel: model,
// body: model.cmcAllPresOrders.length > 0 ? ListView.separated(
// padding: EdgeInsets.all(21),
// physics: BouncingScrollPhysics(),
// itemBuilder: (context, index) {
// GetCMCAllOrdersResponseModel order = model.cmcAllPresOrders.reversed.toList()[index];
//
// int status = order.statusId!;
// String _statusDisp = order.statusText!;
// late Color _color;
// if (status == 1) {
// //pending
// _color = Color(0xffCC9B14);
// } else if (status == 2) {
// //processing
// _color = Color(0xff2E303A);
// } else if (status == 3) {
// //completed
// _color = Color(0xff359846);
// } else if (status == 4 || status == 6 || status == 7) {
// //cancel // Rejected
// _color = Color(0xffD02127);
// }
// return Container(
// decoration: BoxDecoration(
// color: _color,
// borderRadius: BorderRadius.all(
// Radius.circular(10.0),
// ),
// boxShadow: [
// BoxShadow(
// color: Color(0xff000000).withOpacity(.05),
// blurRadius: 27,
// offset: Offset(0, -3),
// ),
// ],
// ),
// child: Container(
// margin: EdgeInsets.only(left: projectViewModel.isArabic ? 0 : 6, right: projectViewModel.isArabic ? 6 : 0),
// padding: EdgeInsets.symmetric(vertical: 14, horizontal: 12),
// decoration: BoxDecoration(
// color: Colors.white,
// border: Border.all(color: Colors.white, width: 1),
// borderRadius: BorderRadius.only(
// bottomRight: projectViewModel.isArabic ? Radius.circular(0) : Radius.circular(10.0),
// topRight: projectViewModel.isArabic ? Radius.circular(0) : Radius.circular(10.0),
// bottomLeft: projectViewModel.isArabic ? Radius.circular(10.0) : Radius.circular(0),
// topLeft: projectViewModel.isArabic ? Radius.circular(10.0) : Radius.circular(0),
// ),
// ),
// // clipBehavior: Clip.antiAlias,
// child: Row(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// Expanded(
// child: Column(
// mainAxisAlignment: MainAxisAlignment.start,
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// Text(
// _statusDisp,
// style: TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: _color, letterSpacing: -0.4, height: 16 / 10),
// ),
// SizedBox(height: 6),
// Text(
// '${TranslationBase.of(context).requestID}: ${order.iD}',
// style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600, color: Color(0xff2E303A), letterSpacing: -0.64, height: 25 / 16),
// ),
// Row(
// crossAxisAlignment: CrossAxisAlignment.start,
// children: [
// Text(
// TranslationBase.of(context).hospital + ": ",
// style: TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xff575757), letterSpacing: -0.4, height: 16 / 10),
// ),
// Expanded(
// child: Text(
// // !projectViewModel.isArabic ? order.nearestProjectDescription.trim().toString() : order.nearestProjectDescriptionN.toString(),
// order.projectName != null ? order.projectName!.trim().toString() : "",
// style: TextStyle(fontSize: 14, fontWeight: FontWeight.w600, color: Color(0xff2B353E), letterSpacing: -0.56),
// ),
// ),
// ],
// )
// ],
// ),
// ),
// Column(
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// crossAxisAlignment: CrossAxisAlignment.end,
// children: [
// Text(
// DateUtil.getDayMonthYearDateFormatted(DateTime.tryParse(order.created!)!),
// style: TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: Color(0xff2B353E), letterSpacing: -0.4, height: 16 / 10),
// ),
// SizedBox(height: 12),
// if (order.statusId == 1 || order.statusId == 2)
// InkWell(
// onTap: () {
// showConfirmMessage(model, order);
// },
// child: Container(
// padding: EdgeInsets.symmetric(vertical: 8, horizontal: 14),
// decoration: BoxDecoration(
// color: Color(0xffD02127),
// border: Border.all(color: Colors.white, width: 1),
// borderRadius: BorderRadius.circular(10),
// ),
// child: Text(
// TranslationBase.of(context).cancel_nocaps,
// style: TextStyle(fontSize: 12, fontWeight: FontWeight.w600, color: Colors.white, letterSpacing: -0.4),
// ),
// ),
// ),
// ],
// ),
// ],
// ),
// ),
// );
// },
// separatorBuilder: (context, index) => SizedBox(height: 12),
// itemCount: model.cmcAllPresOrders.length) : getNoDataWidget(context),
// );
// }
// }

@ -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<AppState>();
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);
},
);
},
),
),
],
);
},
);
}
}

@ -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<AppState>();
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,
// ),
// ),
],
);
}
}

@ -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<HmgServicesViewModel>();
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<HmgServicesViewModel>();
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,
);
}
}

@ -9,7 +9,9 @@ import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/contact_us/contact_us_view_model.dart';
import 'package:hmg_patient_app_new/features/contact_us/models/feedback_type.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/contact_us/feedback_page.dart';
import 'package:hmg_patient_app_new/presentation/contact_us/find_us_page.dart';
import 'package:hmg_patient_app_new/presentation/contact_us/live_chat_page.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
@ -51,7 +53,17 @@ class ContactUs extends StatelessWidget {
AppAssets.checkin_location_icon,
LocaleKeys.feedback.tr(),
"Provide your feedback on our services".needTranslation,
),
).onPress(() {
contactUsViewModel.setSelectedFeedbackType(
FeedbackType(id: 5, nameEN: "Not classified", nameAR: 'غير محدد'),
);
Navigator.pop(context);
Navigator.of(context).push(
CustomPageRoute(
page: FeedbackPage(),
),
);
}),
SizedBox(height: 16.h),
checkInOptionCard(
AppAssets.checkin_location_icon,
@ -59,6 +71,7 @@ class ContactUs extends StatelessWidget {
"Live chat option with HMG".needTranslation,
).onPress(() {
locationUtils.getCurrentLocation(onSuccess: (value) {
contactUsViewModel.getLiveChatProjectsList();
Navigator.pop(context);
Navigator.of(context).push(
CustomPageRoute(

@ -0,0 +1,400 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/contact_us/contact_us_view_model.dart';
import 'package:hmg_patient_app_new/features/contact_us/models/feedback_type.dart';
import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_card.dart';
import 'package:hmg_patient_app_new/presentation/contact_us/widgets/feedback_appointment_selection.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart';
import 'package:hmg_patient_app_new/widgets/image_picker.dart';
import 'package:hmg_patient_app_new/widgets/input_widget.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'package:provider/provider.dart';
class FeedbackPage extends StatelessWidget {
FeedbackPage({super.key});
late ContactUsViewModel contactUsViewModel;
late MedicalFileViewModel medicalFileViewModel;
final TextEditingController subjectTextController = TextEditingController();
final TextEditingController messageTextController = TextEditingController();
@override
Widget build(BuildContext context) {
contactUsViewModel = Provider.of<ContactUsViewModel>(context, listen: false);
medicalFileViewModel = Provider.of<MedicalFileViewModel>(context, listen: false);
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: Consumer<ContactUsViewModel>(builder: (context, contactUsVM, child) {
return Column(
children: [
Expanded(
child: CollapsingListView(
isLeading: Navigator.canPop(context),
title: LocaleKeys.feedback.tr(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 16.h),
CustomTabBar(
activeTextColor: AppColors.primaryRedColor,
activeBackgroundColor: AppColors.primaryRedColor.withValues(alpha: .1),
tabs: [
CustomTabBarModel(null, "Send".needTranslation),
CustomTabBarModel(null, "Status".needTranslation),
],
onTabChange: (index) {
contactUsViewModel.setIsSendFeedbackTabSelected(index == 0);
},
).paddingSymmetrical(24.h, 0.h),
getSelectedTabWidget(context).paddingSymmetrical(24.h, 16.w),
],
),
),
),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
customBorder: BorderRadius.only(
topLeft: Radius.circular(24.h),
topRight: Radius.circular(24.h),
),
hasShadow: true,
),
child: CustomButton(
text: LocaleKeys.submit.tr(context: context),
onPressed: () async {
if (subjectTextController.text.isEmpty) {
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: LocaleKeys.emptySubject.tr(context: context)),
);
return;
}
if (messageTextController.text.isEmpty) {
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: LocaleKeys.emptyMessage.tr(context: context)),
);
return;
}
LoaderBottomSheet.showLoader(loadingText: "Sending Feedback...".needTranslation);
contactUsViewModel.insertCOCItem(
subject: subjectTextController.text,
message: messageTextController.text,
onSuccess: (val) {
LoaderBottomSheet.hideLoader();
subjectTextController.clear();
messageTextController.clear();
contactUsViewModel.setPatientFeedbackSelectedAppointment(null);
showCommonBottomSheetWithoutHeight(context, child: Utils.getSuccessWidget(loadingText: LocaleKeys.success.tr(context: context)), callBackFunc: () {
Navigator.pop(context);
});
},
onError: (err) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getSuccessWidget(loadingText: err),
);
});
},
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,
textColor: AppColors.whiteColor,
fontSize: 16,
fontWeight: FontWeight.w500,
borderRadius: 12,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 50.h,
icon: AppAssets.feedback,
iconColor: AppColors.whiteColor,
iconSize: 20.h,
).paddingSymmetrical(24.h, 24.h),
),
],
);
}),
);
}
Widget getSelectedTabWidget(BuildContext context) {
if (contactUsViewModel.isSendFeedbackTabSelected) {
return Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
LocaleKeys.likeToHear.tr().toText14(weight: FontWeight.w500),
SizedBox(height: 16.h),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 16.r,
hasShadow: false,
),
child: Padding(
padding: EdgeInsets.all(16.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
Utils.buildSvgWithAssets(icon: AppAssets.ask_doctor_icon, width: 24.w, height: 24.h, iconColor: AppColors.greyTextColor),
SizedBox(width: 12.w),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
LocaleKeys.feedbackType.tr().toText16(color: AppColors.textColor, weight: FontWeight.w500),
(getIt.get<AppState>().isArabic() ? contactUsViewModel.selectedFeedbackType.nameAR : contactUsViewModel.selectedFeedbackType.nameEN)
.toText14(color: AppColors.greyTextColor, weight: FontWeight.w500),
],
),
],
),
Utils.buildSvgWithAssets(icon: AppAssets.arrow_down, width: 25.h, height: 25.h),
],
).onPress(() {
showCommonBottomSheetWithoutHeight(context,
title: "Select Feedback Type".needTranslation,
child: Container(
width: double.infinity,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24),
child: ListView.builder(
itemCount: contactUsViewModel.feedbackTypeList.length,
physics: NeverScrollableScrollPhysics(),
padding: EdgeInsets.only(top: 8, bottom: 8),
shrinkWrap: true,
itemBuilder: (innerContext, index) {
return Theme(
data: Theme.of(context).copyWith(
listTileTheme: ListTileThemeData(horizontalTitleGap: 4),
),
child: RadioListTile<FeedbackType>(
title: Text(
getIt.get<AppState>().isArabic() ? contactUsViewModel.feedbackTypeList[index].nameAR : contactUsViewModel.feedbackTypeList[index].nameEN,
style: TextStyle(
fontSize: 16.h,
fontWeight: FontWeight.w500,
),
),
value: contactUsViewModel.feedbackTypeList[index],
fillColor: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.selected)) {
return AppColors.primaryRedColor;
}
return Color(0xffEEEEEE);
}),
contentPadding: EdgeInsets.only(left: 12.h, right: 12.h),
groupValue: contactUsViewModel.selectedFeedbackType,
onChanged: (FeedbackType? newValue) async {
Navigator.pop(context);
contactUsViewModel.setSelectedFeedbackType(newValue!);
if (contactUsViewModel.selectedFeedbackType.id == 1) {
LoaderBottomSheet.showLoader(loadingText: "Loading appointments list...".needTranslation);
await medicalFileViewModel.getPatientMedicalReportAppointmentsList(onSuccess: (val) async {
LoaderBottomSheet.hideLoader();
bool? value = await Navigator.of(context).push(
CustomPageRoute(
page: FeedbackAppointmentSelection(),
fullScreenDialog: true,
direction: AxisDirection.down,
),
);
if (value != null) {
// showConfirmRequestMedicalReportBottomSheet();
}
}, onError: (err) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: "You do not have any appointments to submit a feedback.".needTranslation),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
} else {
contactUsViewModel.setPatientFeedbackSelectedAppointment(null);
}
},
),
);
},
),
),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true);
}),
]),
),
),
if (contactUsViewModel.patientFeedbackSelectedAppointment != null) ...[
SizedBox(height: 16.h),
"Selected Appointment:".needTranslation.toText16(isBold: true),
SizedBox(height: 8.h),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.r,
hasShadow: false,
),
padding: EdgeInsets.all(16.h),
child: Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Image.network(
contactUsViewModel.patientFeedbackSelectedAppointment!.doctorImageURL!,
width: 63.h,
height: 63.h,
fit: BoxFit.cover,
).circle(100).toShimmer2(isShow: false),
SizedBox(width: 16.h),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
(contactUsViewModel.patientFeedbackSelectedAppointment!.doctorNameObj!).toText16(isBold: true, maxlines: 1).toShimmer2(isShow: false),
SizedBox(height: 8.h),
Wrap(
direction: Axis.horizontal,
spacing: 3.h,
runSpacing: 4.h,
children: [
AppCustomChipWidget(labelText: contactUsViewModel.patientFeedbackSelectedAppointment!.clinicName!).toShimmer2(isShow: false),
AppCustomChipWidget(labelText: contactUsViewModel.patientFeedbackSelectedAppointment!.projectName!).toShimmer2(isShow: false),
AppCustomChipWidget(
icon: AppAssets.appointment_calendar_icon,
labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(contactUsViewModel.patientFeedbackSelectedAppointment!.appointmentDate), false),
).toShimmer2(isShow: false),
],
),
],
),
),
],
),
),
],
SizedBox(height: 16.h),
TextInputWidget(
labelText: "Subject".needTranslation,
hintText: "Enter subject here".needTranslation,
controller: subjectTextController,
isEnable: true,
prefix: null,
autoFocus: false,
isBorderAllowed: false,
keyboardType: TextInputType.text,
padding: EdgeInsets.symmetric(
vertical: ResponsiveExtension(10).h,
horizontal: ResponsiveExtension(15).h,
),
),
SizedBox(height: 16.h),
TextInputWidget(
labelText: "Message".needTranslation,
hintText: "Enter message here".needTranslation,
controller: messageTextController,
isEnable: true,
prefix: null,
autoFocus: false,
isBorderAllowed: false,
isMultiline: true,
keyboardType: TextInputType.text,
padding: EdgeInsets.symmetric(
vertical: ResponsiveExtension(10).h,
horizontal: ResponsiveExtension(15).h,
),
),
SizedBox(height: 16.h),
CustomButton(
text: LocaleKeys.selectAttachment.tr(context: context),
onPressed: () async {
ImageOptions.showImageOptionsNew(
context,
true,
(String image, file) {
print(image);
print(file);
Navigator.pop(context);
contactUsViewModel.addFeedbackAttachment(image);
},
);
},
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: 14.f,
fontWeight: FontWeight.w500,
borderRadius: 10.r,
padding: EdgeInsets.symmetric(horizontal: 10.w),
height: isTablet || isFoldable ? 46.h : 40.h,
icon: AppAssets.file_icon,
iconColor: AppColors.primaryRedColor,
iconSize: 16.h,
),
SizedBox(height: 16.h),
contactUsViewModel.feedbackAttachmentList.isNotEmpty
? Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 12.r,
hasShadow: false,
),
child: ListView.builder(
padding: EdgeInsets.all(16.h),
shrinkWrap: true,
itemCount: contactUsViewModel.feedbackAttachmentList.length,
itemBuilder: (BuildContext context, int index) {
return Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Icon(
Icons.attach_file,
color: Color(0xff2B353E),
),
SizedBox(width: 8.w),
"Image ${index + 1}".toText14().paddingOnly(bottom: 8.h),
],
),
Utils.buildSvgWithAssets(icon: AppAssets.cancel_circle_icon).onPress(() {
contactUsViewModel.removeFeedbackAttachment(contactUsViewModel.feedbackAttachmentList[index]);
}),
],
);
},
),
)
: SizedBox.shrink(),
],
);
} else {
return Container();
}
}
}

@ -1,27 +1,176 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/contact_us/contact_us_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart';
class LiveChatPage extends StatelessWidget {
const LiveChatPage({super.key});
LiveChatPage({super.key});
String chatURL = "";
late AppState appState;
@override
Widget build(BuildContext context) {
appState = getIt.get<AppState>();
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: Column(
children: [
Expanded(
child: CollapsingListView(
title: LocaleKeys.liveChat.tr(),
child: SingleChildScrollView(),
body: Consumer<ContactUsViewModel>(builder: (context, contactUsVM, child) {
return Column(
children: [
Expanded(
child: CollapsingListView(
title: LocaleKeys.liveChat.tr(),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 16.h),
ListView.separated(
padding: EdgeInsets.only(top: 16.h),
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: contactUsVM.isLiveChatProjectsListLoading ? 5 : contactUsVM.liveChatProjectsList.length,
itemBuilder: (context, index) {
return contactUsVM.isLiveChatProjectsListLoading
? Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true),
child: Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.h,
hasShadow: true,
),
child: Padding(
padding: EdgeInsets.all(14.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Image.network(
"https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png",
width: 63.h,
height: 63.h,
fit: BoxFit.cover,
).circle(100).toShimmer2(isShow: true),
SizedBox(width: 16.h),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"Dr John Smith".toText16(isBold: true).toShimmer2(isShow: true),
SizedBox(height: 8.h),
Wrap(
direction: Axis.horizontal,
spacing: 3.h,
runSpacing: 4.h,
children: [
AppCustomChipWidget(labelText: "").toShimmer2(isShow: true, width: 16.h),
AppCustomChipWidget(labelText: "").toShimmer2(isShow: true, width: 16.h),
],
),
],
),
),
],
),
],
),
),
),
).paddingSymmetrical(24.h, 0.h)
: AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 500),
child: SlideAnimation(
verticalOffset: 100.0,
child: FadeInAnimation(
child: AnimatedContainer(
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true),
child: DecoratedBox(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: contactUsVM.selectedLiveChatProjectIndex == index ? AppColors.primaryRedColor : AppColors.whiteColor,
borderRadius: 16.r,
hasShadow: false,
),
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
("${appState.isArabic() ? contactUsVM.liveChatProjectsList[index].projectNameN! : contactUsVM.liveChatProjectsList[index].projectName!}\n${contactUsVM.liveChatProjectsList[index].distanceInKilometers!} KM")
.needTranslation
.toText14(isBold: true, color: contactUsVM.selectedLiveChatProjectIndex == index ? AppColors.whiteColor : AppColors.textColor),
Transform.flip(
flipX: getIt.get<AppState>().isArabic(),
child: Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon_small,
iconColor: contactUsVM.selectedLiveChatProjectIndex == index ? AppColors.whiteColor : AppColors.textColor,
width: 18.h,
height: 13.h,
fit: BoxFit.contain,
),
),
],
).paddingSymmetrical(16.h, 16.h),
).onPress(() {
contactUsVM.setSelectedLiveChatProjectIndex(index);
chatURL =
"https://chat.hmg.com/Index.aspx?Name=${appState.getAuthenticatedUser()!.firstName}&PatientID=${appState.getAuthenticatedUser()!.patientId}&MobileNo=${appState.getAuthenticatedUser()!.mobileNumber}&Language=${appState.isArabic() ? 'ar' : 'en'}&WorkGroup=${contactUsVM.liveChatProjectsList[index].value}";
debugPrint("Chat URL: $chatURL");
}),
).paddingSymmetrical(24.h, 0.h),
),
),
);
},
separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h),
),
SizedBox(height: 24.h),
],
),
),
),
Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 24.h,
hasShadow: true,
),
child: CustomButton(
text: LocaleKeys.liveChat.tr(context: context),
onPressed: () async {
Uri uri = Uri.parse(chatURL);
launchUrl(uri, mode: LaunchMode.platformDefault, webOnlyWindowName: "");
},
backgroundColor: contactUsVM.selectedLiveChatProjectIndex == -1 ? AppColors.greyColor : AppColors.primaryRedColor,
borderColor: contactUsVM.selectedLiveChatProjectIndex == -1 ? AppColors.greyColor : AppColors.primaryRedColor,
textColor: AppColors.whiteColor,
fontSize: 16,
fontWeight: FontWeight.w500,
borderRadius: 12,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 50.h,
).paddingSymmetrical(24.h, 24.h),
),
),
Container()
],
),
],
);
}),
);
}
}

@ -0,0 +1,70 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/contact_us/contact_us_view_model.dart';
import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_card.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:provider/provider.dart';
class FeedbackAppointmentSelection extends StatelessWidget {
FeedbackAppointmentSelection({super.key});
late MedicalFileViewModel medicalFileViewModel;
late ContactUsViewModel contactUsViewModel;
@override
Widget build(BuildContext context) {
medicalFileViewModel = Provider.of<MedicalFileViewModel>(context, listen: false);
contactUsViewModel = Provider.of<ContactUsViewModel>(context, listen: false);
return CollapsingListView(
title: LocaleKeys.feedback.tr(),
isClose: true,
child: Column(
children: [
ListView.separated(
padding: EdgeInsets.only(top: 24.h),
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: medicalFileViewModel.patientMedicalReportAppointmentHistoryList.length,
itemBuilder: (context, index) {
return AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 500),
child: SlideAnimation(
verticalOffset: 100.0,
child: FadeInAnimation(
child: AnimatedContainer(
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true),
child: AppointmentCard(
patientAppointmentHistoryResponseModel: medicalFileViewModel.patientMedicalReportAppointmentHistoryList[index],
myAppointmentsViewModel: Provider.of<MyAppointmentsViewModel>(context, listen: false),
bookAppointmentsViewModel: Provider.of<BookAppointmentsViewModel>(context, listen: false),
medicalFileViewModel: medicalFileViewModel,
contactUsViewModel: contactUsViewModel,
isLoading: false,
isFromHomePage: false,
isFromMedicalReport: true,
isForFeedback: true,
),
).paddingSymmetrical(24.h, 0.h),
),
),
);
},
separatorBuilder: (BuildContext cxt, int index) => SizedBox(height: 16.h),
),
SizedBox(height: 24.h),
],
),
);
}
}

@ -0,0 +1,633 @@
import 'dart:io';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_export.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/emergency_services/emergency_services_view_model.dart';
import 'package:hmg_patient_app_new/features/emergency_services/models/AmbulanceCallingPlace.dart';
import 'package:hmg_patient_app_new/features/location/GeocodeResponse.dart';
import 'package:hmg_patient_app_new/features/location/PlaceDetails.dart';
import 'package:hmg_patient_app_new/features/location/PlacePrediction.dart';
import 'package:hmg_patient_app_new/features/location/location_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_doctor_card.dart';
import 'package:hmg_patient_app_new/presentation/emergency_services/call_ambulance/widgets/AddressItem.dart';
import 'package:hmg_patient_app_new/presentation/emergency_services/call_ambulance/widgets/HospitalBottomSheetBody.dart';
import 'package:hmg_patient_app_new/presentation/emergency_services/call_ambulance/widgets/appointment_bottom_sheet.dart' show AppointmentBottomSheet;
import 'package:hmg_patient_app_new/presentation/emergency_services/widgets/location_input_bottom_sheet.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/CustomSwitch.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/expandable_bottom_sheet/ExpandableBottomSheet.dart';
import 'package:hmg_patient_app_new/widgets/expandable_bottom_sheet/model/BottomSheetType.dart';
import 'package:hmg_patient_app_new/widgets/input_widget.dart';
import 'package:hmg_patient_app_new/widgets/map/HMSMap.dart';
import 'package:hmg_patient_app_new/widgets/map/map.dart';
import 'package:provider/provider.dart';
import '../../../widgets/common_bottom_sheet.dart';
class RrtMapScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: Visibility(
visible: context.watch<EmergencyServicesViewModel>().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<EmergencyServicesViewModel>()
.moveToCurrentLocation();
}),
),
),
),
bottomSheet: ExpandableBottomSheet(
bottomSheetType:
context.watch<EmergencyServicesViewModel>().bottomSheetType,
children: {
BottomSheetType.EXPANDED: ExpanedBottomSheet(context),
BottomSheetType.FIXED: FixedBottomSheet(context),
},
),
body: Stack(
children: [
if (context.read<EmergencyServicesViewModel>().isGMSAvailable)
GMSMap(
currentLocation:
context.read<EmergencyServicesViewModel>().getGMSLocation(),
onCameraMoved: (value) => context
.read<LocationViewModel>()
.handleGMSMapCameraMoved(value),
onCameraIdle:
context.read<LocationViewModel>().handleOnCameraIdle,
myLocationEnabled: true,
inputController:
context.read<EmergencyServicesViewModel>().gmsController,
showCenterMarker: true,
)
else
HMSMap(
currentLocation:
context.read<EmergencyServicesViewModel>().getHMSLocation(),
onCameraMoved: (value) => context
.read<LocationViewModel>()
.handleHMSMapCameraMoved(value),
onCameraIdle:
context.read<LocationViewModel>().handleOnCameraIdle,
myLocationEnabled: false,
inputController:
context.read<EmergencyServicesViewModel>().hmsController,
showCenterMarker: true,
),
Align(
alignment: AlignmentDirectional.topStart,
child: Utils.buildSvgWithAssets(
icon: AppAssets.closeBottomNav, width: 32.h, height: 32.h)
.onPress(() {
context
.read<EmergencyServicesViewModel>()
.flushPickupInformation();
Navigator.pop(context);
}),
).paddingOnly(top: 51.h, left: 24.h),
],
),
);
}
Widget FixedBottomSheet(BuildContext context) {
return GestureDetector(
onVerticalDragUpdate: (details){
},
child: Column(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
spacing: 24.h,
children: [
inputFields(context).paddingSymmetrical(16.h, 0.h),
SizedBox(
child: DecoratedBox(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.scaffoldBgColor,
customBorder: BorderRadius.only(
topLeft: Radius.circular(24.h),
topRight: Radius.circular(24.h),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 24.h,
children: [
Column(
spacing: 4.h,
children: [
"Select Location".needTranslation.toText21(
weight: FontWeight.w600,
color: AppColors.textColor,
),
"Please select the location".needTranslation.toText12(
fontWeight: FontWeight.w500,
color: AppColors.greyTextColor,
)
],
),
CustomButton(
text: "Submit Request".needTranslation,
onPressed: () {
LocationViewModel locationViewModel = context.read<LocationViewModel>();
GeocodeResponse? response = locationViewModel.geocodeResponse;
PlaceDetails? placeDetails = locationViewModel.placeDetails;
PlacePrediction? placePrediction = locationViewModel.selectedPrediction;
context.read<EmergencyServicesViewModel>().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<EmergencyServicesViewModel>().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<EmergencyServicesViewModel>()
.pickupFromInsideTheLocation,
onChanged: (value){
context
.read<EmergencyServicesViewModel>()
.updateThePickupPlaceFromLocation(value);
},
)
],
),
Row(
children: [
hospitalAndPickUpItemContent(
title: '',
subTitle: "Have any appointment".needTranslation,
leadingIcon: AppAssets.appointment_checkin_icon,
),
CustomSwitch(
value: context
.watch<EmergencyServicesViewModel>()
.haveAnAppointment,
onChanged: (value) async {
// if (value) {
// openAppointmentList(context);
// }
await context.read<EmergencyServicesViewModel>()
.updateAppointment(value);
if (context.read<EmergencyServicesViewModel>().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<EmergencyServicesViewModel>()
.getTotalPrice() ??
0))
.toText24(
fontWeight: FontWeight.w600,
color: AppColors.textColor,
letterSpacing: -2),
AppColors.blackColor,
17.h)
// Utils.getPaymentAmountWithSymbol2(context.read<EmergencyServicesViewModel>().selectedTransportOption?.priceTotal??"0", letterSpacing: -2)
],
),
CustomButton(
text: "Submit Request".needTranslation,
onPressed: () {
LocationViewModel locationViewModel = context.read<LocationViewModel>();
GeocodeResponse? response = locationViewModel.geocodeResponse;
PlaceDetails? placeDetails = locationViewModel.placeDetails;
PlacePrediction? placePrediction = locationViewModel.selectedPrediction;
context.read<EmergencyServicesViewModel>().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<EmergencyServicesViewModel>(
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<EmergencyServicesViewModel>().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<LocationViewModel>(builder: (_, vm, __) {
print(
"the data is ${vm.geocodeResponse?.results.first.formattedAddress ?? vm.selectedPrediction?.description}");
return SizedBox(
width: MediaQuery.sizeOf(context).width,
child: TextInputWidget(
labelText: "Enter Pickup Location Manually".needTranslation,
hintText: "Enter Pickup Location".needTranslation,
controller: TextEditingController(
text: vm.geocodeResponse?.results.first.formattedAddress ??
vm.selectedPrediction?.description,
),
leadingIcon: AppAssets.location_pickup,
isAllowLeadingIcon: true,
isEnable: false,
prefix: null,
autoFocus: false,
isBorderAllowed: false,
keyboardType: TextInputType.text,
padding: EdgeInsets.symmetric(
vertical: ResponsiveExtension(10).h,
horizontal: ResponsiveExtension(15).h,
),
).onPress(() {
openLocationInputBottomSheet(context);
}),
);
});
}
///decide which field to show first based on the selected calling place
Widget inputFields(BuildContext context) {
return textPlaceInput(context);
}
openLocationInputBottomSheet(BuildContext context) {
context.read<LocationViewModel>().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<EmergencyServicesViewModel>()
.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<EmergencyServicesViewModel>().appointments!,
onAppointmentSelection: (appointment) {
Navigator.pop(context);
context.read<EmergencyServicesViewModel>().setSelectedAppointment(appointment);
}),
),
isFullScreen: false,
isCloseButtonVisible: true,
hasBottomPadding: false,
backgroundColor: AppColors.bottomSheetBgColor,
callBackFunc: () {
context.read<EmergencyServicesViewModel>().checkHasAppointment();
},
);
// );
}
}

@ -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<EmergencyServicesViewModel>(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<EmergencyServicesViewModel>().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<EmergencyServicesViewModel>().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<EmergencyServicesViewModel>().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<EmergencyServicesViewModel, (List<RRTProceduresResponseModel>?, 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<int>(
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<EmergencyServicesViewModel>().setSelectedRRTProcedure(procedure);
},
);
// return Row(
// children: [
// Radio<String?>(
// // 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<EmergencyServicesViewModel>().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<Color>((Set<MaterialState> 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);
// },
// );
}
}

@ -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)))]));
}
}

@ -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<EmergencyServicesViewModel>().bottomSheetType ==
BottomSheetType.FIXED,
visible: context.watch<EmergencyServicesViewModel>().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<EmergencyServicesViewModel>()
.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<EmergencyServicesViewModel>().moveToCurrentLocation();
}),
),
),
@ -62,8 +49,7 @@ class CallAmbulancePage extends StatelessWidget {
mainAxisSize: MainAxisSize.min,
children: [
ExpandableBottomSheet(
bottomSheetType:
context.watch<EmergencyServicesViewModel>().bottomSheetType,
bottomSheetType: context.watch<EmergencyServicesViewModel>().bottomSheetType,
children: {
BottomSheetType.EXPANDED: ExpanedBottomSheet(context),
BottomSheetType.FIXED: FixedBottomSheet(context),
@ -73,42 +59,28 @@ class CallAmbulancePage extends StatelessWidget {
),
body: Stack(
children: [
if (context.read<EmergencyServicesViewModel>().isGMSAvailable )
if (context.read<EmergencyServicesViewModel>().isGMSAvailable)
GMSMap(
currentLocation:
context.read<EmergencyServicesViewModel>().getGMSLocation(),
onCameraMoved: (value) => context
.read<LocationViewModel>()
.handleGMSMapCameraMoved(value),
onCameraIdle:
context.read<LocationViewModel>().handleOnCameraIdle,
currentLocation: context.read<EmergencyServicesViewModel>().getGMSLocation(),
onCameraMoved: (value) => context.read<LocationViewModel>().handleGMSMapCameraMoved(value),
onCameraIdle: context.read<LocationViewModel>().handleOnCameraIdle,
myLocationEnabled: true,
inputController:
context.read<EmergencyServicesViewModel>().gmsController,
inputController: context.read<EmergencyServicesViewModel>().gmsController,
showCenterMarker: true,
)
else
HMSMap(
currentLocation:
context.read<EmergencyServicesViewModel>().getHMSLocation(),
onCameraMoved: (value) => context
.read<LocationViewModel>()
.handleHMSMapCameraMoved(value),
onCameraIdle:
context.read<LocationViewModel>().handleOnCameraIdle,
currentLocation: context.read<EmergencyServicesViewModel>().getHMSLocation(),
onCameraMoved: (value) => context.read<LocationViewModel>().handleHMSMapCameraMoved(value),
onCameraIdle: context.read<LocationViewModel>().handleOnCameraIdle,
myLocationEnabled: false,
inputController:
context.read<EmergencyServicesViewModel>().hmsController,
inputController: context.read<EmergencyServicesViewModel>().hmsController,
showCenterMarker: true,
),
Align(
alignment: AlignmentDirectional.topStart,
child: Utils.buildSvgWithAssets(
icon: AppAssets.closeBottomNav, width: 32.h, height: 32.h)
.onPress(() {
context
.read<EmergencyServicesViewModel>()
.flushPickupInformation();
child: Utils.buildSvgWithAssets(icon: AppAssets.closeBottomNav, width: 32.h, height: 32.h).onPress(() {
context.read<EmergencyServicesViewModel>().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<EmergencyServicesViewModel>().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<EmergencyServicesViewModel>()
.updateBottomSheetState(BottomSheetType.EXPANDED);
context.read<EmergencyServicesViewModel>().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<EmergencyServicesViewModel>().updateBottomSheetState(BottomSheetType.FIXED);
}
},
onVerticalDragUpdate: (details) {
if (details.delta.dy > 0) {
context.read<EmergencyServicesViewModel>().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<EmergencyServicesViewModel>().appointment != null)
AppointmentDoctorCard(
renderWidgetForERDisplay: true,
patientAppointmentHistoryResponseModel: context.read<EmergencyServicesViewModel>().appointment!,
onAskDoctorTap: () {},
onCancelTap: () async {},
onRescheduleTap: () async {},
).onPress((){
openAppointmentList(context);
})
if (context.read<EmergencyServicesViewModel>().appointment != null)
AppointmentDoctorCard(
renderWidgetForERDisplay: true,
patientAppointmentHistoryResponseModel: context.read<EmergencyServicesViewModel>().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<EmergencyServicesViewModel>()
.pickupFromInsideTheLocation,
onChanged: (value){
context
.read<EmergencyServicesViewModel>()
.updateThePickupPlaceFromLocation(value);
value: context.watch<EmergencyServicesViewModel>().pickupFromInsideTheLocation,
onChanged: (value) {
context.read<EmergencyServicesViewModel>().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<EmergencyServicesViewModel>()
.haveAnAppointment,
value: context.watch<EmergencyServicesViewModel>().haveAnAppointment,
onChanged: (value) async {
// if (value) {
// openAppointmentList(context);
// }
await context.read<EmergencyServicesViewModel>()
.updateAppointment(value);
await context.read<EmergencyServicesViewModel>().updateAppointment(value);
if (context.read<EmergencyServicesViewModel>().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<EmergencyServicesViewModel>()
.getTotalPrice() ??
0))
.toText24(
fontWeight: FontWeight.w600,
color: AppColors.textColor,
letterSpacing: -2),
(Utils.formatNumberToInternationalFormat(context.read<EmergencyServicesViewModel>().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<EmergencyServicesViewModel>().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<EmergencyServicesViewModel>(
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<LocationViewModel>(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<EmergencyServicesViewModel>().callingPlace ==
AmbulanceCallingPlace.FROM_HOSPITAL
return context.read<EmergencyServicesViewModel>().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<EmergencyServicesViewModel>()
.getSelectedHospitalName() ??
"Select Hospital".needTranslation,
subTitle: context.read<EmergencyServicesViewModel>().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");

@ -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<EmergencyServicesViewModel>().cancelOrder(order, shouldPop: true);
if(isRRTOrder){
context.read<EmergencyServicesViewModel>().cancelRRTOrder(rrtOrder?.iD??-1, shouldPop: true);
return;
}else {
context.read<EmergencyServicesViewModel>().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),
// //
// // )
// ],
// );
// }
}

@ -29,7 +29,7 @@ class PickupLocation extends StatelessWidget {
),
"Select Direction"
.needTranslation
.toText14(color: AppColors.textColor, weight: FontWeight.w600),
.toText16(color: AppColors.textColor, weight: FontWeight.w600),
SizedBox(
height: 12.h,
),
@ -39,56 +39,54 @@ class PickupLocation extends StatelessWidget {
return Column(
spacing: 12.h,
children: [
// Use a plain Row with Radios and provide required groupValue and onChanged
Row(
mainAxisAlignment: MainAxisAlignment.start,
spacing: 24.h,
children: [
Row(
children: [
Radio<AmbulanceCallingPlace>(
value: AmbulanceCallingPlace.TO_HOSPITAL,
groupValue: value,
onChanged: (AmbulanceCallingPlace? v) {
if (v != null) {
context.read<EmergencyServicesViewModel>().updateCallingPlace(v);
}
},
activeColor: AppColors.primaryRedColor,
fillColor: MaterialStateProperty.all(AppColors.primaryRedColor),
),
"To Hospital"
.needTranslation
.toText12(color: AppColors.textColor)
],
).onPress(() {
context
.read<EmergencyServicesViewModel>()
.updateCallingPlace(AmbulanceCallingPlace.TO_HOSPITAL);
}),
Row(
children: [
Radio<AmbulanceCallingPlace>(
value: AmbulanceCallingPlace.FROM_HOSPITAL,
groupValue: value,
onChanged: (AmbulanceCallingPlace? v) {
if (v != null) {
context.read<EmergencyServicesViewModel>().updateCallingPlace(v);
}
},
activeColor: AppColors.primaryRedColor,
fillColor: MaterialStateProperty.all(AppColors.primaryRedColor),
),
"From Hospital"
.needTranslation
.toText12(color: AppColors.textColor)
],
).onPress(() {
context
.read<EmergencyServicesViewModel>()
.updateCallingPlace(AmbulanceCallingPlace.FROM_HOSPITAL);
}),
],
RadioGroup<AmbulanceCallingPlace>(
groupValue: value,
onChanged: (value) {
context
.read<EmergencyServicesViewModel>()
.updateCallingPlace(value);
},
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
spacing: 24.h,
children: [
Row(
children: [
Radio(
value: AmbulanceCallingPlace.TO_HOSPITAL,
groupValue: value,
activeColor: AppColors.primaryRedColor,
fillColor: MaterialStateProperty.all(AppColors.primaryRedColor),
),
"To Hospital"
.needTranslation
.toText14(color: AppColors.textColor, weight: FontWeight.w500)
],
).onPress((){
context
.read<EmergencyServicesViewModel>()
.updateCallingPlace(AmbulanceCallingPlace.TO_HOSPITAL);
}),
Row(
children: [
Radio(
value: AmbulanceCallingPlace.FROM_HOSPITAL,
activeColor: AppColors.primaryRedColor,
fillColor: MaterialStateProperty.all(AppColors.primaryRedColor),
),
"From Hospital"
.needTranslation
.toText14(color: AppColors.textColor, weight: FontWeight.w500)
],
).onPress((){
context
.read<EmergencyServicesViewModel>()
.updateCallingPlace(AmbulanceCallingPlace.FROM_HOSPITAL);
}),
],
),
),
Visibility(
visible: value == AmbulanceCallingPlace.TO_HOSPITAL,
@ -98,59 +96,58 @@ class PickupLocation extends StatelessWidget {
builder: (context, directionValue, _) {
return Column(
spacing: 12.h,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"Select Way"
.needTranslation
.toText14(color: AppColors.textColor, weight: FontWeight.w600),
// Use a Row and assign groupValue/onChanged to each Radio
Row(
mainAxisAlignment: MainAxisAlignment.start,
spacing: 24.h,
children: [
Row(
children: [
Radio<AmbulanceDirection>(
value: AmbulanceDirection.ONE_WAY,
groupValue: directionValue,
onChanged: (AmbulanceDirection? v) {
if (v != null) {
context.read<EmergencyServicesViewModel>().updateDirection(v);
}
},
activeColor: AppColors.primaryRedColor,
fillColor: MaterialStateProperty.all(AppColors.primaryRedColor),
),
"One Way"
.needTranslation
.toText12(color: AppColors.textColor)
],
).onPress(() {
context
.read<EmergencyServicesViewModel>()
.updateDirection(AmbulanceDirection.ONE_WAY);
}),
Row(
children: [
Radio<AmbulanceDirection>(
value: AmbulanceDirection.TWO_WAY,
groupValue: directionValue,
onChanged: (AmbulanceDirection? v) {
if (v != null) {
context.read<EmergencyServicesViewModel>().updateDirection(v);
}
},
fillColor: MaterialStateProperty.all(AppColors.primaryRedColor),
),
"Two Way"
.needTranslation
.toText12(color: AppColors.textColor)
],
).onPress(() {
context
.read<EmergencyServicesViewModel>()
.updateDirection(AmbulanceDirection.TWO_WAY);
}),
],
.toText16(color: AppColors.textColor, weight: FontWeight.w600),
RadioGroup<AmbulanceDirection>(
groupValue: directionValue,
onChanged: (value) {
context
.read<EmergencyServicesViewModel>()
.updateDirection(value);
},
child: Row(
mainAxisAlignment: MainAxisAlignment.start,
spacing: 24.h,
children: [
Row(
children: [
Radio(
value: AmbulanceDirection.ONE_WAY,
activeColor: AppColors.primaryRedColor,
fillColor: MaterialStateProperty.all(AppColors.primaryRedColor),
),
"One Way"
.needTranslation
.toText12(color: AppColors.textColor, fontWeight: FontWeight.w500)
],
).onPress((){
context
.read<EmergencyServicesViewModel>()
.updateDirection(AmbulanceDirection.ONE_WAY);
}),
Row(
children: [
Radio(
value: AmbulanceDirection.TWO_WAY,
// activeColor: AppColors.primaryRedColor,
fillColor: MaterialStateProperty.all(AppColors.primaryRedColor),
),
"Two Way"
.needTranslation
.toText14(color: AppColors.textColor, weight: FontWeight.w500)
],
).onPress((){
context
.read<EmergencyServicesViewModel>()
.updateDirection(AmbulanceDirection.TWO_WAY);
}),
],
),
),
],
);

@ -1,13 +1,12 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/location_util.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/emergency_services/emergency_services_view_model.dart';
import 'package:hmg_patient_app_new/features/emergency_services/models/OrderDisplay.dart';
import 'package:hmg_patient_app_new/features/location/location_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/emergency_services/RRT/rrt_request_type_select.dart';
@ -27,17 +26,18 @@ class EmergencyServicesPage extends StatelessWidget {
EmergencyServicesPage({super.key});
late EmergencyServicesViewModel emergencyServicesViewModel;
LocationUtils? locationUtils;
_handleConfirmationBottomSheet() {}
@override
Widget build(BuildContext context) {
emergencyServicesViewModel = Provider.of<EmergencyServicesViewModel>(context, listen: false);
locationUtils = getIt.get<LocationUtils>();
locationUtils!.isShowConfirmDialog = true;
return CollapsingListView(
title: LocaleKeys.emergencyServices.tr(),
requests: () {
Navigator.of(context).push(CustomPageRoute(page: ErHistoryListing(), direction: AxisDirection.up));
emergencyServicesViewModel.changeOrderDisplayItems(OrderDislpay.ALL);
Navigator.of(context).push(CustomPageRoute(page: ErHistoryListing(), direction: AxisDirection.up));
},
child: Padding(
padding: EdgeInsets.all(24.h),
@ -109,7 +109,6 @@ class EmergencyServicesPage extends StatelessWidget {
CustomButton(
text: LocaleKeys.confirm.tr(context: context),
onPressed: () async {
//
Navigator.of(context).pop();
await emergencyServicesViewModel.getTransportationOption();
openTranportationSelectionBottomSheet(context);
@ -201,23 +200,7 @@ class EmergencyServicesPage extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
"".toText14(),
Utils.buildSvgWithAssets(
icon: AppAssets.cancel_circle_icon,
iconColor: AppColors.whiteColor,
width: 24.h,
height: 24.h,
fit: BoxFit.contain,
).onPress(() {
Navigator.of(context).pop();
}),
],
),
Lottie.asset(AppAnimations.ambulance_alert,
repeat: false, reverse: false, frameRate: FrameRate(60), width: 120.h, height: 120.h, fit: BoxFit.contain),
Lottie.asset(AppAnimations.ambulance_alert, repeat: false, reverse: false, frameRate: FrameRate(60), width: 120.h, height: 120.h, fit: BoxFit.contain),
SizedBox(height: 8.h),
LocaleKeys.confirm.tr().toText28(color: AppColors.whiteColor, isBold: true),
SizedBox(height: 8.h),
@ -231,10 +214,23 @@ class EmergencyServicesPage extends StatelessWidget {
Navigator.of(context).pop();
LoaderBottomSheet.showLoader();
emergencyServicesViewModel.clearRRTData();
await emergencyServicesViewModel.getRRTProcedures(onSuccess: (val) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
title: "Rapid Response Team (RRT)".needTranslation,
padding: EdgeInsets.only(top: 24.h),
titleWidget: Transform.flip(
flipX: emergencyServicesViewModel.isArabic,
child: Utils.buildSvgWithAssets(
icon: AppAssets.arrow_back,
iconColor: Color(0xff2B353E),
fit: BoxFit.contain,
),
).onPress(() {
Navigator.pop(context);
}),
// title: "Rapid Response Team (RRT)".needTranslation,
context,
child: RrtRequestTypeSelect(),
isFullScreen: false,
@ -294,10 +290,7 @@ class EmergencyServicesPage extends StatelessWidget {
showCommonBottomSheetWithoutHeight(
context,
child: Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.primaryRedColor,
borderRadius: 24.h,
),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.primaryRedColor, borderRadius: 24.h),
child: Padding(
padding: EdgeInsets.all(24.h),
child: Column(
@ -365,8 +358,8 @@ class EmergencyServicesPage extends StatelessWidget {
onCloseClicked: () {
context.read<EmergencyServicesViewModel>().flushPickupInformation();
},
titleWidget: Transform.flip(
flipX: emergencyServicesViewModel.isArabic ? true : false,
titleWidget: Transform.flip(
flipX: emergencyServicesViewModel.isArabic,
child: Utils.buildSvgWithAssets(
icon: AppAssets.arrow_back,
iconColor: Color(0xff2B353E),

@ -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<EmergencyServicesViewModel>()
.orders
?.isNotEmpty == true,
child: ListView.builder(
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: context
.read<EmergencyServicesViewModel>()
.orders
?.length ?? 0,
itemBuilder: (_, index) =>
Consumer<EmergencyServicesViewModel>( builder: (_, vm, __)=>
AmbulanceHistoryItem(order: vm.orders![index]),
)
children: [
Selector<EmergencyServicesViewModel,(List, bool)>(
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<EmergencyServicesViewModel>()
.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<EmergencyServicesViewModel, OrderDislpay>(
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<EmergencyServicesViewModel>().changeOrderDisplayItems(OrderDislpay.ALL);
}).toShimmer2(isShow: isLoading),
if(context
.read<EmergencyServicesViewModel>()
.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<EmergencyServicesViewModel>().changeOrderDisplayItems(OrderDislpay.AMBULANCE);
}),
if(context
.read<EmergencyServicesViewModel>()
.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<EmergencyServicesViewModel>().changeOrderDisplayItems(OrderDislpay.RRT);
}),
],
);
});
}
}

@ -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:

@ -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,
);
}

@ -0,0 +1,78 @@
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_export.dart';
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/emergency_services/emergency_services_view_model.dart';
import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/AmbulanceRequestOrdersModel.dart';
import 'package:hmg_patient_app_new/features/emergency_services/models/resp_model/RRTServiceData.dart';
import 'package:hmg_patient_app_new/presentation/emergency_services/history/widget/RequestStatus.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
import 'package:provider/provider.dart';
import '../../../../core/utils/utils.dart';
class RRTItem extends StatelessWidget {
final GetCMCAllOrdersResponseModel order;
const RRTItem({super.key, required this.order});
@override
Widget build(BuildContext context) {
return DecoratedBox(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: Colors.white,
hasShadow: true,
customBorder: BorderRadius.all(
Radius.circular(20.h),
),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
spacing: 8.h,
children: [
RequestStatus(status: order.statusId ?? 0),
"Req ID: ${order.iD}".toText16(color: AppColors.textColor, weight: FontWeight.w600),
Row(
spacing: 4.w,
children: [
chip( Utils.getDayMonthYearDateFormatted(DateUtil.convertStringToDate(order.time)), AppAssets.calendar, AppColors.blackBgColor),
chip("Rapid Response Team(RRT)".needTranslation, AppAssets.ic_rrt_vehicle, AppColors.blackBgColor),
],
),
SizedBox(height: 4.h),
if (order.statusId == 1 || order.statusId == 2)
CustomButton(
text: "Cancel Request".needTranslation,
onPressed: () async {
openCancelOrderBottomSheet(context);
},
backgroundColor: AppColors.primaryRedColor,
borderColor: AppColors.primaryRedColor,
textColor: Colors.white,
icon: AppAssets.cancel,
iconSize: 20.h,
height: 40.h,
),
],
).paddingAll(16.h),
).paddingOnly(bottom: 16.h);
}
chip(String title, String iconString, Color iconColor) {
return AppCustomChipWidget(
labelText: title,
icon: iconString,
iconColor: iconColor,
iconSize: 12.h,
);
}
openCancelOrderBottomSheet(BuildContext context) {
context.read<EmergencyServicesViewModel>().cancelRRTOrder(order.iD);
}
}

@ -90,7 +90,7 @@ class _WalletPaymentConfirmPageState extends State<WalletPaymentConfirmPage> {
Transform.flip(
flipX: appState.isArabic(),
child: Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon,
icon: AppAssets.forward_arrow_icon_small,
iconColor: AppColors.blackColor,
width: 18.h,
height: 13.h,
@ -132,7 +132,7 @@ class _WalletPaymentConfirmPageState extends State<WalletPaymentConfirmPage> {
Transform.flip(
flipX: appState.isArabic(),
child: Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon,
icon: AppAssets.forward_arrow_icon_small,
iconColor: AppColors.blackColor,
width: 18.h,
height: 13.h,

@ -31,13 +31,23 @@ class ServicesPage extends StatelessWidget {
textColor: AppColors.blackColor,
route: AppRoutes.comprehensiveCheckupPage,
),
HmgServicesComponentModel(
12,
"Home Health Care".needTranslation,
"".needTranslation,
AppAssets.emergency_services_icon,
true,
bgColor: AppColors.bgGreenColor,
textColor: AppColors.blackColor,
route: AppRoutes.homeHealthCarePage,
),
];
@override
Widget build(BuildContext context) {
return CollapsingListView(
title: "Explore Services".needTranslation,
isLeading: false,
isLeading: Navigator.canPop(context),
child: Padding(
padding: EdgeInsets.all(24.h),
child: Column(

@ -31,6 +31,7 @@ import 'package:hmg_patient_app_new/presentation/book_appointment/book_appointme
import 'package:hmg_patient_app_new/presentation/book_appointment/livecare/immediate_livecare_pending_request_page.dart';
import 'package:hmg_patient_app_new/presentation/contact_us/contact_us.dart';
import 'package:hmg_patient_app_new/presentation/emergency_services/er_online_checkin/er_online_checkin_home.dart';
import 'package:hmg_patient_app_new/presentation/hmg_services/services_page.dart';
import 'package:hmg_patient_app_new/presentation/home/data/landing_page_data.dart';
import 'package:hmg_patient_app_new/presentation/home/widgets/habib_wallet_card.dart';
import 'package:hmg_patient_app_new/presentation/home/widgets/large_service_card.dart';
@ -245,7 +246,7 @@ class _LandingPageState extends State<LandingPage> {
indicatorLayout: PageIndicatorLayout.COLOR,
axisDirection: AxisDirection.right,
controller: _controller,
itemHeight: 210 + 25,
itemHeight: 200.h,
pagination: const SwiperPagination(
alignment: Alignment.bottomCenter,
margin: EdgeInsets.only(top: 210 + 8 + 24),
@ -439,7 +440,7 @@ class _LandingPageState extends State<LandingPage> {
}),
SizedBox(height: 16.h),
Container(
height: 120.h,
height: 121.h,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.r),
child: Column(
children: [
@ -526,11 +527,13 @@ class _LandingPageState extends State<LandingPage> {
SizedBox(width: 2.h),
Icon(Icons.arrow_forward_ios, color: AppColors.primaryRedColor, size: 10.h),
],
),
).onPress(() {
Navigator.of(context).push(CustomPageRoute(page: ServicesPage()));
}),
],
).paddingSymmetrical(24.h, 0.h),
SizedBox(
height: 280.h,
height: 340.h,
child: ListView.separated(
scrollDirection: Axis.horizontal,
itemCount: LandingPageData.getServiceCardsList.length,
@ -567,9 +570,8 @@ class _LandingPageState extends State<LandingPage> {
void showQuickLogin(BuildContext context) {
showCommonBottomSheetWithoutHeight(
context,
title: "",
// title: "",
isCloseButtonVisible: false,
child: StatefulBuilder(
builder: (context, setState) {
return QuickLogin(

@ -2,6 +2,7 @@ import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/book_appointment_page.dart';
import 'package:hmg_patient_app_new/presentation/contact_us/feedback_page.dart';
import 'package:hmg_patient_app_new/presentation/hmg_services/services_page.dart';
import 'package:hmg_patient_app_new/presentation/home/landing_page.dart';
import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart';
@ -29,7 +30,7 @@ class _LandingNavigationState extends State<LandingNavigation> {
physics: const NeverScrollableScrollPhysics(),
children: [
const LandingPage(),
appState.isAuthenticated ? MedicalFilePage() : /* need add feedback page */ const LandingPage(),
appState.isAuthenticated ? MedicalFilePage() : /* need add feedback page */ FeedbackPage(),
BookAppointmentPage(),
const ToDoPage(),
appState.isAuthenticated ? /* need add news page */ ServicesPage() : const LandingPage(),

@ -110,6 +110,7 @@ class SmallServiceCard extends StatelessWidget {
case "emergency":
context.read<EmergencyServicesViewModel>().flushData();
context.read<EmergencyServicesViewModel>().getTransportationOrders(showLoader: false,);
context.read<EmergencyServicesViewModel>().getRRTOrders(showLoader: false,);
Navigator.of(context).push(
CustomPageRoute(
page: EmergencyServicesPage(),

@ -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<HhcOrderDetailPage> createState() => _HhcOrderDetailPageState();
}
class _HhcOrderDetailPageState extends State<HhcOrderDetailPage> {
@override
void initState() {
super.initState();
final hmgServicesViewModel = context.read<HmgServicesViewModel>();
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<HmgServicesViewModel>(
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),
),
);
}
}

@ -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<HhcProceduresPage> createState() => _HhcProceduresPageState();
}
class _HhcProceduresPageState extends State<HhcProceduresPage> {
@override
void initState() {
super.initState();
final HmgServicesViewModel hmgServicesViewModel = context.read<HmgServicesViewModel>();
final AppState appState = getIt.get<AppState>();
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<GetCMCAllOrdersResponseModel> 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<GetCMCServicesResponseModel> 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<HmgServicesViewModel>(
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<AppState>().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<HmgServicesViewModel>();
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<HmgServicesViewModel>(
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<HmgServicesViewModel>(
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);
}
},
),
);
}
}

@ -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<GetCMCServicesResponseModel> selectedServices;
const HhcSelectionReviewPage({super.key, required this.selectedServices});
@override
State<HhcSelectionReviewPage> createState() => _HhcSelectionReviewPageState();
}
class _HhcSelectionReviewPageState extends State<HhcSelectionReviewPage> {
@override
void initState() {
super.initState();
// Initialize ViewModel state with selected services
WidgetsBinding.instance.addPostFrameCallback((_) {
final hmgServicesViewModel = context.read<HmgServicesViewModel>();
if (widget.selectedServices.isNotEmpty) {
hmgServicesViewModel.setSelectedServiceForHhcOrder(widget.selectedServices.first);
}
});
}
@override
Widget build(BuildContext context) {
final appState = getIt.get<AppState>();
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<HmgServicesViewModel>();
final appState = getIt.get<AppState>();
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,
);
}
}

@ -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<HmgServicesViewModel>();
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,
);
}
}

File diff suppressed because one or more lines are too long

@ -46,7 +46,7 @@ class LabOrderResultItem extends StatelessWidget {
padding: EdgeInsets.only(bottom: 8.h),
child: '${tests!.description}'.toText14(weight: FontWeight.w500),
),
'${tests!.packageShortDescription}'.toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight),
(tests!.packageShortDescription ?? "").toText12(fontWeight: FontWeight.w500, color: AppColors.textColorLight),
SizedBox(height: 12.h),
Row(
mainAxisSize: MainAxisSize.max,
@ -58,22 +58,25 @@ class LabOrderResultItem extends StatelessWidget {
fontSize: 24.f,
fontWeight: FontWeight.w600,
fontFamily: 'Poppins',
color: context.read<LabViewModel>().getColor(
tests?.calculatedResultFlag ?? "",
),
color: tests!.checkIfGraphShouldBeDisplayed()
? context.read<LabViewModel>().getColor(
tests?.calculatedResultFlag ?? "",
)
: Colors.grey.shade700,
letterSpacing: -2,
),
),
),
SizedBox(width: 4.h,),
Visibility(
visible: tests?.checkIfGraphShouldBeDisplayed() == true,
// visible: tests?.checkIfGraphShouldBeDisplayed() == true,
visible: true,
child: Expanded(
flex: 2,
child: Visibility(
visible: tests?.referanceRange != null,
child: Text(
"(Reference range ${tests?.referanceRange})".needTranslation,
"(Reference range: ${tests?.referanceRange})".needTranslation,
style: TextStyle(
fontSize: 12.f,
fontWeight: FontWeight.w500,

@ -344,7 +344,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
Consumer<MyAppointmentsViewModel>(builder: (context, myAppointmentsVM, child) {
// Provide an explicit height so the horizontal ListView has a bounded height
return SizedBox(
height: 190.h,
height: 192.h,
child: myAppointmentsVM.isMyAppointmentsLoading
? MedicalFileAppointmentCard(
patientAppointmentHistoryResponseModel: PatientAppointmentHistoryResponseModel(),
@ -472,7 +472,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
child: Column(
children: [
ListView.separated(
itemCount: prescriptionVM.patientPrescriptionOrders.length,
itemCount: prescriptionVM.patientPrescriptionOrders.length <= 2 ? prescriptionVM.patientPrescriptionOrders.length : 2,
shrinkWrap: true,
padding: EdgeInsets.only(left: 0, right: 8.w),
physics: NeverScrollableScrollPhysics(),
@ -533,6 +533,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
Navigator.of(context).push(
CustomPageRoute(
page: PrescriptionDetailPage(
isFromAppointments: false,
prescriptionsResponseModel: prescriptionVM.patientPrescriptionOrders[index]),
),
);
@ -564,7 +565,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
fontSize: 12.f,
fontWeight: FontWeight.w500,
borderRadius: 12.r,
height: 56.h,
height: 40.h,
icon: AppAssets.requests,
iconColor: AppColors.primaryRedColor,
iconSize: 16.w,
@ -581,7 +582,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
fontSize: 12.f,
fontWeight: FontWeight.w500,
borderRadius: 12.h,
height: 56.h,
height: 40.h,
icon: AppAssets.all_medications_icon,
iconColor: AppColors.primaryRedColor,
iconSize: 16.h,

@ -60,9 +60,7 @@ class LabRadCard extends StatelessWidget {
itemCount: 3,
)
: "You don't have any records yet".needTranslation.toText13(
color: AppColors.greyTextColor,
isCenter: true,
),
color: AppColors.greyTextColor, isCenter: true),
SizedBox(height: 16.h),
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,

@ -29,6 +29,7 @@ class MedicalFileCard extends StatelessWidget {
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: backgroundColor,
borderRadius: 12.r,
hasShadow: true
),
padding: EdgeInsets.all(12.w),
child: Column(

@ -25,9 +25,10 @@ import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart';
class PrescriptionDetailPage extends StatefulWidget {
PrescriptionDetailPage({super.key, required this.prescriptionsResponseModel});
PrescriptionDetailPage({super.key, required this.prescriptionsResponseModel, required this.isFromAppointments});
PatientPrescriptionsResponseModel prescriptionsResponseModel;
bool isFromAppointments = false;
@override
State<PrescriptionDetailPage> createState() => _PrescriptionDetailPageState();
@ -43,10 +44,12 @@ class _PrescriptionDetailPageState extends State<PrescriptionDetailPage> {
checkAndRemove(false);
// locationUtils = new LocationUtils(isShowConfirmDialog: true, context: context);
// WidgetsBinding.instance.addPostFrameCallback((_) => locationUtils.getCurrentLocation());
scheduleMicrotask(() {
prescriptionsViewModel.setPrescriptionsDetailsLoading();
prescriptionsViewModel.getPrescriptionDetails(widget.prescriptionsResponseModel);
});
if (!widget.isFromAppointments) {
scheduleMicrotask(() {
prescriptionsViewModel.setPrescriptionsDetailsLoading();
prescriptionsViewModel.getPrescriptionDetails(widget.prescriptionsResponseModel);
});
}
super.initState();
}
@ -146,7 +149,7 @@ class _PrescriptionDetailPageState extends State<PrescriptionDetailPage> {
CustomButton(
text: "Download Prescription".needTranslation,
onPressed: () async {
LoaderBottomSheet.showLoader();
LoaderBottomSheet.showLoader(loadingText: "Fetching prescription PDF, Please wait...".needTranslation);
await prescriptionVM.getPrescriptionPDFBase64(widget.prescriptionsResponseModel).then((val) async {
LoaderBottomSheet.hideLoader();
if (prescriptionVM.prescriptionPDFBase64Data.isNotEmpty) {
@ -181,8 +184,10 @@ class _PrescriptionDetailPageState extends State<PrescriptionDetailPage> {
),
),
).paddingSymmetrical(24.h, 0.h),
SizedBox(height: 16.h),
ListView.builder(
shrinkWrap: true,
padding: EdgeInsets.zero,
physics: NeverScrollableScrollPhysics(),
itemCount: prescriptionVM.isPrescriptionsDetailsLoading ? 5 : prescriptionVM.prescriptionDetailsList.length,
itemBuilder: (context, index) {

@ -255,22 +255,20 @@ class _PrescriptionsListPageState extends State<PrescriptionsListPage> {
Expanded(
flex: 1,
child: Container(
height: 40.h,
width: 40.h,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
height: 40.h,
width: 40.w,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.textColor,
borderRadius: 10.h,
),
borderRadius: 12,
),
child: Padding(
padding: EdgeInsets.all(8.h),
child: Transform.flip(
padding: EdgeInsets.all(12.h),
child: Transform.flip(
flipX: appState.isArabic(),
child: Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon_small,
iconColor: AppColors.whiteColor,
width: 10.h,
height: 10.h,
fit: BoxFit.contain,
fit: BoxFit.contain,
),
),
),
@ -278,9 +276,12 @@ class _PrescriptionsListPageState extends State<PrescriptionsListPage> {
model.setPrescriptionsDetailsLoading();
Navigator.of(context).push(
CustomPageRoute(
page: PrescriptionDetailPage(prescriptionsResponseModel: prescription),
),
);
page: PrescriptionDetailPage(
prescriptionsResponseModel: prescription,
isFromAppointments: false,
),
),
);
}),
),
],

@ -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),
),
),
);
}
}

@ -2,6 +2,8 @@ import 'dart:async';
import 'dart:developer';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
@ -21,11 +23,16 @@ class ToDoPage extends StatefulWidget {
}
class _ToDoPageState extends State<ToDoPage> {
late AppState appState;
@override
void initState() {
final TodoSectionViewModel todoSectionViewModel = context.read<TodoSectionViewModel>();
scheduleMicrotask(() async {
await todoSectionViewModel.initializeTodoSectionViewModel();
if (appState.isAuthenticated) {
await todoSectionViewModel.initializeTodoSectionViewModel();
}
});
super.initState();
}
@ -51,6 +58,7 @@ class _ToDoPageState extends State<ToDoPage> {
@override
Widget build(BuildContext context) {
appState = getIt.get<AppState>();
return CollapsingListView(
title: "ToDo List".needTranslation,
isLeading: false,

@ -5,6 +5,7 @@ import 'package:hmg_patient_app_new/presentation/authentication/register_step2.d
import 'package:hmg_patient_app_new/presentation/comprehensive_checkup/comprehensive_checkup_page.dart';
import 'package:hmg_patient_app_new/presentation/e_referral/e_referral_page_home.dart';
import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart';
import 'package:hmg_patient_app_new/presentation/home_health_care/hhc_procedures_page.dart';
import 'package:hmg_patient_app_new/presentation/medical_file/medical_file_page.dart';
import 'package:hmg_patient_app_new/splashPage.dart';
@ -17,6 +18,7 @@ class AppRoutes {
static const String medicalFilePage = '/medicalFilePage';
static const String eReferralPage = '/erReferralPage';
static const String comprehensiveCheckupPage = '/comprehensiveCheckupPage';
static const String homeHealthCarePage = '/homeHealthCarePage';
static Map<String, WidgetBuilder> get routes => {
initialRoute: (context) => SplashPage(),
@ -26,6 +28,7 @@ class AppRoutes {
registerStepTwo: (context) => RegisterNewStep2(),
medicalFilePage: (context) => MedicalFilePage(),
eReferralPage: (context) => EReferralPage(),
comprehensiveCheckupPage: (context) => ComprehensiveCheckupPage()
comprehensiveCheckupPage: (context) => ComprehensiveCheckupPage(),
homeHealthCarePage: (context) => HhcProceduresPage()
};
}

@ -3,21 +3,12 @@ import 'package:flutter/material.dart';
class AppColors {
static const transparent = Colors.transparent;
static const mainPurple = Color(0xFF7954F7);
static const purpleBg = Color(0xFFAEA4FC);
static const deepPurple = Color(0xFF7C65E7);
static const logoColor = Color(0xFF7C65E7);
static const buttonColor = Color(0xFF6A46F5);
static const splashBgColor = Color(0xFF3C355D);
static const lightGray = Color(0xFFF4F5F7);
static const lightPurple = Color(0xFFB7A3E6);
static const scaffoldBgColor = Color(0xFFF8F8F8);
static const bottomSheetBgColor = Color(0xFFF8F8FA);
static const lightGreyEFColor = Color(0xffeaeaff);
static const greyF7Color = Color(0xffF7F7F7);
static const lightGrayColor = Color(0xff808080);
static const buttonGrayColor = Color(0xffF1F1F1);
static const lightPurpleAlpha = Color(0x5AB7A3E6);
// New UI Colors
static const whiteColor = Color(0xFFffffff);
@ -37,11 +28,10 @@ class AppColors {
static const Color warningColorYellow = Color(0xFFF4A308);
static const Color blackBgColor = Color(0xFF2E3039);
static const blackColor = textColor;
static const Color inputLabelTextColor = Color(0xff898A8D);
static const Color inputLabelTextColor = Color(0xff898A8D);
static const Color greyTextColor = Color(0xFF8F9AA3);
static const Color lightGrayBGColor = Color(0x142E3039);
static const lightGreenColor = Color(0xFF0ccedde);
static const textGreenColor = Color(0xFF18C273);
static const Color ratingColorYellow = Color(0xFFFFAF15);
@ -57,27 +47,38 @@ class AppColors {
static const Color chipPrimaryRedBorderColor = Color(0xFFED1C2B);
static const Color chipSecondaryLightRedColor = Color(0xFFFEE9EA);
static const Color successLightColor = Color(0xFF18C273);
static const Color errorLightColor = Color(0xFFED1C2B);
static const Color alertLightColor = Color(0xFFD48D05);
static const Color infoLightColor = Color(0xFF0B85F7);
static const Color warningLightColor = Color(0xFFFFCC00);
static const Color greyLightColor = Color(0xFFEFEFF0);
static const Color thumbColor = Color(0xFF18C273);
static const Color switchBackgroundColor = Color(0x2618C273);
static const Color successLightColor = Color(0xFF18C273);
static const Color errorLightColor = Color(0xFFED1C2B);
static const Color alertLightColor = Color(0xFFD48D05);
static const Color infoLightColor = Color(0xFF0B85F7);
static const Color warningLightColor = Color(0xFFFFCC00);
static const Color greyLightColor = Color(0xFFEFEFF0);
static const Color thumbColor = Color(0xFF18C273);
static const Color switchBackgroundColor = Color(0x2618C273);
static const Color bottomNAVBorder = Color(0xFFEEEEEE);
static const Color bottomNAVBorder = Color(0xFFEEEEEE);
static const Color quickLoginColor = Color(0xFF666666);
static const Color quickLoginColor = Color(0xFF666666);
static const Color tooltipTextColor = Color(0xFF414D55);
static const Color graphGridColor = Color(0x4D18C273);
static const Color criticalLowAndHigh = Color(0xFFED1C2B);
static const Color highAndLow = Color(0xFFFFAF15);
static const Color labelTextColor = Color(0xFF838383);
static const Color calenderTextColor = Color(0xFFD0D0D0);
static const Color lightGreenButtonColor = Color(0x2618C273);
static const Color tooltipTextColor = Color(0xFF414D55);
static const Color graphGridColor = Color(0x4D18C273);
static const Color criticalLowAndHigh = Color(0xFFED1C2B);
static const Color highAndLow = Color(0xFFFFAF15);
static const Color labelTextColor = Color(0xFF838383);
static const Color calenderTextColor = Color(0xFFD0D0D0);
static const Color lightGreenButtonColor = Color(0x2618C273);
static const Color lightRedButtonColor = Color(0x1AED1C2B);
static const Color lightRedButtonColor = Color(0x1AED1C2B);
// Status Colors
static const Color statusPendingColor = Color(0xffCC9B14);
static const Color statusProcessingColor = Color(0xff2E303A);
static const Color statusCompletedColor = Color(0xff359846);
static const Color statusRejectedColor = Color(0xffD02127);
// Info Banner Colors
static const Color infoBannerBgColor = Color(0xFFFFF4E6);
static const Color infoBannerBorderColor = Color(0xFFFFE5B4);
static const Color infoBannerIconColor = Color(0xFFCC9B14);
static const Color infoBannerTextColor = Color(0xFF856404);
}

@ -21,7 +21,7 @@ class _CustomSwitchState extends State<CustomSwitch> {
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<CustomSwitch> {
width: 28.w,
height: 28.h,
decoration: BoxDecoration(
color: AppColors.thumbColor,
color: widget.value? AppColors.thumbColor : AppColors.greyColor,
shape: BoxShape.circle,
),
),

@ -54,7 +54,7 @@ class CollapsingListView extends StatelessWidget {
SliverAppBar(
automaticallyImplyLeading: false,
pinned: true,
expandedHeight: MediaQuery.of(context).size.height * 0.12.h,
expandedHeight: MediaQuery.of(context).size.height * 0.11.h,
stretch: true,
systemOverlayStyle: SystemUiOverlayStyle(statusBarBrightness: Brightness.light),
surfaceTintColor: Colors.transparent,
@ -92,8 +92,7 @@ class CollapsingListView extends StatelessWidget {
t,
)!,
child: Padding(
padding: EdgeInsets.only(
left: appState.isArabic() ? 0 : leftPadding, right: appState.isArabic() ? leftPadding : 0, bottom: bottomPadding),
padding: EdgeInsets.only(left: appState.isArabic() ? 0 : leftPadding, right: appState.isArabic() ? leftPadding : 0, bottom: bottomPadding),
child: Row(
spacing: 4.h,
children: [
@ -110,18 +109,11 @@ class CollapsingListView extends StatelessWidget {
color: AppColors.blackColor,
letterSpacing: -0.5),
).expanded,
if (logout != null)
actionButton(context, t, title: "Logout".needTranslation, icon: AppAssets.logout).onPress(logout!),
if (report != null)
actionButton(context, t, title: "Report".needTranslation, icon: AppAssets.report_icon).onPress(report!),
if (history != null)
actionButton(context, t, title: "History".needTranslation, icon: AppAssets.insurance_history_icon)
.onPress(history!),
if (instructions != null)
actionButton(context, t, title: "Instructions".needTranslation, icon: AppAssets.requests).onPress(instructions!),
if (requests != null)
actionButton(context, t, title: "Requests".needTranslation, icon: AppAssets.insurance_history_icon)
.onPress(requests!),
if (logout != null) actionButton(context, t, title: "Logout".needTranslation, icon: AppAssets.logout).onPress(logout!),
if (report != null) actionButton(context, t, title: "Report".needTranslation, icon: AppAssets.report_icon).onPress(report!),
if (history != null) actionButton(context, t, title: "History".needTranslation, icon: AppAssets.insurance_history_icon).onPress(history!),
if (instructions != null) actionButton(context, t, title: "Instructions".needTranslation, icon: AppAssets.requests).onPress(instructions!),
if (requests != null) actionButton(context, t, title: "Requests".needTranslation, icon: AppAssets.insurance_history_icon).onPress(requests!),
if (search != null) Utils.buildSvgWithAssets(icon: AppAssets.search_icon).onPress(search!).paddingOnly(right: 24),
if (trailing != null) trailing!,
],

@ -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: <Color>[
disabledColor ?? const Color(0xffEAEAEA),
disabledColor ?? const Color(0xffEAEAEA),
],
)
: LinearGradient(
transform: const GradientRotation(.83),
begin: Alignment.topRight,
end: Alignment.bottomLeft,
colors: colors ??
<Color>[
AppColors.buttonColor,
AppColors.buttonColor,
],
),
),
borderRadius: BorderRadius.circular(borderRadius),
gradient: onPress == null
? LinearGradient(
colors: <Color>[
disabledColor ?? const Color(0xffEAEAEA),
disabledColor ?? const Color(0xffEAEAEA),
],
)
: null),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
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(

@ -44,6 +44,7 @@ class AppCustomChipWidget extends StatelessWidget {
final EdgeInsetsDirectional? labelPadding;
final void Function()? onChipTap;
final void Function()? onDeleteTap;
@override
Widget build(BuildContext context) {
final iconS = iconSize ?? 12.w;
@ -65,12 +66,18 @@ class AppCustomChipWidget extends StatelessWidget {
child: icon.isNotEmpty
? Chip(
avatar: icon.isNotEmpty
? Utils.buildSvgWithAssets(icon: icon, width: iconS, height: iconS, iconColor: iconHasColor ? iconColor : null, fit: BoxFit.contain)
? Utils.buildSvgWithAssets(
icon: icon,
width: iconS,
height: iconS,
iconColor: iconHasColor ? iconColor : null,
fit: BoxFit.contain,
)
: SizedBox.shrink(),
label: richText ?? labelText!.toText10(weight: FontWeight.w500, letterSpacing: 0, color: textColor),
padding: padding,
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
labelPadding: labelPadding ?? EdgeInsetsDirectional.only(start: 2.w, end: deleteIcon?.isNotEmpty == true ? 2.w : 8.w),
labelPadding: labelPadding ?? EdgeInsetsDirectional.only(end: deleteIcon?.isNotEmpty == true ? 2.w : 8.w),
backgroundColor: backgroundColor,
shape: shape ??
SmoothRectangleBorder(
@ -79,19 +86,21 @@ class AppCustomChipWidget extends StatelessWidget {
side: BorderSide(color: AppColors.transparent, width: 1.5),
),
deleteIcon: deleteIcon?.isNotEmpty == true
? InkWell(onTap: onDeleteTap,child:Utils.buildSvgWithAssets(
icon: deleteIcon!,
width: iconS,
height: iconS,
iconColor: deleteIconHasColor ? deleteIconColor : null,
),)
? InkWell(
onTap: onDeleteTap,
child: Utils.buildSvgWithAssets(
icon: deleteIcon!,
width: iconS,
height: iconS,
iconColor: deleteIconHasColor ? deleteIconColor : null,
),
)
: null,
onDeleted: deleteIcon?.isNotEmpty == true ? () {} : null,
)
: Chip(
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
label: richText ?? labelText!.toText10(weight: FontWeight.w500, letterSpacing: 0, color: textColor),
label: richText ?? labelText!.toText10(weight: FontWeight.w500, letterSpacing: 0, color: textColor, isCenter: true),
padding: EdgeInsets.zero,
backgroundColor: backgroundColor,
shape: shape ??
@ -100,9 +109,12 @@ class AppCustomChipWidget extends StatelessWidget {
smoothness: 10,
side: BorderSide(color: AppColors.transparent, width: 1.5),
),
labelPadding: labelPadding ?? EdgeInsetsDirectional.only(start: 2.w, end: deleteIcon?.isNotEmpty == true ? 2.w : 8.w),
labelPadding: labelPadding ?? EdgeInsetsDirectional.only(start: 6.w, end: deleteIcon?.isNotEmpty == true ? 2.w : 8.w),
deleteIcon: deleteIcon?.isNotEmpty == true
? InkWell(onTap: onDeleteTap,child: Utils.buildSvgWithAssets(icon: deleteIcon!, width: iconS, height: iconS, iconColor: deleteIconHasColor ? deleteIconColor : null))
? InkWell(
onTap: onDeleteTap,
child: Utils.buildSvgWithAssets(
icon: deleteIcon!, width: iconS, height: iconS, iconColor: deleteIconHasColor ? deleteIconColor : null))
: null,
onDeleted: deleteIcon?.isNotEmpty == true ? () {} : null,
),

@ -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<String>(
sheetAnimationStyle: AnimationStyle(
@ -124,7 +125,8 @@ void showCommonBottomSheetWithoutHeight(
reverseDuration: Duration(milliseconds: 300),
),
constraints: BoxConstraints(
maxWidth: MediaQuery.of(context).size.width, // Full width
maxWidth: MediaQuery.sizeOf(context).width//MediaQuery.of(context).size.width, // Full width
),
context: context,
isScrollControlled: true,
@ -146,7 +148,7 @@ void showCommonBottomSheetWithoutHeight(
physics: ClampingScrollPhysics(),
child: isCloseButtonVisible
? Container(
padding: EdgeInsets.only(
padding: padding ?? const EdgeInsets.only(
left: 24,
top: 24,
right: 24,
@ -160,25 +162,28 @@ void showCommonBottomSheetWithoutHeight(
mainAxisSize: MainAxisSize.min,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
titleWidget ??
Expanded(
child: title.toText20(weight: FontWeight.w600),
),
if (isCloseButtonVisible) ...[
Utils.buildSvgWithAssets(
icon: AppAssets.close_bottom_sheet_icon,
iconColor: Color(0xff2B353E),
).onPress(() {
onCloseClicked?.call();
Navigator.of(context).pop();
}),],
],
Padding(
padding: padding != null? EdgeInsets.symmetric(horizontal: 24.w): EdgeInsets.zero,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
titleWidget ??
Expanded(
child: title.toText20(weight: FontWeight.w600),
),
if (isCloseButtonVisible) ...[
Utils.buildSvgWithAssets(
icon: AppAssets.close_bottom_sheet_icon,
iconColor: Color(0xff2B353E),
).onPress(() {
onCloseClicked?.call();
Navigator.of(context).pop();
}),],
],
),
),
SizedBox(height: 16.h),
isCloseButtonVisible ? SizedBox(height: 16.h) : SizedBox.shrink(),
child,
],
),

@ -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) {

@ -43,6 +43,11 @@ class TextInputWidget extends StatelessWidget {
final Color? labelColor;
final Function(String)? onSubmitted;
// new multiline options
final bool isMultiline;
final int minLines;
final int maxLines;
// final List<Country> countryList;
// final Function(Country)? onCountryChange;
@ -73,10 +78,14 @@ class TextInputWidget extends StatelessWidget {
this.isWalletAmountInput = false,
this.suffix,
this.labelColor,
this.onSubmitted
// this.countryList = const [],
// this.onCountryChange,
});
this.onSubmitted,
// multiline defaults
this.isMultiline = false,
this.minLines = 3,
this.maxLines = 6,
// this.countryList = const [],
// this.onCountryChange,
});
final FocusNode _focusNode = FocusNode();
@ -113,7 +122,7 @@ class TextInputWidget extends StatelessWidget {
children: [
Container(
padding: padding,
height: 64.h,
height: isMultiline ? null : 64.h,
alignment: Alignment.center,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: Colors.white,
@ -205,9 +214,7 @@ class TextInputWidget extends StatelessWidget {
initialDate: DateTime.now(),
fontFamily: appState.getLanguageCode() == "ar" ? "GESSTwo" : "Poppins",
okWidget: Padding(padding: EdgeInsets.only(right: 8.h), child: Utils.buildSvgWithAssets(icon: AppAssets.confirm, width: 24.h, height: 24.h)),
cancelWidget: Padding(
padding: EdgeInsets.only(right: 8.h),
child: Utils.buildSvgWithAssets(icon: AppAssets.cancel, iconColor: Colors.white, width: 24.h, height: 24.h)),
cancelWidget: Padding(padding: EdgeInsets.only(right: 8.h), child: Utils.buildSvgWithAssets(icon: AppAssets.cancel, iconColor: Colors.white, width: 24.h, height: 24.h)),
onCalendarTypeChanged: (bool value) {
isGregorian = value;
});
@ -240,7 +247,7 @@ class TextInputWidget extends StatelessWidget {
return TextField(
enabled: isEnable,
scrollPadding: EdgeInsets.zero,
keyboardType: keyboardType,
keyboardType: isMultiline ? TextInputType.multiline : keyboardType,
controller: controller,
readOnly: isReadOnly,
textAlignVertical: TextAlignVertical.top,
@ -255,7 +262,15 @@ class TextInputWidget extends StatelessWidget {
FocusManager.instance.primaryFocus?.unfocus();
},
onSubmitted: onSubmitted,
style: TextStyle(fontSize: fontS, height: isWalletAmountInput! ? 1 / 4 : 0, fontWeight: FontWeight.w500, color: AppColors.textColor, letterSpacing: -1),
minLines: isMultiline ? minLines : 1,
maxLines: isMultiline ? maxLines : 1,
style: TextStyle(
fontSize: fontS,
height: isMultiline ? 1.2 : (isWalletAmountInput! ? 1 / 4 : 0),
fontWeight: FontWeight.w500,
color: AppColors.textColor,
letterSpacing: -1,
),
decoration: InputDecoration(
isDense: true,
hintText: hintText,

@ -4,6 +4,8 @@ import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_export.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:huawei_map/huawei_map.dart' ;
class HMSMap extends StatefulWidget{
@ -55,7 +57,7 @@ class _HMSMapState extends State<HMSMap> {
visible: widget.showCenterMarker,
child: Align(
alignment: Alignment.center,
child: Utils.buildSvgWithAssets(icon: AppAssets.pin_location, width: 24.w, height: 36.h),
child: Icon(Icons.location_pin, size: 36.h, color: AppColors.primaryRedColor).paddingOnly(bottom: 24.h),
),
)
],

@ -5,6 +5,8 @@ import 'package:google_maps_flutter/google_maps_flutter.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
class GMSMap extends StatelessWidget{
Completer<GoogleMapController>? controller;
@ -29,14 +31,15 @@ class GMSMap extends StatelessWidget{
children: [
GoogleMap(
mapType: mapType,
zoomControlsEnabled: false,
myLocationEnabled: myLocationEnabled,
myLocationButtonEnabled: false,
zoomControlsEnabled: true,
myLocationEnabled: myLocationEnabled,
myLocationButtonEnabled: false,
compassEnabled: compassEnabled,
initialCameraPosition: currentLocation,
onCameraMove: (value) => onCameraMoved(value),
onCameraIdle: ()=>onCameraIdle(),
onMapCreated: (GoogleMapController controller) {
// padding: EdgeInsets.only(bottom: 300.h),
onMapCreated: (GoogleMapController controller) {
this.controller?.complete(controller);
},
),
@ -44,10 +47,10 @@ class GMSMap extends StatelessWidget{
visible: showCenterMarker,
child: Align(
alignment: Alignment.center,
child: Utils.buildSvgWithAssets(icon: AppAssets.pin_location, width: 24.w, height: 36.h),
),
)
],
);
child: Icon(Icons.location_pin, size: 36.h, color: AppColors.primaryRedColor).paddingOnly(bottom: 24.h),
),
)
],
);
}
}

@ -46,7 +46,7 @@ class RadioListTileWidget<T> extends StatelessWidget {
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(
color: isSelected ? AppColors.blackColor : const Color(0xff9E9E9E),
color: isSelected ? AppColors.primaryRedColor : const Color(0xff9E9E9E),
width: 2,
),
),
@ -57,7 +57,7 @@ class RadioListTileWidget<T> extends StatelessWidget {
height: 10.h,
decoration: BoxDecoration(
shape: BoxShape.circle,
color: AppColors.blackColor,
color: AppColors.primaryRedColor,
),
),
)

Loading…
Cancel
Save