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

@ -5,7 +5,7 @@ import 'package:hmg_patient_app_new/core/enums.dart';
class ApiConsts { class ApiConsts {
static const maxSmallScreen = 660; 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 // static String baseUrl = 'https://uat.hmgwebservices.com/'; // HIS API URL UAT

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

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

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

@ -11,17 +11,29 @@ import 'package:flutter_paytabs_bridge/PaymentSdkTransactionType.dart';
import 'package:flutter_paytabs_bridge/flutter_paytabs_bridge.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/api_consts.dart';
import 'package:hmg_patient_app_new/core/app_state.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/enums.dart';
import 'package:hmg_patient_app_new/core/paytabs_config.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/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 { class PayTabsViewModel extends ChangeNotifier {
late PaymentSdkConfigurationDetails paymentConfiguration; late PaymentSdkConfigurationDetails paymentConfiguration;
final AppState appState; final AppState appState;
final PayfortRepo payfortRepo;
final ErrorHandlerService errorHandlerService;
late PaytabsTransactionResponseModel paytabsTransactionResponseModel; late PaytabsTransactionResponseModel paytabsTransactionResponseModel;
PayTabsViewModel({ PayTabsViewModel({
required this.appState, required this.appState,
required this.payfortRepo,
required this.errorHandlerService,
}); });
setPaymentConfiguration(String paymentDescription, num amount, {bool isAddCard = false}) { setPaymentConfiguration(String paymentDescription, num amount, {bool isAddCard = false}) {
@ -152,6 +164,42 @@ class PayTabsViewModel extends ChangeNotifier {
notifyListeners(); 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 { startApplePayPayment({Function(PaytabsTransactionResponseModel)? onSuccess, Function(String)? onError}) async {
bool _callbackFired = false; // prevent duplicate callback invocations 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 bool _callbackFired = false; // prevent duplicate callback invocations
FlutterPaytabsBridge.startCardPayment(paymentConfiguration, (event) { try {
if (_callbackFired) return; // ignore any subsequent callback calls LoaderBottomSheet.showLoader();
final transactionDetails = event["data"] as Map<Object?, Object?>?; // Create PayfortRequestInsertModel (same as Payfort implementation)
if (event["status"] == "success") { PayfortRequestInsertModel payfortRequestInsertModel = PayfortRequestInsertModel();
_callbackFired = true;
// Cast Map<Object?, Object?> to Map<String, dynamic> payfortRequestInsertModel.commandType = "SALE";
final data = Map<String, dynamic>.from(event["data"] as Map); payfortRequestInsertModel.amount = num.parse(amount);
paytabsTransactionResponseModel = PaytabsTransactionResponseModel.fromJson(data); payfortRequestInsertModel.customerEmail = getIt.get<AppState>().getAuthenticatedUser()!.emailAddress;
if (paytabsTransactionResponseModel.isSuccess!) { payfortRequestInsertModel.orderDescription = orderDescription;
onSuccess!(paytabsTransactionResponseModel); payfortRequestInsertModel.serviceID = serviceID;
} else { payfortRequestInsertModel.paymentOption = paymentOption;
// Transaction was processed but failed payfortRequestInsertModel.clientRequestID = transactionID;
final reason = payfortRequestInsertModel.projectID = projectID;
transactionDetails?["payResponseReturn"] ?? transactionDetails?["responseMessage"] ?? transactionDetails?["message"] ?? "Unknown error"; payfortRequestInsertModel.fileNumber = getIt.get<AppState>().getAuthenticatedUser()!.patientId.toString();
final responseCode = transactionDetails?["responseCode"] ?? ""; payfortRequestInsertModel.customerName = "${getIt.get<AppState>().getAuthenticatedUser()!.firstName} ${getIt.get<AppState>().getAuthenticatedUser()!.lastName}";
final errorMessage = payfortRequestInsertModel.currency = getIt.get<AppState>().getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED";
responseCode.toString().isNotEmpty ? "Transaction failed (Code: $responseCode): $reason" : "Transaction failed: $reason"; payfortRequestInsertModel.liveServiceID = liveServiceID;
onError!(errorMessage); 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; } catch (error) {
final errorMessage = event["message"] ?? "An error occurred"; debugPrint("PayTabs Payment Error: $error");
debugPrint("Error occurred in transaction: $errorMessage"); LoaderBottomSheet.hideLoader();
debugPrint("Full error event: $event"); if (onError != null) {
onError!(errorMessage); onError("Payment initialization failed: $error");
} else if (event["status"] == "event") {
// Intermediate SDK events do NOT mark _callbackFired, just log
final eventMessage = event["message"] ?? "Event occurred";
debugPrint("Event occurred: $eventMessage");
} }
}); }
} }
startAlternatePayment({Function(PaytabsTransactionResponseModel)? onSuccess, Function(String)? onError}) { startAlternatePayment({Function(PaytabsTransactionResponseModel)? onSuccess, Function(String)? onError}) {

@ -139,12 +139,30 @@ class _AppointmentPaymentPageState extends State<AppointmentPaymentPage> {
).paddingSymmetrical(24.h, 0.h).onPress(() async { ).paddingSymmetrical(24.h, 0.h).onPress(() async {
selectedPaymentMethod = "MADA"; selectedPaymentMethod = "MADA";
if (appState.isPaytabsEnabled) { if (appState.isPaytabsEnabled) {
transID = Utils.getAppointmentTransID(
widget.patientAppointmentHistoryResponseModel.projectID,
widget.patientAppointmentHistoryResponseModel.clinicID,
widget.patientAppointmentHistoryResponseModel.appointmentNo,
);
paytabsViewModel.setPaymentConfiguration( paytabsViewModel.setPaymentConfiguration(
"Appointment Payment", "Appointment Payment",
num.parse(myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString()), num.parse(myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString()),
); );
paytabsViewModel.startCardPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async { paytabsViewModel.startCardPayment(
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr(context: context)); 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( await myAppointmentsViewModel.createAdvancePayment(
paymentMethodName: selectedPaymentMethod, paymentMethodName: selectedPaymentMethod,
projectID: widget.patientAppointmentHistoryResponseModel.projectID, projectID: widget.patientAppointmentHistoryResponseModel.projectID,
@ -362,12 +380,30 @@ class _AppointmentPaymentPageState extends State<AppointmentPaymentPage> {
).paddingSymmetrical(24.h, 0.h).onPress(() async { ).paddingSymmetrical(24.h, 0.h).onPress(() async {
selectedPaymentMethod = "VISA"; selectedPaymentMethod = "VISA";
if (appState.isPaytabsEnabled) { if (appState.isPaytabsEnabled) {
transID = Utils.getAppointmentTransID(
widget.patientAppointmentHistoryResponseModel.projectID,
widget.patientAppointmentHistoryResponseModel.clinicID,
widget.patientAppointmentHistoryResponseModel.appointmentNo,
);
paytabsViewModel.setPaymentConfiguration( paytabsViewModel.setPaymentConfiguration(
"Appointment Payment", "Appointment Payment",
num.parse(myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString()), num.parse(myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString()),
); );
paytabsViewModel.startCardPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async { paytabsViewModel.startCardPayment(
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr(context: context)); 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( await myAppointmentsViewModel.createAdvancePayment(
paymentMethodName: selectedPaymentMethod, paymentMethodName: selectedPaymentMethod,
projectID: widget.patientAppointmentHistoryResponseModel.projectID, 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)), Transform.flip(flipX: appState.isArabic(), child: Utils.buildSvgWithAssets(icon: AppAssets.forward_arrow_icon, iconColor: AppColors.textColor, width: 40.h, height: 40.h)),
], ],
).onPress(() { ).onPress(() {
Clarity.sendCustomEvent('Select_Region_Page');
bookAppointmentsViewModel.setProjectID(null); bookAppointmentsViewModel.setProjectID(null);
openRegionListBottomSheet(context, RegionBottomSheetType.FOR_REGION); 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:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart';
@ -30,6 +31,7 @@ class DoctorProfilePage extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
Clarity.setCurrentScreenName('Doctor Profile Page');
final bookAppointmentsViewModel = Provider.of<BookAppointmentsViewModel>(context, listen: false); final bookAppointmentsViewModel = Provider.of<BookAppointmentsViewModel>(context, listen: false);
final appState = getIt.get<AppState>(); final appState = getIt.get<AppState>();
return Scaffold( return Scaffold(

@ -130,11 +130,29 @@ class _ImmediateLiveCarePaymentPageState extends State<ImmediateLiveCarePaymentP
).paddingSymmetrical(24.h, 0.h).onPress(() async { ).paddingSymmetrical(24.h, 0.h).onPress(() async {
selectedPaymentMethod = "MADA"; selectedPaymentMethod = "MADA";
if (appState.isPaytabsEnabled) { if (appState.isPaytabsEnabled) {
transID = Utils.getAppointmentTransID(
immediateLiveCareViewModel.immediateLiveCareSelectedClinic.serviceID!,
ApiConsts.appEnvironmentType == AppEnvironmentTypeEnum.uat ? 15 : 12,
DateTime.now().millisecondsSinceEpoch,
);
paytabsViewModel.setPaymentConfiguration( paytabsViewModel.setPaymentConfiguration(
"LiveCare Payment", "LiveCare Payment",
num.parse(immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.total ?? "0.0"), 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()); LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr());
await immediateLiveCareViewModel.addNewCallRequestForImmediateLiveCare(transactionData.transactionReference!); await immediateLiveCareViewModel.addNewCallRequestForImmediateLiveCare(transactionData.transactionReference!);
await immediateLiveCareViewModel.getPatientLiveCareHistory(); await immediateLiveCareViewModel.getPatientLiveCareHistory();
@ -281,11 +299,29 @@ class _ImmediateLiveCarePaymentPageState extends State<ImmediateLiveCarePaymentP
).paddingSymmetrical(24.h, 0.h).onPress(() async { ).paddingSymmetrical(24.h, 0.h).onPress(() async {
selectedPaymentMethod = "VISA"; selectedPaymentMethod = "VISA";
if (appState.isPaytabsEnabled) { if (appState.isPaytabsEnabled) {
transID = Utils.getAppointmentTransID(
immediateLiveCareViewModel.immediateLiveCareSelectedClinic.serviceID!,
ApiConsts.appEnvironmentType == AppEnvironmentTypeEnum.uat ? 15 : 12,
DateTime.now().millisecondsSinceEpoch,
);
paytabsViewModel.setPaymentConfiguration( paytabsViewModel.setPaymentConfiguration(
"LiveCare Payment", "LiveCare Payment",
num.parse(immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.total ?? "0.0"), 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()); LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr());
await immediateLiveCareViewModel.addNewCallRequestForImmediateLiveCare(transactionData.transactionReference!); await immediateLiveCareViewModel.addNewCallRequestForImmediateLiveCare(transactionData.transactionReference!);
await immediateLiveCareViewModel.getPatientLiveCareHistory(); await immediateLiveCareViewModel.getPatientLiveCareHistory();

@ -134,8 +134,28 @@ class _SelectDoctorPageState extends State<SelectDoctorPage> {
isLoading: bookAppointmentsVM.isClinicsListLoading, isLoading: bookAppointmentsVM.isClinicsListLoading,
doctorsListResponseModel: bookAppointmentsVM.liveCareDoctorsList[index], doctorsListResponseModel: bookAppointmentsVM.liveCareDoctorsList[index],
bookAppointmentsViewModel: bookAppointmentsViewModel, bookAppointmentsViewModel: bookAppointmentsViewModel,
).onPress(() { ).onPress(() async {
// onLiveCareClinicSelected(bookAppointmentsVM.liveCareClinicsList[index]); 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 { ).paddingSymmetrical(24.h, 0.h).onPress(() async {
selectedPaymentMethod = "MADA"; selectedPaymentMethod = "MADA";
if (appState.isPaytabsEnabled) { if (appState.isPaytabsEnabled) {
transID = Utils.getAppointmentTransID(
bookAppointmentsViewModel.selectedDoctor.projectID!,
bookAppointmentsViewModel.selectedDoctor.clinicID!,
bookAppointmentsViewModel.selectedDoctor.doctorID!,
);
paytabsViewModel.setPaymentConfiguration( paytabsViewModel.setPaymentConfiguration(
"Appointment Payment", "Appointment Payment",
bookAppointmentsViewModel.patientWalkInAppointmentShareResponseModel!.patientShareWithTax!, bookAppointmentsViewModel.patientWalkInAppointmentShareResponseModel!.patientShareWithTax!,
); );
paytabsViewModel.startCardPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async { paytabsViewModel.startCardPayment(
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.bookingWaitingAppointment.tr(context: context)); 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 { await bookAppointmentsViewModel.insertSpecificAppointmentForWalkIn(onSuccess: (val) async {
String appointmentNo = val.data['AppointmentNo'].toString(); String appointmentNo = val.data['AppointmentNo'].toString();
await myAppointmentsViewModel.createAdvancePayment( await myAppointmentsViewModel.createAdvancePayment(
@ -289,12 +307,31 @@ class _WaitingAppointmentPaymentPageState extends State<WaitingAppointmentPaymen
).paddingSymmetrical(24.h, 0.h).onPress(() async { ).paddingSymmetrical(24.h, 0.h).onPress(() async {
selectedPaymentMethod = "VISA"; selectedPaymentMethod = "VISA";
if (appState.isPaytabsEnabled) { if (appState.isPaytabsEnabled) {
transID = Utils.getAppointmentTransID(
bookAppointmentsViewModel.selectedDoctor.projectID!,
bookAppointmentsViewModel.selectedDoctor.clinicID!,
bookAppointmentsViewModel.selectedDoctor.doctorID!,
);
paytabsViewModel.setPaymentConfiguration( paytabsViewModel.setPaymentConfiguration(
"Appointment Payment", "Appointment Payment",
bookAppointmentsViewModel.patientWalkInAppointmentShareResponseModel!.patientShareWithTax!, bookAppointmentsViewModel.patientWalkInAppointmentShareResponseModel!.patientShareWithTax!,
); );
paytabsViewModel.startCardPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async { paytabsViewModel.startCardPayment(
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.bookingWaitingAppointment.tr(context: context)); 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 { await bookAppointmentsViewModel.insertSpecificAppointmentForWalkIn(onSuccess: (val) async {
String appointmentNo = val.data['AppointmentNo'].toString(); String appointmentNo = val.data['AppointmentNo'].toString();
await myAppointmentsViewModel.createAdvancePayment( await myAppointmentsViewModel.createAdvancePayment(

@ -167,9 +167,10 @@ class DoctorCard extends StatelessWidget {
], ],
), ),
), ),
(doctorsListResponseModel.isLiveCare ?? true) // (doctorsListResponseModel.isLiveCare ?? true)
? SizedBox.shrink() // ? SizedBox.shrink()
: Expanded( // :
Expanded(
flex: 1, flex: 1,
child: Utils.buildSvgWithAssets(icon: AppAssets.doctor_profile_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown).toShimmer2(isShow: isLoading), 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(() { ).paddingSymmetrical(24.h, 0.h).onPress(() {
selectedPaymentMethod = "MADA"; selectedPaymentMethod = "MADA";
if (appState.isPaytabsEnabled) { if (appState.isPaytabsEnabled) {
transID = Utils.getAdvancePaymentTransID(
emergencyServicesViewModel.selectedHospital!.iD,
appState.getAuthenticatedUser()!.patientId!,
);
paytabsViewModel.setPaymentConfiguration( paytabsViewModel.setPaymentConfiguration(
"ER Online Check-In Payment", "ER Online Check-In Payment",
emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientShareWithTax!, emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientShareWithTax!,
); );
paytabsViewModel.startCardPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async { paytabsViewModel.startCardPayment(
if (emergencyServicesViewModel.isERBookAppointment) { 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( await emergencyServicesViewModel.ER_CreateAdvancePayment(
paymentMethodName: selectedPaymentMethod, paymentMethodName: selectedPaymentMethod,
paymentReference: transactionData.transactionReference!, paymentReference: transactionData.transactionReference!,
@ -214,12 +231,29 @@ class _ErOnlineCheckinPaymentPageState extends State<ErOnlineCheckinPaymentPage>
).paddingSymmetrical(24.h, 0.h).onPress(() { ).paddingSymmetrical(24.h, 0.h).onPress(() {
selectedPaymentMethod = "VISA"; selectedPaymentMethod = "VISA";
if (appState.isPaytabsEnabled) { if (appState.isPaytabsEnabled) {
transID = Utils.getAdvancePaymentTransID(
emergencyServicesViewModel.selectedHospital!.iD,
appState.getAuthenticatedUser()!.patientId!,
);
paytabsViewModel.setPaymentConfiguration( paytabsViewModel.setPaymentConfiguration(
"ER Online Check-In Payment", "ER Online Check-In Payment",
emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientShareWithTax!, emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientShareWithTax!,
); );
paytabsViewModel.startCardPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async { paytabsViewModel.startCardPayment(
if (emergencyServicesViewModel.isERBookAppointment) { 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( await emergencyServicesViewModel.ER_CreateAdvancePayment(
paymentMethodName: selectedPaymentMethod, paymentMethodName: selectedPaymentMethod,
paymentReference: transactionData.transactionReference!, paymentReference: transactionData.transactionReference!,

@ -113,9 +113,23 @@ class _WalletPaymentConfirmPageState extends State<WalletPaymentConfirmPage> {
).paddingSymmetrical(24.h, 0.h).onPress(() async { ).paddingSymmetrical(24.h, 0.h).onPress(() async {
selectedPaymentMethod = "MADA"; selectedPaymentMethod = "MADA";
if (appState.isPaytabsEnabled) { if (appState.isPaytabsEnabled) {
transID = Utils.getAdvancePaymentTransID(habibWalletVM.selectedHospital!.iD!, int.parse(habibWalletVM.fileNumber));
paytabsViewModel.setPaymentConfiguration("Advance Payment", habibWalletVM.walletRechargeAmount); paytabsViewModel.setPaymentConfiguration("Advance Payment", habibWalletVM.walletRechargeAmount);
paytabsViewModel.startCardPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async { paytabsViewModel.startCardPayment(
LoaderBottomSheet.showLoader(); 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( await habibWalletVM.HISCreateAdvancePayment(
paymentMethodName: selectedPaymentMethod, paymentMethodName: selectedPaymentMethod,
paidAmount: habibWalletVM.walletRechargeAmount, paidAmount: habibWalletVM.walletRechargeAmount,
@ -295,9 +309,23 @@ class _WalletPaymentConfirmPageState extends State<WalletPaymentConfirmPage> {
).paddingSymmetrical(24.h, 0.h).onPress(() async { ).paddingSymmetrical(24.h, 0.h).onPress(() async {
selectedPaymentMethod = "VISA"; selectedPaymentMethod = "VISA";
if (appState.isPaytabsEnabled) { if (appState.isPaytabsEnabled) {
transID = Utils.getAdvancePaymentTransID(habibWalletVM.selectedHospital!.iD!, int.parse(habibWalletVM.fileNumber));
paytabsViewModel.setPaymentConfiguration("Advance Payment", habibWalletVM.walletRechargeAmount); paytabsViewModel.setPaymentConfiguration("Advance Payment", habibWalletVM.walletRechargeAmount);
paytabsViewModel.startCardPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async { paytabsViewModel.startCardPayment(
LoaderBottomSheet.showLoader(); 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( await habibWalletVM.HISCreateAdvancePayment(
paymentMethodName: selectedPaymentMethod, paymentMethodName: selectedPaymentMethod,
paidAmount: habibWalletVM.walletRechargeAmount, paidAmount: habibWalletVM.walletRechargeAmount,

@ -1,6 +1,7 @@
import 'package:easy_localization/easy_localization.dart'; import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.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/api_consts.dart';
import 'package:hmg_patient_app_new/core/app_assets.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/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/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/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/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/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/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/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/water_monitor/water_monitor_view_model.dart';
import 'package:hmg_patient_app_new/features/weather/weather_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 'package:url_launcher/url_launcher.dart';
import '../../core/dependencies.dart'; import '../../core/dependencies.dart';
import '../emergency_services/call_ambulance/widgets/HospitalBottomSheetBody.dart';
class ServicesPage extends StatefulWidget { class ServicesPage extends StatefulWidget {
final bool showBackIcon; final bool showBackIcon;
@ -116,51 +120,51 @@ class _ServicesPageState extends State<ServicesPage> {
); );
} }
}), }),
// HmgServicesComponentModel( HmgServicesComponentModel(
// 11, 11,
// LocaleKeys.indoorNavigation.tr(), LocaleKeys.indoorNavigation.tr(),
// "", "",
// AppAssets.indoor_nav_icon, AppAssets.indoor_nav_icon,
// bgColor: Color(0xff45A2F8), bgColor: Color(0xff45A2F8),
// true, true,
// route: null, route: null,
// onTap: () { onTap: () {
// showCommonBottomSheetWithoutHeight( showCommonBottomSheetWithoutHeight(
// title: LocaleKeys.selectHospital.tr(), title: LocaleKeys.selectHospital.tr(),
// GetIt.instance<NavigationService>().navigatorKey.currentContext!, GetIt.instance<NavigationService>().navigatorKey.currentContext!,
// child: ChangeNotifierProvider( child: ChangeNotifierProvider(
// create: (context) => HospitalSelectionBottomSheetViewModel(getIt()), create: (context) => HospitalSelectionBottomSheetViewModel(getIt()),
// child: Consumer<HospitalSelectionBottomSheetViewModel>( child: Consumer<HospitalSelectionBottomSheetViewModel>(
// builder: (_, vm, __) => HospitalBottomSheetBody( builder: (_, vm, __) => HospitalBottomSheetBody(
// searchText: vm.searchController, searchText: vm.searchController,
// displayList: vm.displayList, displayList: vm.displayList,
// onFacilityClicked: (value) { onFacilityClicked: (value) {
// vm.setSelectedFacility(value); vm.setSelectedFacility(value);
// vm.getDisplayList(); vm.getDisplayList();
// }, },
// onHospitalClicked: (hospital) { onHospitalClicked: (hospital) {
// Navigator.pop(GetIt.instance<NavigationService>().navigatorKey.currentContext!); Navigator.pop(GetIt.instance<NavigationService>().navigatorKey.currentContext!);
// vm.openPenguin(hospital); vm.openPenguin(hospital);
// }, },
// onHospitalSearch: (value) { onHospitalSearch: (value) {
// vm.searchHospitals(value ?? ""); vm.searchHospitals(value ?? "");
// }, },
// selectedFacility: vm.selectedFacility, selectedFacility: vm.selectedFacility,
// hmcCount: vm.hmcCount, hmcCount: vm.hmcCount,
// hmgCount: vm.hmgCount, hmgCount: vm.hmgCount,
// ), ),
// ), ),
// ), ),
// isFullScreen: false, isFullScreen: false,
// isCloseButtonVisible: true, isCloseButtonVisible: true,
// hasBottomPadding: false, hasBottomPadding: false,
// backgroundColor: AppColors.bottomSheetBgColor, backgroundColor: AppColors.bottomSheetBgColor,
// callBackFunc: () { callBackFunc: () {
// GetIt.instance<NavigationService>().navigatorKey.currentContext!.read<EmergencyServicesViewModel>().clearSearchText(); GetIt.instance<NavigationService>().navigatorKey.currentContext!.read<EmergencyServicesViewModel>().clearSearchText();
// }, },
// ); );
// }, },
// ), ),
HmgServicesComponentModel( HmgServicesComponentModel(
11, LocaleKeys.eReferralServices.tr(), "", AppAssets.eReferral, bgColor: AppColors.eReferralCardColor, true, route: null, onTap: () async { 11, LocaleKeys.eReferralServices.tr(), "", AppAssets.eReferral, bgColor: AppColors.eReferralCardColor, true, route: null, onTap: () async {
if (getIt.get<AppState>().isAuthenticated) { if (getIt.get<AppState>().isAuthenticated) {

@ -511,8 +511,8 @@ class _LandingPageState extends State<LandingPage> {
).paddingSymmetrical(24.h, 0.h).onPress(() { ).paddingSymmetrical(24.h, 0.h).onPress(() {
myAppointmentsViewModel.onTabChange(0); myAppointmentsViewModel.onTabChange(0);
myAppointmentsViewModel.updateListWRTTab(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>().setPaymentConfiguration("Appointment Payment", 15.5);
// getIt.get<PayTabsViewModel>().startCardPayment(); // getIt.get<PayTabsViewModel>().startCardPayment();
}), }),

@ -29,11 +29,19 @@ class OrganSelectorPage extends StatefulWidget {
State<OrganSelectorPage> createState() => _OrganSelectorPageState(); 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 AppState _appState;
late final DialogService dialogService; late final DialogService dialogService;
late final CacheService cacheService; late final CacheService cacheService;
late final AnimationController _introZoomHintController;
late final Animation<double> _introZoomHintAnimation;
bool _showTutorial = false; bool _showTutorial = false;
bool _hasPlayedIntroZoomHint = false;
@override @override
void initState() { void initState() {
@ -41,10 +49,52 @@ class _OrganSelectorPageState extends State<OrganSelectorPage> {
_appState = getIt.get<AppState>(); _appState = getIt.get<AppState>();
dialogService = getIt<DialogService>(); dialogService = getIt<DialogService>();
cacheService = getIt<CacheService>(); 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(); _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 { Future<void> _checkAndShowTutorial() async {
if (!_enableTutorialOverlay) {
_showTutorial = false;
return;
}
// final hasSeenTutorial = cacheService.getBool(key: CacheConst.organSelectorTutorialShown) ?? false; // final hasSeenTutorial = cacheService.getBool(key: CacheConst.organSelectorTutorialShown) ?? false;
// if (!hasSeenTutorial) { // if (!hasSeenTutorial) {
// Show tutorial after a short delay to ensure the screen is fully built // Show tutorial after a short delay to ensure the screen is fully built
@ -118,7 +168,11 @@ class _OrganSelectorPageState extends State<OrganSelectorPage> {
Expanded( Expanded(
child: Stack( child: Stack(
children: [ children: [
_buildBodyViewer(viewModel), SafeArea(
top: false,
bottom: false,
child: _buildBodyViewer(viewModel),
),
_buildViewToggleButtons(viewModel), _buildViewToggleButtons(viewModel),
// _buildViewZoomButtons(viewModel), // _buildViewZoomButtons(viewModel),
_buildBottomSheet(viewModel), _buildBottomSheet(viewModel),
@ -131,7 +185,7 @@ class _OrganSelectorPageState extends State<OrganSelectorPage> {
), ),
), ),
// Tutorial overlay // Tutorial overlay
if (_showTutorial) if (_enableTutorialOverlay && _showTutorial)
PinchZoomTutorialOverlay( PinchZoomTutorialOverlay(
onComplete: _onTutorialComplete, onComplete: _onTutorialComplete,
), ),
@ -152,7 +206,13 @@ class _OrganSelectorPageState extends State<OrganSelectorPage> {
height: 24.h, height: 24.h,
), ),
padding: EdgeInsetsDirectional.only(start: 12, end: 12), 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, highlightColor: Colors.transparent,
), ),
), ),
@ -175,45 +235,82 @@ class _OrganSelectorPageState extends State<OrganSelectorPage> {
Widget _buildBodyViewer(SymptomsCheckerViewModel viewModel) { Widget _buildBodyViewer(SymptomsCheckerViewModel viewModel) {
return GestureDetector( return GestureDetector(
onHorizontalDragEnd: (details) { onHorizontalDragEnd: !_enableSwipeToFlip
// Swipe left or right to toggle view ? null
if (details.primaryVelocity != null) { : (details) {
if (details.primaryVelocity! < -200 || details.primaryVelocity! > 200) { // Swipe left or right to toggle view
viewModel.toggleView(); 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,
);
}, },
), 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) { Widget _buildExpandCollapseButton(SymptomsCheckerViewModel viewModel) {
final organCount = viewModel.selectedOrgans.length;
final showBadge = !viewModel.isBottomSheetExpanded && organCount > 0;
return PositionedDirectional( return PositionedDirectional(
end: 24.w, end: 24.w,
top: -24.h, top: -24.h,
child: GestureDetector( child: GestureDetector(
onTap: viewModel.toggleBottomSheet, onTap: viewModel.toggleBottomSheet,
behavior: HitTestBehavior.opaque, behavior: HitTestBehavior.opaque,
child: Container( child: SizedBox(
width: 70.w, width: 70.w,
height: 70.h, height: 70.h,
alignment: Alignment.center, child: Stack(
child: Container( clipBehavior: Clip.none,
width: 48.w, children: [
height: 48.h, Align(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration( alignment: Alignment.center,
color: AppColors.whiteColor, child: Container(
borderRadius: 11.r, width: 48.w,
), height: 48.h,
child: Center( decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
child: Transform.flip( color: AppColors.whiteColor,
flipX: _appState.isArabic(), borderRadius: 11.r,
child: AnimatedRotation( ),
duration: const Duration(milliseconds: 300), child: Center(
turns: viewModel.isBottomSheetExpanded ? 0.25 : -0.25, child: Transform.flip(
child: Utils.buildSvgWithAssets( flipX: _appState.isArabic(),
icon: AppAssets.arrowRight, child: AnimatedRotation(
width: 25.w, duration: const Duration(milliseconds: 300),
height: 25.h, 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 'dart:ui' as ui;
import 'package:flutter/material.dart'; 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/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_dot_widget.dart';
import 'package:hmg_patient_app_new/presentation/symptoms_checker/widgets/organ_tooltip_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 { class InteractiveBodyWidget extends StatefulWidget {
final String bodyImageAsset; final String bodyImageAsset;
@ -63,9 +63,12 @@ class _InteractiveBodyWidgetState extends State<InteractiveBodyWidget> {
@override @override
void dispose() { void dispose() {
_transformationController.dispose(); _transformationController.dispose();
currentZoom = 0.0;
super.dispose(); super.dispose();
} }
double currentZoom = 0.0;
Future<void> _loadImageAspectRatio() async { Future<void> _loadImageAspectRatio() async {
final ByteData data = await rootBundle.load(widget.bodyImageAsset); final ByteData data = await rootBundle.load(widget.bodyImageAsset);
final ui.Codec codec = await ui.instantiateImageCodec(data.buffer.asUint8List()); final ui.Codec codec = await ui.instantiateImageCodec(data.buffer.asUint8List());
@ -77,17 +80,16 @@ class _InteractiveBodyWidgetState extends State<InteractiveBodyWidget> {
_imageAspectRatio = image.width / image.height; _imageAspectRatio = image.width / image.height;
}); });
} }
_transformationController.addListener(() {
currentZoom = _transformationController.value.getMaxScaleOnAxis();
setState(() {});
});
} }
void _updateZoom(double scale) { void _updateZoom(double scale) {
// Get current translation // Keep programmatic zoom centered to avoid drifting the body off-screen.
final currentTransform = _transformationController.value; final newTransform = Matrix4.identity()..scaleByDouble(scale, scale, 1.0, 1.0);
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));
_transformationController.value = newTransform; _transformationController.value = newTransform;
} }
@ -103,8 +105,12 @@ class _InteractiveBodyWidgetState extends State<InteractiveBodyWidget> {
return Center( return Center(
child: InteractiveViewer( child: InteractiveViewer(
transformationController: _transformationController, transformationController: _transformationController,
minScale: 0.5, alignment: Alignment.center,
maxScale: 4.0, panEnabled: true,
scaleEnabled: true,
boundaryMargin: currentZoom > 1.2 ? EdgeInsets.all(200.h) : EdgeInsets.zero,
minScale: 1.0,
maxScale: 9.0,
clipBehavior: Clip.none, clipBehavior: Clip.none,
child: AspectRatio( child: AspectRatio(
aspectRatio: _imageAspectRatio!, aspectRatio: _imageAspectRatio!,
@ -135,7 +141,7 @@ class _InteractiveBodyWidgetState extends State<InteractiveBodyWidget> {
// Organ dots // Organ dots
...widget.organs.map((organ) { ...widget.organs.map((organ) {
final isSelected = widget.selectedOrganIds.contains(organ.id); 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 leftPos = (organ.position.x * imageConstraints.maxWidth) - (dotSize / 2);
final topPos = (organ.position.y * imageConstraints.maxHeight) - (dotSize / 2); final topPos = (organ.position.y * imageConstraints.maxHeight) - (dotSize / 2);

@ -132,12 +132,29 @@ class _AncillaryOrderPaymentPageState extends State<AncillaryOrderPaymentPage> {
if (!todoVM.isProcessingPayment) { if (!todoVM.isProcessingPayment) {
selectedPaymentMethod = "MADA"; selectedPaymentMethod = "MADA";
if (appState.isPaytabsEnabled) { if (appState.isPaytabsEnabled) {
transID = Utils.getAdvancePaymentTransID(
widget.projectID,
appState.getAuthenticatedUser()!.patientId!,
);
paytabsViewModel.setPaymentConfiguration( paytabsViewModel.setPaymentConfiguration(
"Ancillary Orders Payment", "Ancillary Orders Payment",
widget.totalAmount, widget.totalAmount,
); );
paytabsViewModel.startCardPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async { paytabsViewModel.startCardPayment(
final paymentAmount = widget.totalAmount; 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 fortId = transactionData.transactionReference;
final paymentMethod = selectedPaymentMethod; final paymentMethod = selectedPaymentMethod;
@ -254,11 +271,27 @@ class _AncillaryOrderPaymentPageState extends State<AncillaryOrderPaymentPage> {
if (!todoVM.isProcessingPayment) { if (!todoVM.isProcessingPayment) {
selectedPaymentMethod = "VISA"; selectedPaymentMethod = "VISA";
if (appState.isPaytabsEnabled) { if (appState.isPaytabsEnabled) {
transID = Utils.getAdvancePaymentTransID(
widget.projectID,
appState.getAuthenticatedUser()!.patientId!,
);
paytabsViewModel.setPaymentConfiguration( paytabsViewModel.setPaymentConfiguration(
"Ancillary Orders Payment", "Ancillary Orders Payment",
widget.totalAmount, 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 paymentAmount = widget.totalAmount;
final fortId = transactionData.transactionReference; final fortId = transactionData.transactionReference;
final paymentMethod = selectedPaymentMethod; final paymentMethod = selectedPaymentMethod;

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

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

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

Loading…
Cancel
Save