* 'master' of http://34.17.182.140/Haroon6138/HMG_Patient_App_New:
  fized the symptoms zoom issue
  Payment request & response insert implemented in paytabs.
  updates
  updates
  updates
dev_sultan
Sultan khan 1 week ago
commit 5b749f7718

@ -8,8 +8,8 @@
/* Begin PBXBuildFile section */
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */ = {isa = PBXBuildFile; fileRef = 1498D2331E8E89220040F4C2 /* GeneratedPluginRegistrant.m */; };
1587365701FF0D2AAAFED0D8 /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A83C76CFF9D3F92EDE3B22B4 /* Pods_Runner.framework */; };
331C808B294A63AB00263BE5 /* RunnerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 331C807B294A618700263BE5 /* RunnerTests.swift */; };
37D27FAF2C38AC83B852680C /* Pods_Runner.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = A83C76CFF9D3F92EDE3B22B4 /* Pods_Runner.framework */; };
3B3967161E833CAA004F5970 /* AppFrameworkInfo.plist in Resources */ = {isa = PBXBuildFile; fileRef = 3B3967151E833CAA004F5970 /* AppFrameworkInfo.plist */; };
478CFA942E638C8E0064F3D7 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = 478CFA932E638C8E0064F3D7 /* GoogleService-Info.plist */; };
61243B492EC5FA3700D46FA0 /* PenguinModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 61243B422EC5FA3700D46FA0 /* PenguinModel.swift */; };
@ -100,7 +100,7 @@
76B1D2512F91BF8000DBBD1B /* Penguin.xcframework in Frameworks */,
76B1D2552F91BF8000DBBD1B /* PenNavUI.xcframework in Frameworks */,
76B1D2532F91BF8000DBBD1B /* PenguinINRenderer.xcframework in Frameworks */,
DE1EF78253E64BE86845D2CC /* Pods_Runner.framework in Frameworks */,
37D27FAF2C38AC83B852680C /* Pods_Runner.framework in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@ -339,10 +339,14 @@
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-input-files.xcfilelist",
);
inputPaths = (
);
name = "[CP] Copy Pods Resources";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources-${CONFIGURATION}-output-files.xcfilelist",
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-resources.sh\"\n";
@ -356,10 +360,14 @@
inputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-input-files.xcfilelist",
);
inputPaths = (
);
name = "[CP] Embed Pods Frameworks";
outputFileListPaths = (
"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks-${CONFIGURATION}-output-files.xcfilelist",
);
outputPaths = (
);
runOnlyForDeploymentPostprocessing = 0;
shellPath = /bin/sh;
shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-Runner/Pods-Runner-frameworks.sh\"\n";
@ -519,7 +527,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 3A359E86ZF;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
@ -529,7 +537,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 0.0.5;
MARKETING_VERSION = 0.0.12;
PRODUCT_BUNDLE_IDENTIFIER = com.cloudsolutions.HMGPatientApp;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -706,7 +714,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 3A359E86ZF;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
@ -716,7 +724,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 0.0.5;
MARKETING_VERSION = 0.0.12;
PRODUCT_BUNDLE_IDENTIFIER = com.cloudsolutions.HMGPatientApp;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@ -736,7 +744,7 @@
CODE_SIGN_ENTITLEMENTS = Runner/Runner.entitlements;
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
CURRENT_PROJECT_VERSION = 2;
CURRENT_PROJECT_VERSION = 1;
DEVELOPMENT_TEAM = 3A359E86ZF;
ENABLE_BITCODE = NO;
INFOPLIST_FILE = Runner/Info.plist;
@ -746,7 +754,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
MARKETING_VERSION = 0.0.5;
MARKETING_VERSION = 0.0.12;
PRODUCT_BUNDLE_IDENTIFIER = com.cloudsolutions.HMGPatientApp;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";

@ -5,7 +5,7 @@ import 'package:hmg_patient_app_new/core/enums.dart';
class ApiConsts {
static const maxSmallScreen = 660;
static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.prod;
static AppEnvironmentTypeEnum appEnvironmentType = AppEnvironmentTypeEnum.uat;
// static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT

@ -369,7 +369,11 @@ class AppDependencies {
errorHandlerService: getIt(),
));
getIt.registerLazySingleton<PayTabsViewModel>(() => PayTabsViewModel(appState: getIt()));
getIt.registerLazySingleton<PayTabsViewModel>(() => PayTabsViewModel(
appState: getIt(),
payfortRepo: getIt(),
errorHandlerService: getIt(),
));
getIt.registerLazySingleton<ClarityService>(
() => ClarityService(appState: getIt.get<AppState>()),

@ -25,7 +25,7 @@ import 'models/resp_models/appointment_details_resp_model.dart';
abstract class MyAppointmentsRepo {
Future<Either<Failure, GenericApiModel<List<PatientAppointmentHistoryResponseModel>>>> getPatientAppointments(
{required bool isActiveAppointment, required bool isArrivedAppointments, bool isForEyeMeasurement = false});
{required bool isActiveAppointment, required bool isArrivedAppointments, bool isForEyeMeasurement = false, bool isForTimeLine = false});
Future<Either<Failure, GenericApiModel<PatientAppointmentShareResponseModel>>> getPatientShareAppointment(
{required int projectID, required int clinicID, required String appointmentNo, required bool isLiveCareAppointment});
@ -105,14 +105,15 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo {
@override
Future<Either<Failure, GenericApiModel<List<PatientAppointmentHistoryResponseModel>>>> getPatientAppointments(
{required bool isActiveAppointment, required bool isArrivedAppointments, bool isForEyeMeasurement = false}) async {
{required bool isActiveAppointment, required bool isArrivedAppointments, bool isForEyeMeasurement = false, bool isForTimeLine = false}) async {
Map<String, dynamic> mapDevice = {
"IsActiveAppointment": isActiveAppointment,
"IsComingFromCOC": false,
"isForUpcomming": false,
"IsForMedicalReport": false,
"IsForArrived": isArrivedAppointments,
"IsIrisPrescription": isForEyeMeasurement
"IsIrisPrescription": isForEyeMeasurement,
"IsForTimeLine": isForTimeLine
};
try {

@ -55,6 +55,7 @@ class MyAppointmentsViewModel extends ChangeNotifier {
bool isAppointmentDataToBeLoaded = true;
bool isMyDoctorsDataToBeLoaded = true;
bool isArrivedAppointmentDataLoaded = false;
bool isEyeMeasurementsAppointmentsLoading = false;
@ -76,7 +77,7 @@ class MyAppointmentsViewModel extends ChangeNotifier {
List<PatientAppointmentHistoryResponseModel> patientUpcomingAppointmentsHistoryList = [];
List<PatientAppointmentHistoryResponseModel> patientArrivedAppointmentsHistoryList = [];
List<PatientAppointmentHistoryResponseModel> patientTimelineAppointmentsList = [];
List<PatientAppointmentHistoryResponseModel> patientAllArrivedAppointmentsHistoryList = [];
List<PatientAppointmentHistoryResponseModel> patientMyDoctorsList = [];
@ -172,7 +173,7 @@ class MyAppointmentsViewModel extends ChangeNotifier {
patientAppointmentsHistoryList.clear();
patientUpcomingAppointmentsHistoryList.clear();
patientArrivedAppointmentsHistoryList.clear();
patientTimelineAppointmentsList.clear();
patientAllArrivedAppointmentsHistoryList.clear();
patientEyeMeasurementsAppointmentsHistoryList.clear();
isMyAppointmentsLoading = true;
isTimeLineAppointmentsLoading = true;
@ -266,7 +267,7 @@ class MyAppointmentsViewModel extends ChangeNotifier {
);
}
Future<void> getPatientAppointments(bool isActiveAppointment, bool isArrivedAppointments, {Function(dynamic)? onSuccess, Function(String)? onError}) async {
Future<void> getPatientAppointments(bool isActiveAppointment, bool isArrivedAppointments, {bool isForTimeLine = false, Function(dynamic)? onSuccess, Function(String)? onError}) async {
if (patientArrivedAppointmentsHistoryList.isNotEmpty) {
isPatientHasQueueAppointment = false;
notifyListeners();
@ -277,12 +278,11 @@ class MyAppointmentsViewModel extends ChangeNotifier {
}
if (!isAppointmentDataToBeLoaded) return;
patientAppointmentsByClinic.clear();
patientAppointmentsByHospital.clear();
patientAppointmentsViewList.clear();
patientTimelineAppointmentsList.clear();
patientAllArrivedAppointmentsHistoryList.clear();
filteredAppointmentList.clear();
patientAppointmentsHistoryList.clear();
patientUpcomingAppointmentsHistoryList.clear();
@ -291,7 +291,7 @@ class MyAppointmentsViewModel extends ChangeNotifier {
final results = await Future.wait([
myAppointmentsRepo.getPatientAppointments(isActiveAppointment: isActiveAppointment, isArrivedAppointments: isArrivedAppointments),
myAppointmentsRepo.getPatientAppointments(isActiveAppointment: false, isArrivedAppointments: true),
myAppointmentsRepo.getPatientAppointments(isActiveAppointment: false, isArrivedAppointments: true, isForTimeLine: isForTimeLine),
]);
results[0].fold(
@ -320,6 +320,10 @@ class MyAppointmentsViewModel extends ChangeNotifier {
patientArrivedAppointmentsHistoryList = apiResponse.data!;
isMyAppointmentsLoading = false;
isAppointmentDataToBeLoaded = false;
if (!isForTimeLine) {
patientAllArrivedAppointmentsHistoryList = apiResponse.data!;
isArrivedAppointmentDataLoaded = true;
}
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);

@ -11,17 +11,29 @@ import 'package:flutter_paytabs_bridge/PaymentSdkTransactionType.dart';
import 'package:flutter_paytabs_bridge/flutter_paytabs_bridge.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/cache_consts.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/paytabs_config.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/features/paytabs/models/paytabs_transaction_response_model.dart';
import 'package:hmg_patient_app_new/features/payfort/models/payfort_request_insert_model.dart';
import 'package:hmg_patient_app_new/features/payfort/models/payfort_response_insert_model.dart';
import 'package:hmg_patient_app_new/features/payfort/payfort_repo.dart';
import 'package:hmg_patient_app_new/services/error_handler_service.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
class PayTabsViewModel extends ChangeNotifier {
late PaymentSdkConfigurationDetails paymentConfiguration;
final AppState appState;
final PayfortRepo payfortRepo;
final ErrorHandlerService errorHandlerService;
late PaytabsTransactionResponseModel paytabsTransactionResponseModel;
PayTabsViewModel({
required this.appState,
required this.payfortRepo,
required this.errorHandlerService,
});
setPaymentConfiguration(String paymentDescription, num amount, {bool isAddCard = false}) {
@ -152,6 +164,42 @@ class PayTabsViewModel extends ChangeNotifier {
notifyListeners();
}
Future<void> paytabsRequestInsert({required PayfortRequestInsertModel payfortRequestInsertModel, Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result = await payfortRepo.payfortRequestInsert(payfortRequestInsertModel: payfortRequestInsertModel);
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);
}
}
},
);
}
Future<void> paytabsResponseInsert({required PayfortResponseInsertModel payfortResponseInsertModel, Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result = await payfortRepo.payfortResponseInsert(payfortResponseInsertModel: payfortResponseInsertModel);
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);
}
}
},
);
}
startApplePayPayment({Function(PaytabsTransactionResponseModel)? onSuccess, Function(String)? onError}) async {
bool _callbackFired = false; // prevent duplicate callback invocations
@ -189,41 +237,132 @@ class PayTabsViewModel extends ChangeNotifier {
});
}
startCardPayment({Function(PaytabsTransactionResponseModel)? onSuccess, Function(String)? onError}) {
startCardPayment({
required String paymentOption,
required String liveServiceID,
required String isScheduleLiveCare,
required String appointmentDate,
required String appointmentNo,
required String doctorID,
required String transactionID,
required String amount,
required String fileNumber,
required String orderDescription,
required String serviceID,
required String projectID,
Function(PaytabsTransactionResponseModel)? onSuccess,
Function(String)? onError,
}) async {
bool _callbackFired = false; // prevent duplicate callback invocations
FlutterPaytabsBridge.startCardPayment(paymentConfiguration, (event) {
if (_callbackFired) return; // ignore any subsequent callback calls
try {
LoaderBottomSheet.showLoader();
final transactionDetails = event["data"] as Map<Object?, Object?>?;
if (event["status"] == "success") {
_callbackFired = true;
// Cast Map<Object?, Object?> to Map<String, dynamic>
final data = Map<String, dynamic>.from(event["data"] as Map);
paytabsTransactionResponseModel = PaytabsTransactionResponseModel.fromJson(data);
if (paytabsTransactionResponseModel.isSuccess!) {
onSuccess!(paytabsTransactionResponseModel);
} else {
// Transaction was processed but failed
final reason =
transactionDetails?["payResponseReturn"] ?? transactionDetails?["responseMessage"] ?? transactionDetails?["message"] ?? "Unknown error";
final responseCode = transactionDetails?["responseCode"] ?? "";
final errorMessage =
responseCode.toString().isNotEmpty ? "Transaction failed (Code: $responseCode): $reason" : "Transaction failed: $reason";
onError!(errorMessage);
// Create PayfortRequestInsertModel (same as Payfort implementation)
PayfortRequestInsertModel payfortRequestInsertModel = PayfortRequestInsertModel();
payfortRequestInsertModel.commandType = "SALE";
payfortRequestInsertModel.amount = num.parse(amount);
payfortRequestInsertModel.customerEmail = getIt.get<AppState>().getAuthenticatedUser()!.emailAddress;
payfortRequestInsertModel.orderDescription = orderDescription;
payfortRequestInsertModel.serviceID = serviceID;
payfortRequestInsertModel.paymentOption = paymentOption;
payfortRequestInsertModel.clientRequestID = transactionID;
payfortRequestInsertModel.projectID = projectID;
payfortRequestInsertModel.fileNumber = getIt.get<AppState>().getAuthenticatedUser()!.patientId.toString();
payfortRequestInsertModel.customerName = "${getIt.get<AppState>().getAuthenticatedUser()!.firstName} ${getIt.get<AppState>().getAuthenticatedUser()!.lastName}";
payfortRequestInsertModel.currency = getIt.get<AppState>().getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED";
payfortRequestInsertModel.liveServiceID = liveServiceID;
payfortRequestInsertModel.latitude = getIt.get<AppState>().userLat.toString();
payfortRequestInsertModel.longitude = getIt.get<AppState>().userLong.toString();
payfortRequestInsertModel.merchantReference = transactionID;
payfortRequestInsertModel.deviceToken = await Utils.getStringFromPrefs(CacheConst.pushToken);
payfortRequestInsertModel.language = getIt.get<AppState>().isArabic() ? "ar" : "en";
payfortRequestInsertModel.integrationId = "2"; // PayTabs integration ID
payfortRequestInsertModel.isSchedule = isScheduleLiveCare;
payfortRequestInsertModel.appointmentDate = appointmentDate;
payfortRequestInsertModel.appointmentNo = appointmentNo;
payfortRequestInsertModel.doctorID = doctorID;
// Call request insert API before payment
await paytabsRequestInsert(payfortRequestInsertModel: payfortRequestInsertModel);
LoaderBottomSheet.hideLoader();
// Start PayTabs payment
FlutterPaytabsBridge.startCardPayment(paymentConfiguration, (event) async {
if (_callbackFired) return; // ignore any subsequent callback calls
final transactionDetails = event["data"] as Map<Object?, Object?>?;
if (event["status"] == "success") {
_callbackFired = true;
LoaderBottomSheet.showLoader();
// Cast Map<Object?, Object?> to Map<String, dynamic>
final data = Map<String, dynamic>.from(event["data"] as Map);
paytabsTransactionResponseModel = PaytabsTransactionResponseModel.fromJson(data);
// Create PayfortResponseInsertModel (same as Payfort implementation)
PayfortResponseInsertModel payfortResponseInsertModel = PayfortResponseInsertModel();
payfortResponseInsertModel.fortId = paytabsTransactionResponseModel.transactionReference ?? "";
payfortResponseInsertModel.commandType = paytabsTransactionResponseModel.transactionType ?? "SALE";
// payfortResponseInsertModel.amount = paytabsTransactionResponseModel.tranTotal ?? 0;
payfortResponseInsertModel.amount = num.parse(amount);
payfortResponseInsertModel.sadadOLP = "";
payfortResponseInsertModel.paymentOption = paytabsTransactionResponseModel.paymentInfo?.paymentMethod ?? "";
payfortResponseInsertModel.customerIP = "";
payfortResponseInsertModel.eCI = "";
payfortResponseInsertModel.responseMessage = paytabsTransactionResponseModel.payResponseReturn ?? "";
payfortResponseInsertModel.cardNumber = paytabsTransactionResponseModel.paymentInfo!.paymentDescription;
payfortResponseInsertModel.cardBin = "";
payfortResponseInsertModel.installmentsNumber = 0;
payfortResponseInsertModel.status = paytabsTransactionResponseModel.isSuccess! ? "success" : "failed";
payfortResponseInsertModel.merchantRef = transactionID;
payfortResponseInsertModel.isRefund = false;
payfortResponseInsertModel.remmeberMe = true;
payfortResponseInsertModel.reconciliationReference = "";
// Call response insert API after payment
await paytabsResponseInsert(payfortResponseInsertModel: payfortResponseInsertModel);
LoaderBottomSheet.hideLoader();
if (paytabsTransactionResponseModel.isSuccess!) {
if (onSuccess != null) {
onSuccess(paytabsTransactionResponseModel);
}
} else {
// Transaction was processed but failed
final reason = transactionDetails?["payResponseReturn"] ?? transactionDetails?["responseMessage"] ?? transactionDetails?["message"] ?? "Unknown error";
final responseCode = transactionDetails?["responseCode"] ?? "";
final errorMessage = responseCode.toString().isNotEmpty ? "Transaction failed (Code: $responseCode): $reason" : "Transaction failed: $reason";
if (onError != null) {
onError(errorMessage);
}
}
} else if (event["status"] == "error") {
_callbackFired = true;
final errorMessage = event["message"] ?? "An error occurred";
debugPrint("Error occurred in transaction: $errorMessage");
debugPrint("Full error event: $event");
if (onError != null) {
onError(errorMessage);
}
} else if (event["status"] == "event") {
// Intermediate SDK events do NOT mark _callbackFired, just log
final eventMessage = event["message"] ?? "Event occurred";
debugPrint("Event occurred: $eventMessage");
}
} else if (event["status"] == "error") {
_callbackFired = true;
final errorMessage = event["message"] ?? "An error occurred";
debugPrint("Error occurred in transaction: $errorMessage");
debugPrint("Full error event: $event");
onError!(errorMessage);
} else if (event["status"] == "event") {
// Intermediate SDK events do NOT mark _callbackFired, just log
final eventMessage = event["message"] ?? "Event occurred";
debugPrint("Event occurred: $eventMessage");
});
} catch (error) {
debugPrint("PayTabs Payment Error: $error");
LoaderBottomSheet.hideLoader();
if (onError != null) {
onError("Payment initialization failed: $error");
}
});
}
}
startAlternatePayment({Function(PaytabsTransactionResponseModel)? onSuccess, Function(String)? onError}) {

@ -139,12 +139,30 @@ class _AppointmentPaymentPageState extends State<AppointmentPaymentPage> {
).paddingSymmetrical(24.h, 0.h).onPress(() async {
selectedPaymentMethod = "MADA";
if (appState.isPaytabsEnabled) {
transID = Utils.getAppointmentTransID(
widget.patientAppointmentHistoryResponseModel.projectID,
widget.patientAppointmentHistoryResponseModel.clinicID,
widget.patientAppointmentHistoryResponseModel.appointmentNo,
);
paytabsViewModel.setPaymentConfiguration(
"Appointment Payment",
num.parse(myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString()),
);
paytabsViewModel.startCardPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr(context: context));
paytabsViewModel.startCardPayment(
paymentOption: selectedPaymentMethod,
liveServiceID: "0",
isScheduleLiveCare: widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? "true" : "false",
appointmentDate: widget.patientAppointmentHistoryResponseModel.appointmentDate!,
appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(),
doctorID: widget.patientAppointmentHistoryResponseModel.doctorID.toString(),
transactionID: transID,
amount: myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString(),
fileNumber: appState.getAuthenticatedUser()!.patientId.toString(),
orderDescription: "Appointment Payment",
serviceID: ServiceTypeEnum.appointmentPayment.getIdFromServiceEnum().toString(),
projectID: widget.patientAppointmentHistoryResponseModel.projectID.toString(),
onSuccess: (PaytabsTransactionResponseModel transactionData) async {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr(context: context));
await myAppointmentsViewModel.createAdvancePayment(
paymentMethodName: selectedPaymentMethod,
projectID: widget.patientAppointmentHistoryResponseModel.projectID,
@ -362,12 +380,30 @@ class _AppointmentPaymentPageState extends State<AppointmentPaymentPage> {
).paddingSymmetrical(24.h, 0.h).onPress(() async {
selectedPaymentMethod = "VISA";
if (appState.isPaytabsEnabled) {
transID = Utils.getAppointmentTransID(
widget.patientAppointmentHistoryResponseModel.projectID,
widget.patientAppointmentHistoryResponseModel.clinicID,
widget.patientAppointmentHistoryResponseModel.appointmentNo,
);
paytabsViewModel.setPaymentConfiguration(
"Appointment Payment",
num.parse(myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString()),
);
paytabsViewModel.startCardPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr(context: context));
paytabsViewModel.startCardPayment(
paymentOption: selectedPaymentMethod,
liveServiceID: "0",
isScheduleLiveCare: widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment! ? "true" : "false",
appointmentDate: widget.patientAppointmentHistoryResponseModel.appointmentDate!,
appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(),
doctorID: widget.patientAppointmentHistoryResponseModel.doctorID.toString(),
transactionID: transID,
amount: myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString(),
fileNumber: appState.getAuthenticatedUser()!.patientId.toString(),
orderDescription: "Appointment Payment",
serviceID: ServiceTypeEnum.appointmentPayment.getIdFromServiceEnum().toString(),
projectID: widget.patientAppointmentHistoryResponseModel.projectID.toString(),
onSuccess: (PaytabsTransactionResponseModel transactionData) async {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr(context: context));
await myAppointmentsViewModel.createAdvancePayment(
paymentMethodName: selectedPaymentMethod,
projectID: widget.patientAppointmentHistoryResponseModel.projectID,

@ -552,6 +552,7 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
Transform.flip(flipX: appState.isArabic(), child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)),
],
).onPress(() {
Clarity.sendCustomEvent('Select_Region_Page');
bookAppointmentsViewModel.setProjectID(null);
openRegionListBottomSheet(context, RegionBottomSheetType.FOR_REGION);
}),

@ -1,3 +1,4 @@
import 'package:clarity_flutter/clarity_flutter.dart';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
@ -30,6 +31,7 @@ class DoctorProfilePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
Clarity.setCurrentScreenName('Doctor Profile Page');
final bookAppointmentsViewModel = Provider.of<BookAppointmentsViewModel>(context, listen: false);
final appState = getIt.get<AppState>();
return Scaffold(

@ -130,11 +130,29 @@ class _ImmediateLiveCarePaymentPageState extends State<ImmediateLiveCarePaymentP
).paddingSymmetrical(24.h, 0.h).onPress(() async {
selectedPaymentMethod = "MADA";
if (appState.isPaytabsEnabled) {
transID = Utils.getAppointmentTransID(
immediateLiveCareViewModel.immediateLiveCareSelectedClinic.serviceID!,
ApiConsts.appEnvironmentType == AppEnvironmentTypeEnum.uat ? 15 : 12,
DateTime.now().millisecondsSinceEpoch,
);
paytabsViewModel.setPaymentConfiguration(
"LiveCare Payment",
num.parse(immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.total ?? "0.0"),
);
paytabsViewModel.startCardPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async {
paytabsViewModel.startCardPayment(
paymentOption: selectedPaymentMethod,
liveServiceID: immediateLiveCareViewModel.immediateLiveCareSelectedClinic.serviceID.toString(),
isScheduleLiveCare: "false",
appointmentDate: DateUtil.convertDateToString(DateTime.now()),
appointmentNo: "0",
doctorID: "0",
transactionID: transID,
amount: myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString(),
fileNumber: appState.getAuthenticatedUser()!.patientId.toString(),
orderDescription: "LiveCare Payment",
serviceID: ServiceTypeEnum.appointmentPayment.getIdFromServiceEnum().toString(),
projectID: (ApiConsts.appEnvironmentType == AppEnvironmentTypeEnum.uat ? 15 : 12).toString(),
onSuccess: (PaytabsTransactionResponseModel transactionData) async {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr());
await immediateLiveCareViewModel.addNewCallRequestForImmediateLiveCare(transactionData.transactionReference!);
await immediateLiveCareViewModel.getPatientLiveCareHistory();
@ -281,11 +299,29 @@ class _ImmediateLiveCarePaymentPageState extends State<ImmediateLiveCarePaymentP
).paddingSymmetrical(24.h, 0.h).onPress(() async {
selectedPaymentMethod = "VISA";
if (appState.isPaytabsEnabled) {
transID = Utils.getAppointmentTransID(
immediateLiveCareViewModel.immediateLiveCareSelectedClinic.serviceID!,
ApiConsts.appEnvironmentType == AppEnvironmentTypeEnum.uat ? 15 : 12,
DateTime.now().millisecondsSinceEpoch,
);
paytabsViewModel.setPaymentConfiguration(
"LiveCare Payment",
num.parse(immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.total ?? "0.0"),
);
paytabsViewModel.startCardPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async {
paytabsViewModel.startCardPayment(
paymentOption: selectedPaymentMethod,
liveServiceID: immediateLiveCareViewModel.immediateLiveCareSelectedClinic.serviceID.toString(),
isScheduleLiveCare: "false",
appointmentDate: DateUtil.convertDateToString(DateTime.now()),
appointmentNo: "0",
doctorID: "0",
transactionID: transID,
amount: myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString(),
fileNumber: appState.getAuthenticatedUser()!.patientId.toString(),
orderDescription: "LiveCare Payment",
serviceID: ServiceTypeEnum.appointmentPayment.getIdFromServiceEnum().toString(),
projectID: (ApiConsts.appEnvironmentType == AppEnvironmentTypeEnum.uat ? 15 : 12).toString(),
onSuccess: (PaytabsTransactionResponseModel transactionData) async {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr());
await immediateLiveCareViewModel.addNewCallRequestForImmediateLiveCare(transactionData.transactionReference!);
await immediateLiveCareViewModel.getPatientLiveCareHistory();

@ -134,8 +134,28 @@ class _SelectDoctorPageState extends State<SelectDoctorPage> {
isLoading: bookAppointmentsVM.isClinicsListLoading,
doctorsListResponseModel: bookAppointmentsVM.liveCareDoctorsList[index],
bookAppointmentsViewModel: bookAppointmentsViewModel,
).onPress(() {
// onLiveCareClinicSelected(bookAppointmentsVM.liveCareClinicsList[index]);
).onPress(() async {
bookAppointmentsVM.setSelectedDoctor(bookAppointmentsVM.liveCareDoctorsList[index]);
LoaderBottomSheet.showLoader();
await bookAppointmentsVM.getDoctorProfile(onSuccess: (dynamic respData) {
LoaderBottomSheet.hideLoader();
Navigator.of(context).push(
CustomPageRoute(
page: DoctorProfilePage(
isDoctorAllowedToBook: true,
),
),
);
}, onError: (err) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: err),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
}),
),
),

@ -138,12 +138,30 @@ class _WaitingAppointmentPaymentPageState extends State<WaitingAppointmentPaymen
).paddingSymmetrical(24.h, 0.h).onPress(() async {
selectedPaymentMethod = "MADA";
if (appState.isPaytabsEnabled) {
transID = Utils.getAppointmentTransID(
bookAppointmentsViewModel.selectedDoctor.projectID!,
bookAppointmentsViewModel.selectedDoctor.clinicID!,
bookAppointmentsViewModel.selectedDoctor.doctorID!,
);
paytabsViewModel.setPaymentConfiguration(
"Appointment Payment",
bookAppointmentsViewModel.patientWalkInAppointmentShareResponseModel!.patientShareWithTax!,
);
paytabsViewModel.startCardPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.bookingWaitingAppointment.tr(context: context));
paytabsViewModel.startCardPayment(
paymentOption: selectedPaymentMethod,
liveServiceID: "0",
isScheduleLiveCare: "false",
appointmentDate: DateUtil.convertDateToString(DateTime.now()),
appointmentNo: DateTime.now().millisecondsSinceEpoch.toString(),
doctorID: bookAppointmentsViewModel.selectedDoctor.doctorID.toString(),
transactionID: transID,
amount: myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString(),
fileNumber: appState.getAuthenticatedUser()!.patientId.toString(),
orderDescription: "Appointment Payment",
serviceID: ServiceTypeEnum.appointmentPayment.getIdFromServiceEnum().toString(),
projectID: bookAppointmentsViewModel.selectedDoctor.projectID.toString(),
onSuccess: (PaytabsTransactionResponseModel transactionData) async {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.bookingWaitingAppointment.tr(context: context));
await bookAppointmentsViewModel.insertSpecificAppointmentForWalkIn(onSuccess: (val) async {
String appointmentNo = val.data['AppointmentNo'].toString();
await myAppointmentsViewModel.createAdvancePayment(
@ -289,12 +307,31 @@ class _WaitingAppointmentPaymentPageState extends State<WaitingAppointmentPaymen
).paddingSymmetrical(24.h, 0.h).onPress(() async {
selectedPaymentMethod = "VISA";
if (appState.isPaytabsEnabled) {
transID = Utils.getAppointmentTransID(
bookAppointmentsViewModel.selectedDoctor.projectID!,
bookAppointmentsViewModel.selectedDoctor.clinicID!,
bookAppointmentsViewModel.selectedDoctor.doctorID!,
);
paytabsViewModel.setPaymentConfiguration(
"Appointment Payment",
bookAppointmentsViewModel.patientWalkInAppointmentShareResponseModel!.patientShareWithTax!,
);
paytabsViewModel.startCardPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.bookingWaitingAppointment.tr(context: context));
paytabsViewModel.startCardPayment(
paymentOption: selectedPaymentMethod,
liveServiceID: "0",
isScheduleLiveCare: "false",
appointmentDate: DateUtil.convertDateToString(DateTime.now()),
appointmentNo: DateTime.now().millisecondsSinceEpoch.toString(),
doctorID: bookAppointmentsViewModel.selectedDoctor.doctorID.toString(),
transactionID: transID,
amount: myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString(),
fileNumber: appState.getAuthenticatedUser()!.patientId.toString(),
orderDescription: "Appointment Payment",
serviceID: ServiceTypeEnum.appointmentPayment.getIdFromServiceEnum().toString(),
projectID: bookAppointmentsViewModel.selectedDoctor.projectID.toString(),
onSuccess: (PaytabsTransactionResponseModel transactionData) async {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.bookingWaitingAppointment.tr(context: context));
await bookAppointmentsViewModel.insertSpecificAppointmentForWalkIn(onSuccess: (val) async {
String appointmentNo = val.data['AppointmentNo'].toString();
await myAppointmentsViewModel.createAdvancePayment(

@ -167,9 +167,10 @@ class DoctorCard extends StatelessWidget {
],
),
),
(doctorsListResponseModel.isLiveCare ?? true)
? SizedBox.shrink()
: Expanded(
// (doctorsListResponseModel.isLiveCare ?? true)
// ? SizedBox.shrink()
// :
Expanded(
flex: 1,
child: Utils.buildSvgWithAssets(icon: AppAssets.doctor_profile_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown).toShimmer2(isShow: isLoading),
),

@ -124,12 +124,29 @@ class _ErOnlineCheckinPaymentPageState extends State<ErOnlineCheckinPaymentPage>
).paddingSymmetrical(24.h, 0.h).onPress(() {
selectedPaymentMethod = "MADA";
if (appState.isPaytabsEnabled) {
transID = Utils.getAdvancePaymentTransID(
emergencyServicesViewModel.selectedHospital!.iD,
appState.getAuthenticatedUser()!.patientId!,
);
paytabsViewModel.setPaymentConfiguration(
"ER Online Check-In Payment",
emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientShareWithTax!,
);
paytabsViewModel.startCardPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async {
if (emergencyServicesViewModel.isERBookAppointment) {
paytabsViewModel.startCardPayment(
paymentOption: selectedPaymentMethod,
liveServiceID: "0",
isScheduleLiveCare: "0",
appointmentDate: "",
appointmentNo: "0",
doctorID: "0",
transactionID: transID,
amount: emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientShareWithTax!.toString(),
fileNumber: appState.getAuthenticatedUser()!.patientId.toString(),
orderDescription: "ER Online Check-In Payment",
serviceID: ServiceTypeEnum.erOnlineCheckIn.getIdFromServiceEnum().toString(),
projectID: emergencyServicesViewModel.selectedHospital!.iD.toString(),
onSuccess: (PaytabsTransactionResponseModel transactionData) async {
if (emergencyServicesViewModel.isERBookAppointment) {
await emergencyServicesViewModel.ER_CreateAdvancePayment(
paymentMethodName: selectedPaymentMethod,
paymentReference: transactionData.transactionReference!,
@ -214,12 +231,29 @@ class _ErOnlineCheckinPaymentPageState extends State<ErOnlineCheckinPaymentPage>
).paddingSymmetrical(24.h, 0.h).onPress(() {
selectedPaymentMethod = "VISA";
if (appState.isPaytabsEnabled) {
transID = Utils.getAdvancePaymentTransID(
emergencyServicesViewModel.selectedHospital!.iD,
appState.getAuthenticatedUser()!.patientId!,
);
paytabsViewModel.setPaymentConfiguration(
"ER Online Check-In Payment",
emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientShareWithTax!,
);
paytabsViewModel.startCardPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async {
if (emergencyServicesViewModel.isERBookAppointment) {
paytabsViewModel.startCardPayment(
paymentOption: selectedPaymentMethod,
liveServiceID: "0",
isScheduleLiveCare: "0",
appointmentDate: "",
appointmentNo: "0",
doctorID: "0",
transactionID: transID,
amount: emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientShareWithTax!.toString(),
fileNumber: appState.getAuthenticatedUser()!.patientId.toString(),
orderDescription: "ER Online Check-In Payment",
serviceID: ServiceTypeEnum.erOnlineCheckIn.getIdFromServiceEnum().toString(),
projectID: emergencyServicesViewModel.selectedHospital!.iD.toString(),
onSuccess: (PaytabsTransactionResponseModel transactionData) async {
if (emergencyServicesViewModel.isERBookAppointment) {
await emergencyServicesViewModel.ER_CreateAdvancePayment(
paymentMethodName: selectedPaymentMethod,
paymentReference: transactionData.transactionReference!,

@ -113,9 +113,23 @@ class _WalletPaymentConfirmPageState extends State<WalletPaymentConfirmPage> {
).paddingSymmetrical(24.h, 0.h).onPress(() async {
selectedPaymentMethod = "MADA";
if (appState.isPaytabsEnabled) {
transID = Utils.getAdvancePaymentTransID(habibWalletVM.selectedHospital!.iD!, int.parse(habibWalletVM.fileNumber));
paytabsViewModel.setPaymentConfiguration("Advance Payment", habibWalletVM.walletRechargeAmount);
paytabsViewModel.startCardPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async {
LoaderBottomSheet.showLoader();
paytabsViewModel.startCardPayment(
paymentOption: selectedPaymentMethod,
liveServiceID: "0",
isScheduleLiveCare: "false",
appointmentDate: DateUtil.convertDateToString(DateTime.now()),
appointmentNo: "0",
doctorID: "0",
transactionID: transID,
amount: habibWalletVM.walletRechargeAmount.toString(),
fileNumber: habibWalletVM.fileNumber.toString(),
orderDescription: "Advance Payment",
serviceID: ServiceTypeEnum.advancePayment.getIdFromServiceEnum().toString(),
projectID: habibWalletVM.selectedHospital!.iD!.toString(),
onSuccess: (PaytabsTransactionResponseModel transactionData) async {
LoaderBottomSheet.showLoader();
await habibWalletVM.HISCreateAdvancePayment(
paymentMethodName: selectedPaymentMethod,
paidAmount: habibWalletVM.walletRechargeAmount,
@ -295,9 +309,23 @@ class _WalletPaymentConfirmPageState extends State<WalletPaymentConfirmPage> {
).paddingSymmetrical(24.h, 0.h).onPress(() async {
selectedPaymentMethod = "VISA";
if (appState.isPaytabsEnabled) {
transID = Utils.getAdvancePaymentTransID(habibWalletVM.selectedHospital!.iD!, int.parse(habibWalletVM.fileNumber));
paytabsViewModel.setPaymentConfiguration("Advance Payment", habibWalletVM.walletRechargeAmount);
paytabsViewModel.startCardPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async {
LoaderBottomSheet.showLoader();
paytabsViewModel.startCardPayment(
paymentOption: selectedPaymentMethod,
liveServiceID: "0",
isScheduleLiveCare: "false",
appointmentDate: DateUtil.convertDateToString(DateTime.now()),
appointmentNo: "0",
doctorID: "0",
transactionID: transID,
amount: habibWalletVM.walletRechargeAmount.toString(),
fileNumber: habibWalletVM.fileNumber.toString(),
orderDescription: "Advance Payment",
serviceID: ServiceTypeEnum.advancePayment.getIdFromServiceEnum().toString(),
projectID: habibWalletVM.selectedHospital!.iD!.toString(),
onSuccess: (PaytabsTransactionResponseModel transactionData) async {
LoaderBottomSheet.showLoader();
await habibWalletVM.HISCreateAdvancePayment(
paymentMethodName: selectedPaymentMethod,
paidAmount: habibWalletVM.walletRechargeAmount,

@ -1,6 +1,7 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:get_it/get_it.dart';
import 'package:hmg_patient_app_new/core/api_consts.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_export.dart';
@ -13,8 +14,10 @@ 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/blood_donation/blood_donation_view_model.dart';
import 'package:hmg_patient_app_new/features/book_appointments/book_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/emergency_services/emergency_services_view_model.dart';
import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart';
import 'package:hmg_patient_app_new/features/hmg_services/models/ui_models/hmg_services_component_model.dart';
import 'package:hmg_patient_app_new/features/hospital/hospital_selection_view_model.dart';
import 'package:hmg_patient_app_new/features/medical_file/medical_file_view_model.dart';
import 'package:hmg_patient_app_new/features/water_monitor/water_monitor_view_model.dart';
import 'package:hmg_patient_app_new/features/weather/weather_view_model.dart';
@ -45,6 +48,7 @@ import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart';
import '../../core/dependencies.dart';
import '../emergency_services/call_ambulance/widgets/HospitalBottomSheetBody.dart';
class ServicesPage extends StatefulWidget {
final bool showBackIcon;
@ -116,51 +120,51 @@ class _ServicesPageState extends State<ServicesPage> {
);
}
}),
// HmgServicesComponentModel(
// 11,
// LocaleKeys.indoorNavigation.tr(),
// "",
// AppAssets.indoor_nav_icon,
// bgColor: Color(0xff45A2F8),
// true,
// route: null,
// onTap: () {
// showCommonBottomSheetWithoutHeight(
// title: LocaleKeys.selectHospital.tr(),
// GetIt.instance<NavigationService>().navigatorKey.currentContext!,
// child: ChangeNotifierProvider(
// create: (context) => HospitalSelectionBottomSheetViewModel(getIt()),
// child: Consumer<HospitalSelectionBottomSheetViewModel>(
// builder: (_, vm, __) => HospitalBottomSheetBody(
// searchText: vm.searchController,
// displayList: vm.displayList,
// onFacilityClicked: (value) {
// vm.setSelectedFacility(value);
// vm.getDisplayList();
// },
// onHospitalClicked: (hospital) {
// Navigator.pop(GetIt.instance<NavigationService>().navigatorKey.currentContext!);
// vm.openPenguin(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: () {
// GetIt.instance<NavigationService>().navigatorKey.currentContext!.read<EmergencyServicesViewModel>().clearSearchText();
// },
// );
// },
// ),
HmgServicesComponentModel(
11,
LocaleKeys.indoorNavigation.tr(),
"",
AppAssets.indoor_nav_icon,
bgColor: Color(0xff45A2F8),
true,
route: null,
onTap: () {
showCommonBottomSheetWithoutHeight(
title: LocaleKeys.selectHospital.tr(),
GetIt.instance<NavigationService>().navigatorKey.currentContext!,
child: ChangeNotifierProvider(
create: (context) => HospitalSelectionBottomSheetViewModel(getIt()),
child: Consumer<HospitalSelectionBottomSheetViewModel>(
builder: (_, vm, __) => HospitalBottomSheetBody(
searchText: vm.searchController,
displayList: vm.displayList,
onFacilityClicked: (value) {
vm.setSelectedFacility(value);
vm.getDisplayList();
},
onHospitalClicked: (hospital) {
Navigator.pop(GetIt.instance<NavigationService>().navigatorKey.currentContext!);
vm.openPenguin(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: () {
GetIt.instance<NavigationService>().navigatorKey.currentContext!.read<EmergencyServicesViewModel>().clearSearchText();
},
);
},
),
HmgServicesComponentModel(
11, LocaleKeys.eReferralServices.tr(), "", AppAssets.eReferral, bgColor: AppColors.eReferralCardColor, true, route: null, onTap: () async {
if (getIt.get<AppState>().isAuthenticated) {

@ -511,8 +511,8 @@ class _LandingPageState extends State<LandingPage> {
).paddingSymmetrical(24.h, 0.h).onPress(() {
myAppointmentsViewModel.onTabChange(0);
myAppointmentsViewModel.updateListWRTTab(0);
Navigator.of(context).push(CustomPageRoute(page: MyAppointmentsPage()));
Navigator.of(context).push(CustomPageRoute(page: MyAppointmentsPage()));
// getIt.get<PayTabsViewModel>().setPaymentConfiguration("Appointment Payment", 15.5);
// getIt.get<PayTabsViewModel>().startCardPayment();
}),

@ -29,11 +29,19 @@ class OrganSelectorPage extends StatefulWidget {
State<OrganSelectorPage> createState() => _OrganSelectorPageState();
}
class _OrganSelectorPageState extends State<OrganSelectorPage> {
class _OrganSelectorPageState extends State<OrganSelectorPage> with SingleTickerProviderStateMixin {
static const double _expandedBodyZoomFactor = 1.08;
static const double _expandedSheetHeightFactor = 0.3;
static const bool _enableTutorialOverlay = false;
static const bool _enableSwipeToFlip = false;
late final AppState _appState;
late final DialogService dialogService;
late final CacheService cacheService;
late final AnimationController _introZoomHintController;
late final Animation<double> _introZoomHintAnimation;
bool _showTutorial = false;
bool _hasPlayedIntroZoomHint = false;
@override
void initState() {
@ -41,10 +49,52 @@ class _OrganSelectorPageState extends State<OrganSelectorPage> {
_appState = getIt.get<AppState>();
dialogService = getIt<DialogService>();
cacheService = getIt<CacheService>();
_introZoomHintController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 2000),
)..addListener(() {
if (mounted) setState(() {});
});
_introZoomHintAnimation = TweenSequence<double>([
TweenSequenceItem(
tween: Tween<double>(begin: 1.0, end: 1.25),
weight: 45,
),
TweenSequenceItem(
tween: Tween<double>(begin: 1.25, end: 1.0),
weight: 55,
),
]).animate(CurvedAnimation(
parent: _introZoomHintController,
curve: Curves.easeInOut,
));
_checkAndShowTutorial();
_playIntroZoomHint();
}
@override
void dispose() {
_introZoomHintController.dispose();
super.dispose();
}
Future<void> _playIntroZoomHint() async {
if (_hasPlayedIntroZoomHint) return;
_hasPlayedIntroZoomHint = true;
await Future.delayed(const Duration(milliseconds: 700));
if (!mounted) return;
_introZoomHintController.forward(from: 0);
}
Future<void> _checkAndShowTutorial() async {
if (!_enableTutorialOverlay) {
_showTutorial = false;
return;
}
// final hasSeenTutorial = cacheService.getBool(key: CacheConst.organSelectorTutorialShown) ?? false;
// if (!hasSeenTutorial) {
// Show tutorial after a short delay to ensure the screen is fully built
@ -118,7 +168,11 @@ class _OrganSelectorPageState extends State<OrganSelectorPage> {
Expanded(
child: Stack(
children: [
_buildBodyViewer(viewModel),
SafeArea(
top: false,
bottom: false,
child: _buildBodyViewer(viewModel),
),
_buildViewToggleButtons(viewModel),
// _buildViewZoomButtons(viewModel),
_buildBottomSheet(viewModel),
@ -131,7 +185,7 @@ class _OrganSelectorPageState extends State<OrganSelectorPage> {
),
),
// Tutorial overlay
if (_showTutorial)
if (_enableTutorialOverlay && _showTutorial)
PinchZoomTutorialOverlay(
onComplete: _onTutorialComplete,
),
@ -152,7 +206,13 @@ class _OrganSelectorPageState extends State<OrganSelectorPage> {
height: 24.h,
),
padding: EdgeInsetsDirectional.only(start: 12, end: 12),
onPressed: () => Navigator.pop(context),
onPressed: () {
// Clear selected organs and sheet status when going back
final viewModel = context.read<SymptomsCheckerViewModel>();
viewModel.clearAllSelections();
viewModel.setBottomSheetExpanded(false);
Navigator.pop(context);
},
highlightColor: Colors.transparent,
),
),
@ -175,45 +235,82 @@ class _OrganSelectorPageState extends State<OrganSelectorPage> {
Widget _buildBodyViewer(SymptomsCheckerViewModel viewModel) {
return GestureDetector(
onHorizontalDragEnd: (details) {
// Swipe left or right to toggle view
if (details.primaryVelocity != null) {
if (details.primaryVelocity! < -200 || details.primaryVelocity! > 200) {
viewModel.toggleView();
}
}
},
child: Padding(
padding: EdgeInsets.fromLTRB(16.h, 16.h, 16.h, 60.h),
child: AnimatedSwitcher(
duration: const Duration(milliseconds: 600),
transitionBuilder: (child, animation) => _build3DFlipTransition(child, animation),
switchInCurve: Curves.easeInOut,
switchOutCurve: Curves.easeInOut,
child: Builder(
key: ValueKey<BodyView>(viewModel.currentView),
builder: (context) {
// Detect female gender from viewModel; allow Arabic value as fallback
final bool isFemale =
(viewModel.selectedGender != null && (viewModel.selectedGender!.toLowerCase() == 'female' || viewModel.selectedGender == 'أنثى'));
final String bodyAsset = viewModel.currentView == BodyView.front
? (isFemale ? AppAssets.fullBodyFrontFemale : AppAssets.fullBodyFrontMale)
: (isFemale ? AppAssets.fullBodyBackFemale : AppAssets.fullBodyBackMale);
return InteractiveBodyWidget(
bodyImageAsset: bodyAsset,
organs: viewModel.currentOrgans,
selectedOrganIds: viewModel.selectedOrganIds,
onOrganTap: viewModel.toggleOrganSelection,
isBodyHidden: viewModel.isBodyHidden,
tooltipOrganId: viewModel.tooltipOrganId,
isArabic: _appState.isArabic(),
zoomScale: viewModel.currentZoomScale,
);
onHorizontalDragEnd: !_enableSwipeToFlip
? null
: (details) {
// Swipe left or right to toggle view
if (details.primaryVelocity != null) {
if (details.primaryVelocity! < -200 || details.primaryVelocity! > 200) {
viewModel.toggleView();
}
}
},
),
),
child: LayoutBuilder(
builder: (context, constraints) {
final bool isExpanded = viewModel.isBottomSheetExpanded;
final double screenHeight = MediaQuery.of(context).size.height;
final double extraBottomInset = isExpanded ? screenHeight * _expandedSheetHeightFactor : 0;
final bool isUserZoomedIn = viewModel.currentZoomScale > 1.01;
Widget buildInteractiveBody({required bool useExpandedZoom}) {
return AnimatedSwitcher(
duration: const Duration(milliseconds: 600),
transitionBuilder: (child, animation) => _build3DFlipTransition(child, animation),
switchInCurve: Curves.easeInOut,
switchOutCurve: Curves.easeInOut,
child: Builder(
key: ValueKey<BodyView>(viewModel.currentView),
builder: (context) {
final bool isFemale = (viewModel.selectedGender != null && (viewModel.selectedGender!.toLowerCase() == 'female' || viewModel.selectedGender == 'أنثى'));
final double effectiveZoomScale = viewModel.currentZoomScale * _introZoomHintAnimation.value;
final String bodyAsset = viewModel.currentView == BodyView.front
? (isFemale ? AppAssets.fullBodyFrontFemale : AppAssets.fullBodyFrontMale)
: (isFemale ? AppAssets.fullBodyBackFemale : AppAssets.fullBodyBackMale);
final body = InteractiveBodyWidget(
bodyImageAsset: bodyAsset,
organs: viewModel.currentOrgans,
selectedOrganIds: viewModel.selectedOrganIds,
onOrganTap: viewModel.toggleOrganSelection,
isBodyHidden: viewModel.isBodyHidden,
tooltipOrganId: viewModel.tooltipOrganId,
isArabic: _appState.isArabic(),
zoomScale: effectiveZoomScale,
);
return body;
},
),
);
}
if (!isExpanded) {
return Padding(
padding: EdgeInsets.fromLTRB(16.h, 16.h, 16.h, 60.h),
child: buildInteractiveBody(useExpandedZoom: false),
);
}
return SingleChildScrollView(
physics: isExpanded && !isUserZoomedIn ? const ClampingScrollPhysics() : const NeverScrollableScrollPhysics(),
child: ConstrainedBox(
constraints: BoxConstraints(
minHeight: constraints.maxHeight + extraBottomInset,
),
child: Padding(
padding: EdgeInsets.only(
bottom: extraBottomInset,
top: 24.h,
),
child: Padding(
padding: EdgeInsets.fromLTRB(16.h, 16.h, 16.h, 60.h),
child: buildInteractiveBody(useExpandedZoom: true),
),
),
),
);
},
),
);
}
@ -408,37 +505,73 @@ class _OrganSelectorPageState extends State<OrganSelectorPage> {
}
Widget _buildExpandCollapseButton(SymptomsCheckerViewModel viewModel) {
final organCount = viewModel.selectedOrgans.length;
final showBadge = !viewModel.isBottomSheetExpanded && organCount > 0;
return PositionedDirectional(
end: 24.w,
top: -24.h,
child: GestureDetector(
onTap: viewModel.toggleBottomSheet,
behavior: HitTestBehavior.opaque,
child: Container(
child: SizedBox(
width: 70.w,
height: 70.h,
alignment: Alignment.center,
child: Container(
width: 48.w,
height: 48.h,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 11.r,
),
child: Center(
child: Transform.flip(
flipX: _appState.isArabic(),
child: AnimatedRotation(
duration: const Duration(milliseconds: 300),
turns: viewModel.isBottomSheetExpanded ? 0.25 : -0.25,
child: Utils.buildSvgWithAssets(
icon: AppAssets.arrowRight,
width: 25.w,
height: 25.h,
child: Stack(
clipBehavior: Clip.none,
children: [
Align(
alignment: Alignment.center,
child: Container(
width: 48.w,
height: 48.h,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 11.r,
),
child: Center(
child: Transform.flip(
flipX: _appState.isArabic(),
child: AnimatedRotation(
duration: const Duration(milliseconds: 300),
turns: viewModel.isBottomSheetExpanded ? 0.25 : -0.25,
child: Utils.buildSvgWithAssets(
icon: AppAssets.arrowRight,
width: 25.w,
height: 25.h,
),
),
),
),
),
),
),
if (showBadge)
PositionedDirectional(
top: 8.h,
end: 8.w,
child: Container(
width: 22.w,
height: 22.h,
alignment: Alignment.center,
decoration: BoxDecoration(
color: AppColors.primaryRedColor,
shape: BoxShape.circle,
border: Border.all(
color: AppColors.whiteColor,
width: 1.5,
),
),
child: Text(
'$organCount',
style: TextStyle(
color: AppColors.whiteColor,
fontSize: 10.f,
fontWeight: FontWeight.w700,
),
),
),
),
],
),
),
),

@ -1,3 +1,4 @@
import 'dart:developer';
import 'dart:ui' as ui;
import 'package:flutter/material.dart';
@ -7,7 +8,6 @@ import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/organ_model.dart';
import 'package:hmg_patient_app_new/presentation/symptoms_checker/widgets/organ_dot_widget.dart';
import 'package:hmg_patient_app_new/presentation/symptoms_checker/widgets/organ_tooltip_widget.dart';
import 'package:vector_math/vector_math_64.dart' show Vector3;
class InteractiveBodyWidget extends StatefulWidget {
final String bodyImageAsset;
@ -63,9 +63,12 @@ class _InteractiveBodyWidgetState extends State<InteractiveBodyWidget> {
@override
void dispose() {
_transformationController.dispose();
currentZoom = 0.0;
super.dispose();
}
double currentZoom = 0.0;
Future<void> _loadImageAspectRatio() async {
final ByteData data = await rootBundle.load(widget.bodyImageAsset);
final ui.Codec codec = await ui.instantiateImageCodec(data.buffer.asUint8List());
@ -77,17 +80,16 @@ class _InteractiveBodyWidgetState extends State<InteractiveBodyWidget> {
_imageAspectRatio = image.width / image.height;
});
}
_transformationController.addListener(() {
currentZoom = _transformationController.value.getMaxScaleOnAxis();
setState(() {});
});
}
void _updateZoom(double scale) {
// Get current translation
final currentTransform = _transformationController.value;
final currentTranslation = currentTransform.getTranslation();
// Create new transformation with updated scale while preserving translation
final newTransform = Matrix4.identity()
..setTranslation(currentTranslation)
..scaleByVector3(Vector3(scale, scale, 1.0));
// Keep programmatic zoom centered to avoid drifting the body off-screen.
final newTransform = Matrix4.identity()..scaleByDouble(scale, scale, 1.0, 1.0);
_transformationController.value = newTransform;
}
@ -103,8 +105,12 @@ class _InteractiveBodyWidgetState extends State<InteractiveBodyWidget> {
return Center(
child: InteractiveViewer(
transformationController: _transformationController,
minScale: 0.5,
maxScale: 4.0,
alignment: Alignment.center,
panEnabled: true,
scaleEnabled: true,
boundaryMargin: currentZoom > 1.2 ? EdgeInsets.all(200.h) : EdgeInsets.zero,
minScale: 1.0,
maxScale: 9.0,
clipBehavior: Clip.none,
child: AspectRatio(
aspectRatio: _imageAspectRatio!,
@ -135,7 +141,7 @@ class _InteractiveBodyWidgetState extends State<InteractiveBodyWidget> {
// Organ dots
...widget.organs.map((organ) {
final isSelected = widget.selectedOrganIds.contains(organ.id);
final dotSize = 16.0;
final dotSize = 18.0;
final leftPos = (organ.position.x * imageConstraints.maxWidth) - (dotSize / 2);
final topPos = (organ.position.y * imageConstraints.maxHeight) - (dotSize / 2);

@ -132,12 +132,29 @@ class _AncillaryOrderPaymentPageState extends State<AncillaryOrderPaymentPage> {
if (!todoVM.isProcessingPayment) {
selectedPaymentMethod = "MADA";
if (appState.isPaytabsEnabled) {
transID = Utils.getAdvancePaymentTransID(
widget.projectID,
appState.getAuthenticatedUser()!.patientId!,
);
paytabsViewModel.setPaymentConfiguration(
"Ancillary Orders Payment",
widget.totalAmount,
);
paytabsViewModel.startCardPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async {
final paymentAmount = widget.totalAmount;
paytabsViewModel.startCardPayment(
paymentOption: selectedPaymentMethod,
liveServiceID: "0",
isScheduleLiveCare: "false",
appointmentDate: DateUtil.convertDateToString(DateTime.now()),
appointmentNo: "0",
doctorID: "0",
transactionID: transID,
amount: widget.totalAmount.toString(),
fileNumber: appState.getAuthenticatedUser()!.patientId!.toString(),
orderDescription: "Ancillary Orders Payment",
serviceID: ServiceTypeEnum.advancePayment.getIdFromServiceEnum().toString(),
projectID: widget.projectID.toString(),
onSuccess: (PaytabsTransactionResponseModel transactionData) async {
final paymentAmount = widget.totalAmount;
final fortId = transactionData.transactionReference;
final paymentMethod = selectedPaymentMethod;
@ -254,11 +271,27 @@ class _AncillaryOrderPaymentPageState extends State<AncillaryOrderPaymentPage> {
if (!todoVM.isProcessingPayment) {
selectedPaymentMethod = "VISA";
if (appState.isPaytabsEnabled) {
transID = Utils.getAdvancePaymentTransID(
widget.projectID,
appState.getAuthenticatedUser()!.patientId!,
);
paytabsViewModel.setPaymentConfiguration(
"Ancillary Orders Payment",
widget.totalAmount,
);
paytabsViewModel.startCardPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async {
paytabsViewModel.startCardPayment(paymentOption: selectedPaymentMethod,
liveServiceID: "0",
isScheduleLiveCare: "false",
appointmentDate: DateUtil.convertDateToString(DateTime.now()),
appointmentNo: "0",
doctorID: "0",
transactionID: transID,
amount: widget.totalAmount.toString(),
fileNumber: appState.getAuthenticatedUser()!.patientId!.toString(),
orderDescription: "Ancillary Orders Payment",
serviceID: ServiceTypeEnum.advancePayment.getIdFromServiceEnum().toString(),
projectID: widget.projectID.toString(),
onSuccess: (PaytabsTransactionResponseModel transactionData) async {
final paymentAmount = widget.totalAmount;
final fortId = transactionData.transactionReference;
final paymentMethod = selectedPaymentMethod;

@ -23,7 +23,8 @@ abstract class DialogService {
Future<void> showExceptionBottomSheet({required String message, required Function() onOkPressed, Function()? onCancelPressed});
Future<void> showCommonBottomSheetWithoutH({String? label, required String message, String? okLabel, String? cancelLabel, bool isConfirmButton = false, required Function() onOkPressed, Function()? onCancelPressed});
Future<void> showCommonBottomSheetWithoutH(
{String? label, required String message, String? okLabel, String? cancelLabel, bool isConfirmButton = false, required Function() onOkPressed, Function()? onCancelPressed});
Future<void> showSuccessBottomSheetWithoutH({String? label, required String message, required Function() onOkPressed, Function()? onCancelPressed});
@ -119,21 +120,15 @@ class DialogServiceImp implements DialogService {
}
@override
Future<void> showCommonBottomSheetWithoutH({String? label, required String message, String? okLabel, String? cancelLabel, bool isConfirmButton = false, required Function() onOkPressed, Function()? onCancelPressed}) async {
Future<void> showCommonBottomSheetWithoutH(
{String? label, required String message, String? okLabel, String? cancelLabel, bool isConfirmButton = false, required Function() onOkPressed, Function()? onCancelPressed}) async {
final context = navigationService.navigatorKey.currentContext;
if (context == null) return;
showCommonBottomSheetWithoutHeight(
context,
title: label ?? "",
child: exceptionBottomSheetWidget(
context: context,
message: message,
okLabel: okLabel,
cancelLabel: cancelLabel,
onOkPressed: onOkPressed,
onCancelPressed: onCancelPressed,
isConfirmButton: isConfirmButton
),
context: context, message: message, okLabel: okLabel, cancelLabel: cancelLabel, onOkPressed: onOkPressed, onCancelPressed: onCancelPressed, isConfirmButton: isConfirmButton),
callBackFunc: () {},
);
}
@ -240,7 +235,8 @@ class DialogServiceImp implements DialogService {
}
}
Widget exceptionBottomSheetWidget({required BuildContext context, required String message, String? okLabel, String? cancelLabel, bool isConfirmButton = false, required Function() onOkPressed, Function()? onCancelPressed}) {
Widget exceptionBottomSheetWidget(
{required BuildContext context, required String message, String? okLabel, String? cancelLabel, bool isConfirmButton = false, required Function() onOkPressed, Function()? onCancelPressed}) {
return Column(
children: [
(message).toText16(isBold: false, color: AppColors.textColor),

@ -1,3 +1,4 @@
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
@ -15,10 +16,7 @@ class AppTheme {
visualDensity: VisualDensity.adaptivePlatformDensity,
brightness: Brightness.light,
pageTransitionsTheme: const PageTransitionsTheme(
builders: {
TargetPlatform.android: ZoomPageTransitionsBuilder(),
TargetPlatform.iOS: CupertinoPageTransitionsBuilder()
},
builders: {TargetPlatform.android: ZoomPageTransitionsBuilder(), TargetPlatform.iOS: CupertinoPageTransitionsBuilder()},
),
hintColor: Colors.grey[400],
disabledColor: Colors.grey[300],
@ -51,10 +49,7 @@ class AppTheme {
visualDensity: VisualDensity.adaptivePlatformDensity,
brightness: Brightness.dark,
pageTransitionsTheme: const PageTransitionsTheme(
builders: {
TargetPlatform.android: ZoomPageTransitionsBuilder(),
TargetPlatform.iOS: CupertinoPageTransitionsBuilder()
},
builders: {TargetPlatform.android: ZoomPageTransitionsBuilder(), TargetPlatform.iOS: CupertinoPageTransitionsBuilder()},
),
hintColor: Colors.grey[600],
disabledColor: Colors.grey[700],
@ -63,8 +58,7 @@ class AppTheme {
scaffoldBackgroundColor: AppColors.dark.scaffoldBgColor,
highlightColor: Colors.grey[800]!.withOpacity(0.4),
splashColor: Colors.transparent,
bottomSheetTheme: BottomSheetThemeData(
backgroundColor: Colors.black.withOpacity(0)),
bottomSheetTheme: BottomSheetThemeData(backgroundColor: Colors.black.withOpacity(0)),
floatingActionButtonTheme: const FloatingActionButtonThemeData(highlightElevation: 2, disabledElevation: 0, elevation: 2),
appBarTheme: AppBarTheme(
color: AppColors.dark.scaffoldBgColor,

@ -2,8 +2,8 @@ name: hmg_patient_app_new
description: "New HMG Patient App"
publish_to: 'none' # Remove this line if you wish to publish to pub.dev
#version: 0.0.40+41
version: 0.0.11+1
#version: 0.0.42+43
version: 0.0.12+1
environment:
sdk: ">=3.6.0 <4.0.0"

Loading…
Cancel
Save