Merge branch 'refs/heads/master' into offers_discounts

# Conflicts:
#	lib/core/api_consts.dart
#	lib/presentation/home/landing_page.dart
#	lib/presentation/my_family/widget/family_cards.dart
pull/319/head
Aamir Muhammad 4 weeks ago
commit 2b53cce0aa

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

@ -0,0 +1,5 @@
<svg width="22" height="22" viewBox="0 0 22 22" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M0 10.75C0 4.81294 4.81294 0 10.75 0C15.5683 0 19.597 3.1682 20.9655 7.52526C21.037 7.75307 20.9958 8.00133 20.8545 8.19382C20.7133 8.3863 20.4887 8.5 20.25 8.5H17.75C17.3357 8.5 17 8.16421 17 7.75C17 7.33579 17.3357 7 17.75 7H19.1673C17.7335 3.75655 14.5197 1.5 10.75 1.5C5.64137 1.5 1.5 5.64137 1.5 10.75C1.5 15.8586 5.64133 20 10.75 20C11.1642 20 11.5 20.3358 11.5 20.75C11.5 21.1642 11.1642 21.5 10.75 21.5C4.8129 21.5 0 16.6871 0 10.75Z" fill="white"/>
<path d="M10.75 6C11.1642 6 11.5 6.33579 11.5 6.75V10.4393L13.2803 12.2197C13.5732 12.5126 13.5732 12.9874 13.2803 13.2803C12.9874 13.5732 12.5125 13.5732 12.2196 13.2803L10.2196 11.2803C10.079 11.1397 9.99996 10.9489 9.99996 10.75V6.75C9.99996 6.33579 10.3357 6 10.75 6Z" fill="white"/>
<path d="M15.2803 14.2197C14.9874 13.9268 14.5126 13.9268 14.2197 14.2197C13.9268 14.5126 13.9268 14.9874 14.2197 15.2803L16.6893 17.75L14.2197 20.2197C13.9268 20.5126 13.9268 20.9874 14.2197 21.2803C14.5126 21.5732 14.9874 21.5732 15.2803 21.2803L17.75 18.8107L20.2197 21.2803C20.5126 21.5732 20.9874 21.5732 21.2803 21.2803C21.5732 20.9874 21.5732 20.5126 21.2803 20.2197L18.8107 17.75L21.2803 15.2803C21.5732 14.9874 21.5732 14.5126 21.2803 14.2197C20.9874 13.9268 20.5126 13.9268 20.2197 14.2197L17.75 16.6893L15.2803 14.2197Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 28 KiB

@ -0,0 +1,4 @@
<svg width="24" height="24" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5.93712 2.25C5.05309 2.24995 4.28757 2.2499 3.67381 2.33562C3.01162 2.4281 2.38072 2.63711 1.87303 3.16449C1.37047 3.68654 1.17623 4.32619 1.08959 4.99561C1.00807 5.62545 1.0081 6.41382 1.00815 7.33738L1.00815 7.40177C1.00815 7.65552 1.00591 7.89931 1.00378 8.13142C0.998266 8.73244 0.993469 9.25515 1.0301 9.66981C1.08709 10.315 1.25466 10.9156 1.76284 11.4435C2.14586 11.8414 2.77891 11.8535 3.17679 11.4704C3.57468 11.0874 3.58672 10.4544 3.2037 10.0565C3.12657 9.97636 3.05658 9.88147 3.02234 9.49383C2.99474 9.18135 2.99819 8.80852 3.00318 8.27087C3.0055 8.02045 3.00815 7.73426 3.00815 7.40177C3.00815 6.39595 3.01012 5.73847 3.07304 5.25232C3.13273 4.79115 3.23113 4.63751 3.31389 4.55154C3.39151 4.47092 3.52427 4.37592 3.95045 4.31639C4.40942 4.25229 5.0335 4.25001 6.00488 4.25001L17.9951 4.25001C18.9664 4.25001 19.5905 4.25229 20.0495 4.3164C20.4757 4.37592 20.6084 4.47092 20.686 4.55155C20.7688 4.63751 20.8672 4.79115 20.9269 5.25232C20.9898 5.73847 20.9918 6.39595 20.9918 7.40177C20.9918 7.73233 20.9944 8.01713 20.9967 8.26653C21.0016 8.80612 21.005 9.18046 20.9773 9.4935C20.9429 9.88104 20.8729 9.97616 20.7956 10.0565C20.4125 10.4544 20.4246 11.0874 20.8225 11.4704C21.2203 11.8535 21.8534 11.8414 22.2364 11.4435C22.7444 10.9158 22.9123 10.3154 22.9695 9.67014C23.0063 9.25455 23.0016 8.73073 22.9961 8.12825C22.994 7.89711 22.9918 7.65439 22.9918 7.40177L22.9918 7.33739C22.9918 6.41387 22.9919 5.62543 22.9103 4.99561C22.8237 4.32619 22.6295 3.68654 22.1269 3.16449C21.6192 2.63711 20.9883 2.4281 20.3261 2.33562C19.7124 2.2499 18.9468 2.24995 18.0628 2.25L5.93712 2.25Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.1978 6.25C14.9586 6.24998 15.598 6.24996 16.1143 6.31403C16.6617 6.38197 17.1602 6.5306 17.5912 6.89146C18.0222 7.25232 18.2562 7.71697 18.4193 8.24389C18.5731 8.74084 18.6855 9.3703 18.8192 10.1193L19.4895 13.8726C19.7725 15.4578 19.9993 16.7275 20.0348 17.7329C20.0715 18.7716 19.9118 19.6676 19.2925 20.4073C18.6731 21.1471 17.8191 21.4618 16.7902 21.6082C15.7942 21.75 14.5044 21.75 12.8942 21.75H11.1065C9.49624 21.75 8.20645 21.75 7.21049 21.6082C6.18153 21.4618 5.32758 21.1471 4.70822 20.4073C4.08886 19.6676 3.92921 18.7716 3.96588 17.7329C4.00137 16.7275 4.22812 15.4578 4.51121 13.8727L5.18145 10.1193C5.31517 9.37034 5.42756 8.74084 5.58139 8.24389C5.74449 7.71697 5.97845 7.25232 6.40946 6.89146C6.84047 6.5306 7.339 6.38197 7.8864 6.31403C8.40264 6.24996 9.04206 6.24998 9.80289 6.25H14.1978ZM12 9.75C12.4142 9.75 12.75 10.0858 12.75 10.5V15.6893L13.9697 14.4697C14.2626 14.1768 14.7374 14.1768 15.0303 14.4697C15.3232 14.7626 15.3232 15.2374 15.0303 15.5303L12.5303 18.0303C12.2374 18.3232 11.7626 18.3232 11.4697 18.0303L8.96967 15.5303C8.67678 15.2374 8.67678 14.7626 8.96967 14.4697C9.26256 14.1768 9.73744 14.1768 10.0303 14.4697L11.25 15.6893V10.5C11.25 10.0858 11.5858 9.75 12 9.75Z" fill="white"/>
</svg>

After

Width:  |  Height:  |  Size: 2.9 KiB

@ -220,7 +220,7 @@
"request": "الطلبات",
"memberName": "اسم العضو",
"switchLogin": "عرض الملف",
"removeMember": "إزالة",
"removeMember": "حذف",
"allowView": "السماح",
"rejectView": "رفض",
"deleteView": "حذف",
@ -1845,5 +1845,10 @@
"maxOneFileAllowed": "يمكن رفع ملف واحد فقط",
"fileSizeExceedsLimit": "يجب ألا يتجاوز حجم الملف 1 ميغابايت",
"completedPrescriptionOrder": "تمت الخدمة",
"submitEReferral": "تقديم الطلب"
"submitEReferral": "تقديم الطلب",
"redeem": "استبدال",
"points": "نقاط",
"transactions": "المعاملات",
"pharmacy": "الصيدلية",
"visitsOrders": "الزيارات/الطلبات"
}

@ -576,7 +576,7 @@
"active": "Active",
"inactive": "InActive",
"balance": "Balance",
"gained": "GAINED",
"gained": "Gained",
"consumed": "Consumed",
"transferred": "Transferred",
"riyal": "RIYAL",
@ -1835,7 +1835,12 @@
"maxOneFileAllowed": "Only 1 file can be uploaded",
"fileSizeExceedsLimit": "File size must not exceed 1 MB",
"completedPrescriptionOrder": "Completed",
"submitEReferral": "Submit"
"submitEReferral": "Submit",
"redeem": "Redeem",
"points": "Points",
"transactions": "Transactions",
"pharmacy": "Pharmacy",
"visitsOrders": "Visits/Orders"
}

@ -281,7 +281,7 @@ class ApiConsts {
static String googleCloudStorageENTranslationFileBaseURL = "https://storage.googleapis.com/hmg-patientapp-translations";
// ************ static values for Api ****************
static final double appVersionID = 21.1;
static final double appVersionID = 21.2;
// static final double appVersionID = 50.7;
static final int appChannelId = 3;
@ -872,6 +872,7 @@ var GET_DOCTOR_LIST_CALCULATION = "Services/Doctors.svc/REST/GetCallculationDoct
// var GET_ALL_APPOINTMENTS_FOR_DENTAL_CLINIC = "Services/Patients.svc/REST/GetDentalAppointments";
var GET_ALL_APPOINTMENTS_FOR_DENTAL_CLINIC = 'Services/Patients.svc/REST/GetAllInvoices';
var GET_ALL_PHARMACY_INVOICES = 'Services/Patients.svc/REST/GetAllPharmacyInvoices';
var GET_DENTAL_APPOINTMENT_INVOICE = "Services/Patients.svc/REST/HIS_eInvoiceForDentalByAppointmentNo";
var SEND_DENTAL_APPOINTMENT_INVOICE_EMAIL = "Services/Notifications.svc/REST/SendInvoiceForDental";
@ -995,6 +996,8 @@ const UPLOAD_PROFILE_IMAGE = 'Services/Patients.svc/REST/Patient_InsertProfileIm
const DOWNLOAD_INVOICE_PDF = 'Services/Notifications.svc/REST/DownloadInvoiceReport';
const DOWNLOAD_PHARMACY_INVOICE_PDF = 'Services/Notifications.svc/REST/DownloadInvoiceReport';
class ApiKeyConstants {
static final String googleMapsApiKey = 'AIzaSyB6TERnxIr0yJ3qG4ULBZbu0sAD4tGqtng';
}

@ -380,6 +380,9 @@ class AppAssets {
static const String gallery = '$svgBasePath/gallery.svg';
static const String files = '$svgBasePath/files.svg';
static const String online_checkin_icon = '$svgBasePath/online_checkin_icon.svg';
static const String hmg_pharmacy_logo = '$svgBasePath/hmg_pharmacy_logo.svg';
static const String redeem_icon = '$svgBasePath/redeem_icon.svg';
static const String expired_icon = '$svgBasePath/expired_icon.svg';
//Health Calculators
static const String bodyfatcalc = '$svgBasePath/bfc.svg';
@ -421,6 +424,7 @@ class AppAssets {
static const String maleIcon = '$pngBasePath/male_icon.png';
static const String femaleIcon = '$pngBasePath/female_icon.png';
static const String lakumLogo = '$pngBasePath/lakum_logo.png';
static const String lakumLogoWhite = '$pngBasePath/lakum_logo_white.png';
static const String fullBodyFrontMale = '$pngBasePath/full_body_front_male.png';
static const String fullBodyBackMale = '$pngBasePath/full_body_back_male.png';
@ -430,8 +434,6 @@ class AppAssets {
static const String bmiFullBodyFemale = '$pngBasePath/female_bmi.png';
static const String defaultMEP = '$pngBasePath/default_MEP.png';
static const String ambulance_section = '$pngBasePath/ambulance_section.png';
static const String rrt_section = '$pngBasePath/rrt_section.png';
static const String er_checkin = '$pngBasePath/er_checkin.png';

@ -44,6 +44,8 @@ class AppState {
AuthenticatedUser? _authenticatedRootUser;
AuthenticatedUser? _authenticatedChildUser;
bool isPaytabsEnabled = false;
int? _superUserID;
bool isChildLoggedIn = false;
bool isGMSAvailable = true;

@ -50,6 +50,7 @@ import 'package:hmg_patient_app_new/features/offers_and_discounts/offers_and_dis
import 'package:hmg_patient_app_new/features/offers_and_discounts/offers_and_discounts_view_model.dart';
import 'package:hmg_patient_app_new/features/payfort/payfort_repo.dart';
import 'package:hmg_patient_app_new/features/payfort/payfort_view_model.dart';
import 'package:hmg_patient_app_new/features/paytabs/paytabs_view_model.dart';
import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_repo.dart';
import 'package:hmg_patient_app_new/features/prescriptions/prescriptions_view_model.dart';
import 'package:hmg_patient_app_new/features/profile_picture/profile_picture_view_model.dart';
@ -342,5 +343,7 @@ class AppDependencies {
getIt.registerLazySingleton<OffersAndDiscountsViewModel>(() => OffersAndDiscountsViewModel(offersAndDiscountsRepo: getIt(), errorHandlerService: getIt()));
getIt.registerLazySingleton<DateRangCalenderModel>(() => DateRangCalenderModel(appState: getIt(), navigationService: getIt(), dialogService: getIt()));
getIt.registerLazySingleton<PayTabsViewModel>(() => PayTabsViewModel(appState: getIt()));
}
}

@ -0,0 +1,12 @@
/// Default configuration values for PayTabs Payment SDK
class PaymentSdkDefaultConfig {
static const String profileID = "119730";
static const String serverKey = "SWJ9LD22MT-JLJZLBKTLK-WGWTLWGTWR";
static const String clientKey = "C7K2DQ-PDDN6B-69B2K7-P9GB9T";
static const String defaultCurrency = "SAR";
static const double defaultAmount = 10.0;
static const String defaultMerchantCountryCode = "SA";
static const String defaultMerchantName = "Sulaiman Al Habib Medical Group";
static const String defaultMerchantAppleBundleID = "merchant.com.hmgwebservices";
static const String defaultMerchantAppleBundleIDUAT = "merchant.com.hmgwebservices.uat";
}

@ -22,7 +22,6 @@ class PenguinMethodChannel {
"baseURL": "https://eagleiq.hmg.com",
"dataURL": "https://eagleiq.hmg.com",
"positionURL": "https://eagleiq.hmg.com",
"dataServiceName": "api",
"positionServiceName": "pe",
"clientID": "HMG",

@ -99,7 +99,11 @@ class Utils {
static bool isArabicText(String inputText) {
bool isArabicText = false;
final arabic = RegExp(r'^[\u0621-\u064A]+');
// Updated regex to include:
// - Arabic letters: \u0621-\u064A
// - Arabic numerals: \u0660-\u0669 (٠-٩)
// - Persian numerals: \u06F0-\u06F9 (۰-۹)
final arabic = RegExp(r'[\u0621-\u064A\u0660-\u0669\u06F0-\u06F9]');
if (arabic.hasMatch(inputText)) {
isArabicText = true;
} else {
@ -872,25 +876,25 @@ class Utils {
);
}
static Widget getPaymentMethods() {
static Widget getPaymentMethods({num spacing = 3, num width = 35, num height = 35}) {
return Row(
spacing: 6.w,
spacing: spacing.w,
mainAxisSize: MainAxisSize.max,
children: [
Image.asset(AppAssets.mada, width: 35.h, height: 35.h),
Image.asset(AppAssets.mada, width: width.h, height: height.h),
Image.asset(
AppAssets.tamaraEng,
width: 35.h,
height: 35.h,
width: width.h,
height: height.h,
fit: BoxFit.contain,
errorBuilder: (context, error, stackTrace) {
debugPrint('Failed to load Tamara PNG in payment methods: $error');
return Utils.buildSvgWithAssets(icon: AppAssets.tamara, width: 35.h, height: 35.h, fit: BoxFit.contain);
},
),
Image.asset(AppAssets.visa, width: 35.h, height: 35.h),
Image.asset(AppAssets.mastercard, width: 35.h, height: 25.h),
Image.asset(AppAssets.applePay, width: 35.h, height: 35.h),
Image.asset(AppAssets.visa, width: width.h, height: height.h),
Image.asset(AppAssets.mastercard, width: width.h, height: height.h),
Platform.isIOS ? Image.asset(AppAssets.applePay, width: width.h, height: height.h) : SizedBox.shrink(),
],
);
}

@ -1,4 +1,5 @@
import 'dart:async';
import 'dart:io';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/foundation.dart';
@ -451,7 +452,9 @@ class _OTPVerificationScreenState extends State<OTPVerificationScreen> {
super.initState();
_otpController = TextEditingController();
_startResendTimer();
checkSignature();
if(Platform.isAndroid) {
checkSignature();
}
}
@override

@ -866,6 +866,7 @@ class BookAppointmentsViewModel extends ChangeNotifier {
initialSlotDuration = apiResponse.data["InitialSlotDuration"];
freeSlotsResponse = apiResponse.data['FreeTimeSlots'];
isWaitingAppointmentAvailable = apiResponse.data["IsAllowToBookWaitingAppointment"];
// isWaitingAppointmentAvailable = true;
freeSlotsResponse.forEach((element) {
@ -1479,6 +1480,9 @@ class BookAppointmentsViewModel extends ChangeNotifier {
result.fold(
(failure) async {
if(onError != null) {
onError(failure.message);
}
onError!(LocaleKeys.noDoctorFound.tr());
},
(apiResponse) {

@ -345,7 +345,7 @@ class ContactUsViewModel extends ChangeNotifier {
}
} else if (apiResponse.messageStatus == 1) {
cocItemsList = apiResponse.data!;
cocItemsList.sort((a, b) => a.date!.compareTo(b.date!));
cocItemsList.sort((a, b) => b.date!.compareTo(a.date!));
isCOCItemsListLoading = false;
notifyListeners();
if (onSuccess != null) {

@ -18,6 +18,10 @@ abstract class HabibWalletRepo {
Future<Either<Failure, GenericApiModel<dynamic>>> addAdvanceNumberRequest({required String advanceNumber, required String paymentReference});
Future<Either<Failure, GenericApiModel<dynamic>>> getPatientInfoByPatientID({required String patientID});
Future<Either<Failure, GenericApiModel<dynamic>>> getLakumAccountInformation({required String identificationNumber});
Future<Either<Failure, GenericApiModel<dynamic>>> getLakumAccountInquiryInformation({required String lakumAccountNumber});
}
class HabibWalletRepoImp implements HabibWalletRepo {
@ -231,4 +235,75 @@ class HabibWalletRepoImp implements HabibWalletRepo {
return Left(UnknownFailure(e.toString()));
}
}
@override
Future<Either<Failure, GenericApiModel>> getLakumAccountInformation({required String identificationNumber}) async {
Map<String, dynamic> requestBody = {"IdentificationNo": identificationNumber};
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
await apiClient.post(
GET_LACUM_ACCOUNT_INFORMATION,
body: requestBody,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: response,
);
} catch (e) {
failure = DataParsingFailure(e.toString());
}
},
);
if (failure != null) return Left(failure!);
if (apiResponse == null) return Left(ServerFailure("Unknown error"));
return Right(apiResponse!);
} catch (e) {
return Left(UnknownFailure(e.toString()));
}
}
@override
Future<Either<Failure, GenericApiModel>> getLakumAccountInquiryInformation({required String lakumAccountNumber}) async {
Map<String, dynamic> requestBody = {
"AccountNumber": lakumAccountNumber,
"IsDetailsRequired": true,
};
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
await apiClient.post(
GET_LACUM_GROUP_INFORMATION,
body: requestBody,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
apiResponse = GenericApiModel<dynamic>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: response,
);
} catch (e) {
failure = DataParsingFailure(e.toString());
}
},
);
if (failure != null) return Left(failure!);
if (apiResponse == null) return Left(ServerFailure("Unknown error"));
return Right(apiResponse!);
} catch (e) {
return Left(UnknownFailure(e.toString()));
}
}
}

@ -1,6 +1,10 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_repo.dart';
import 'package:hmg_patient_app_new/features/habib_wallet/models/lakum_inquiry_information_response_model.dart';
import 'package:hmg_patient_app_new/features/habib_wallet/models/patient_advance_balance_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/hospital_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
@ -43,6 +47,54 @@ class HabibWalletViewModel extends ChangeNotifier {
String? get hospitalError => _hospitalError;
String yahalaAccountNumber = '';
bool isLakumAccountInfoLoading = false;
LakumInquiryInformationResponseModel? lakumAccountInfo;
// Get all flattened transactions sorted by date descending
List<PointsDetails> getAllLakumTransactions() {
if (lakumAccountInfo?.gainedPointsAmountPerYear == null) {
return [];
}
List<PointsDetails> allTransactions = [];
// Flatten the nested structure
for (var yearData in lakumAccountInfo!.gainedPointsAmountPerYear!) {
if (yearData.pointsAmountPerMonth != null) {
for (var monthData in yearData.pointsAmountPerMonth!) {
if (monthData.pointsAmountPerday != null) {
for (var dayData in monthData.pointsAmountPerday!) {
if (dayData.pointsDetails != null) {
allTransactions.addAll(dayData.pointsDetails!);
}
}
}
}
}
}
// Sort by transaction date DESCENDING (latest/most recent transactions first, older transactions last)
allTransactions.sort((a, b) {
// Null dates go to the end
if (a.transactionDate == null && b.transactionDate == null) return 0;
if (a.transactionDate == null) return 1; // a goes after b
if (b.transactionDate == null) return -1; // a goes before b
try {
DateTime dateA = DateUtil.convertStringToDate(a.transactionDate!);
DateTime dateB = DateUtil.convertStringToDate(b.transactionDate!);
// Compare dateB to dateA (not dateA to dateB) for DESCENDING order
// This puts newer dates (larger values) at the top of the list
return dateB.compareTo(dateA);
} catch (e) {
// If date parsing fails, maintain current order
return 0;
}
});
return allTransactions;
}
// Clear amount error
void clearAmountError() {
@ -103,6 +155,7 @@ class HabibWalletViewModel extends ChangeNotifier {
initHabibWalletProvider() {
isWalletAmountLoading = true;
isBottomSheetContentLoading = false;
isLakumAccountInfoLoading = true;
habibWalletAmount = 0;
walletRechargeAmount = 0;
selectedRechargeType = 1;
@ -112,6 +165,7 @@ class HabibWalletViewModel extends ChangeNotifier {
fileNumber = '';
depositorName = '';
mobileNumber = '';
yahalaAccountNumber = "";
notifyListeners();
}
@ -148,6 +202,11 @@ class HabibWalletViewModel extends ChangeNotifier {
notifyListeners();
}
setYahalaAccountNumber(String accountNumber) {
yahalaAccountNumber = accountNumber;
notifyListeners();
}
String getSelectedRechargeTypeValue() {
switch (selectedRechargeType) {
case 1:
@ -221,7 +280,12 @@ class HabibWalletViewModel extends ChangeNotifier {
paymentMethodName: paymentMethodName, paidAmount: paidAmount, paymentReference: paymentReference, patientID: patientID, projectID: projectID, depositorName: depositorName);
result.fold(
(failure) async => await errorHandlerService.handleError(failure: failure),
// (failure) async => await errorHandlerService.handleError(failure: failure),
(failure) async {
if(onError != null) {
onError(failure.message);
}
},
(apiResponse) {
if (apiResponse.messageStatus == 2) {
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
@ -277,4 +341,46 @@ class HabibWalletViewModel extends ChangeNotifier {
},
);
}
Future<void> getLakumAccountInformation({Function(dynamic)? onSuccess, Function(String)? onError}) async {
isLakumAccountInfoLoading = true;
lakumAccountInfo = LakumInquiryInformationResponseModel();
notifyListeners();
final result = await habibWalletRepo.getLakumAccountInformation(identificationNumber: getIt.get<AppState>().getAuthenticatedUser()!.patientIdentificationNo ?? "");
result.fold(
(failure) async => await errorHandlerService.handleError(failure: failure),
(apiResponse) async {
if (apiResponse.messageStatus == 2) {
yahalaAccountNumber = "0";
isLakumAccountInfoLoading = false;
notifyListeners();
} else if (apiResponse.messageStatus == 1) {
if (apiResponse.data["YahalaAccountNo"] == 0 || apiResponse.data["YahalaAccountNo"] == "0") {
yahalaAccountNumber = "0";
} else {
yahalaAccountNumber = apiResponse.data["YahalaAccountNo"].toString();
final lakumAccountResult = await habibWalletRepo.getLakumAccountInquiryInformation(lakumAccountNumber: yahalaAccountNumber);
lakumAccountResult.fold(
(failure) async => await errorHandlerService.handleError(failure: failure),
(lakumApiResponse) async {
if (lakumApiResponse.messageStatus == 2) {
lakumAccountInfo = LakumInquiryInformationResponseModel();
} else if (lakumApiResponse.messageStatus == 1) {
lakumAccountInfo = LakumInquiryInformationResponseModel.fromJson(lakumApiResponse.data["LakumInquiryInformationObjVersion"]);
}
},
);
}
isLakumAccountInfoLoading = false;
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
}
}
},
);
}
}

@ -0,0 +1,318 @@
class LakumInquiryInformationResponseModel {
int? accountNumber;
String? accountStatus;
String? barCode;
num? consumedPoints;
dynamic consumedPointsAmount;
dynamic consumedPointsAmountPerYear;
// List<Null>? consumedPointsDetails;
String? createdDate;
num? expiredPoints;
String? expiryDate;
num? gainedPoints;
num? gainedPointsAmount;
List<GainedPointsAmountPerYear>? gainedPointsAmountPerYear;
// List<Null>? gainedPointsDetails;
String? lakumMessageStatus;
String? memberName;
String? memberUniversalId;
String? mobileNumber;
num? pointsBalance;
num? pointsBalanceAmount;
num? pointsWillBeExpired;
String? prefLang;
num? statusCode;
num? transferPoints;
dynamic transferPointsAmountPerYear;
// List<Null>? transferPointsDetails;
num? waitingPoints;
num? loyalityAmount;
num? loyalityPoints;
num? purchaseRate;
LakumInquiryInformationResponseModel(
{this.accountNumber,
this.accountStatus,
this.barCode,
this.consumedPoints,
this.consumedPointsAmount,
this.consumedPointsAmountPerYear,
// this.consumedPointsDetails,
this.createdDate,
this.expiredPoints,
this.expiryDate,
this.gainedPoints,
this.gainedPointsAmount,
this.gainedPointsAmountPerYear,
// this.gainedPointsDetails,
this.lakumMessageStatus,
this.memberName,
this.memberUniversalId,
this.mobileNumber,
this.pointsBalance,
this.pointsBalanceAmount,
this.pointsWillBeExpired,
this.prefLang,
this.statusCode,
this.transferPoints,
this.transferPointsAmountPerYear,
// this.transferPointsDetails,
this.waitingPoints,
this.loyalityAmount,
this.loyalityPoints,
this.purchaseRate});
LakumInquiryInformationResponseModel.fromJson(Map<String, dynamic> json) {
accountNumber = json['AccountNumber'];
accountStatus = json['AccountStatus'];
barCode = json['BarCode'];
consumedPoints = json['ConsumedPoints'];
consumedPointsAmount = json['ConsumedPointsAmount'];
consumedPointsAmountPerYear = json['ConsumedPointsAmountPerYear'];
// if (json['ConsumedPointsDetails'] != null) {
// consumedPointsDetails = <Null>[];
// json['ConsumedPointsDetails'].forEach((v) {
// consumedPointsDetails!.add(new Null.fromJson(v));
// });
// }
createdDate = json['CreatedDate'];
expiredPoints = json['ExpiredPoints'];
expiryDate = json['ExpiryDate'];
gainedPoints = json['GainedPoints'];
gainedPointsAmount = json['GainedPointsAmount'];
if (json['GainedPointsAmountPerYear'] != null) {
gainedPointsAmountPerYear = <GainedPointsAmountPerYear>[];
json['GainedPointsAmountPerYear'].forEach((v) {
gainedPointsAmountPerYear!.add(new GainedPointsAmountPerYear.fromJson(v));
});
}
// if (json['GainedPointsDetails'] != null) {
// gainedPointsDetails = <Null>[];
// json['GainedPointsDetails'].forEach((v) {
// gainedPointsDetails!.add(new Null.fromJson(v));
// });
// }
lakumMessageStatus = json['LakumMessageStatus'];
memberName = json['MemberName'];
memberUniversalId = json['MemberUniversalId'];
mobileNumber = json['MobileNumber'];
pointsBalance = json['PointsBalance'];
pointsBalanceAmount = json['PointsBalanceAmount'];
pointsWillBeExpired = json['PointsWillBeExpired'];
prefLang = json['PrefLang'];
statusCode = json['StatusCode'];
transferPoints = json['TransferPoints'];
transferPointsAmountPerYear = json['TransferPointsAmountPerYear'];
// if (json['TransferPointsDetails'] != null) {
// transferPointsDetails = <Null>[];
// json['TransferPointsDetails'].forEach((v) {
// transferPointsDetails!.add(new Null.fromJson(v));
// });
// }
waitingPoints = json['WaitingPoints'];
loyalityAmount = json['loyalityAmount'];
loyalityPoints = json['loyalityPoints'];
purchaseRate = json['purchaseRate'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['AccountNumber'] = this.accountNumber;
data['AccountStatus'] = this.accountStatus;
data['BarCode'] = this.barCode;
data['ConsumedPoints'] = this.consumedPoints;
data['ConsumedPointsAmount'] = this.consumedPointsAmount;
data['ConsumedPointsAmountPerYear'] = this.consumedPointsAmountPerYear;
// if (this.consumedPointsDetails != null) {
// data['ConsumedPointsDetails'] = this.consumedPointsDetails!.map((v) => v.toJson()).toList();
// }
data['CreatedDate'] = this.createdDate;
data['ExpiredPoints'] = this.expiredPoints;
data['ExpiryDate'] = this.expiryDate;
data['GainedPoints'] = this.gainedPoints;
data['GainedPointsAmount'] = this.gainedPointsAmount;
if (this.gainedPointsAmountPerYear != null) {
data['GainedPointsAmountPerYear'] = this.gainedPointsAmountPerYear!.map((v) => v.toJson()).toList();
}
// if (this.gainedPointsDetails != null) {
// data['GainedPointsDetails'] = this.gainedPointsDetails!.map((v) => v.toJson()).toList();
// }
data['LakumMessageStatus'] = this.lakumMessageStatus;
data['MemberName'] = this.memberName;
data['MemberUniversalId'] = this.memberUniversalId;
data['MobileNumber'] = this.mobileNumber;
data['PointsBalance'] = this.pointsBalance;
data['PointsBalanceAmount'] = this.pointsBalanceAmount;
data['PointsWillBeExpired'] = this.pointsWillBeExpired;
data['PrefLang'] = this.prefLang;
data['StatusCode'] = this.statusCode;
data['TransferPoints'] = this.transferPoints;
data['TransferPointsAmountPerYear'] = this.transferPointsAmountPerYear;
// if (this.transferPointsDetails != null) {
// data['TransferPointsDetails'] = this.transferPointsDetails!.map((v) => v.toJson()).toList();
// }
data['WaitingPoints'] = this.waitingPoints;
data['loyalityAmount'] = this.loyalityAmount;
data['loyalityPoints'] = this.loyalityPoints;
data['purchaseRate'] = this.purchaseRate;
return data;
}
}
class GainedPointsAmountPerYear {
num? amountPerYear;
List<PointsAmountPerMonth>? pointsAmountPerMonth;
num? pointsPerYear;
int? year;
GainedPointsAmountPerYear({this.amountPerYear, this.pointsAmountPerMonth, this.pointsPerYear, this.year});
GainedPointsAmountPerYear.fromJson(Map<String, dynamic> json) {
amountPerYear = json['AmountPerYear'];
if (json['PointsAmountPerMonth'] != null) {
pointsAmountPerMonth = <PointsAmountPerMonth>[];
json['PointsAmountPerMonth'].forEach((v) {
pointsAmountPerMonth!.add(new PointsAmountPerMonth.fromJson(v));
});
}
pointsPerYear = json['PointsPerYear'];
year = json['Year'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['AmountPerYear'] = this.amountPerYear;
if (this.pointsAmountPerMonth != null) {
data['PointsAmountPerMonth'] = this.pointsAmountPerMonth!.map((v) => v.toJson()).toList();
}
data['PointsPerYear'] = this.pointsPerYear;
data['Year'] = this.year;
return data;
}
}
class PointsAmountPerMonth {
num? amountPerMonth;
String? month;
num? monthNumber;
List<PointsAmountPerday>? pointsAmountPerday;
num? pointsPerMonth;
PointsAmountPerMonth({this.amountPerMonth, this.month, this.monthNumber, this.pointsAmountPerday, this.pointsPerMonth});
PointsAmountPerMonth.fromJson(Map<String, dynamic> json) {
amountPerMonth = json['AmountPerMonth'];
month = json['Month'];
monthNumber = json['MonthNumber'];
if (json['PointsAmountPerday'] != null) {
pointsAmountPerday = <PointsAmountPerday>[];
json['PointsAmountPerday'].forEach((v) {
pointsAmountPerday!.add(new PointsAmountPerday.fromJson(v));
});
}
pointsPerMonth = json['PointsPerMonth'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['AmountPerMonth'] = this.amountPerMonth;
data['Month'] = this.month;
data['MonthNumber'] = this.monthNumber;
if (this.pointsAmountPerday != null) {
data['PointsAmountPerday'] = this.pointsAmountPerday!.map((v) => v.toJson()).toList();
}
data['PointsPerMonth'] = this.pointsPerMonth;
return data;
}
}
class PointsAmountPerday {
num? amountPerDay;
String? day;
List<PointsDetails>? pointsDetails;
num? pointsPerDay;
String? transationDate;
PointsAmountPerday({this.amountPerDay, this.day, this.pointsDetails, this.pointsPerDay, this.transationDate});
PointsAmountPerday.fromJson(Map<String, dynamic> json) {
amountPerDay = json['AmountPerDay'];
day = json['Day'];
if (json['PointsDetails'] != null) {
pointsDetails = <PointsDetails>[];
json['PointsDetails'].forEach((v) {
pointsDetails!.add(new PointsDetails.fromJson(v));
});
}
pointsPerDay = json['PointsPerDay'];
transationDate = json['TransationDate'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['AmountPerDay'] = this.amountPerDay;
data['Day'] = this.day;
if (this.pointsDetails != null) {
data['PointsDetails'] = this.pointsDetails!.map((v) => v.toJson()).toList();
}
data['PointsPerDay'] = this.pointsPerDay;
data['TransationDate'] = this.transationDate;
return data;
}
}
class PointsDetails {
int? accNumber;
String? accountStatus;
num? amount;
int? lineItemNo;
String? operationType;
num? points;
num? purchasePoints;
int? subTransactionType;
String? subTransactionTypeDescription;
String? transactionDate;
PointsDetails(
{this.accNumber,
this.accountStatus,
this.amount,
this.lineItemNo,
this.operationType,
this.points,
this.purchasePoints,
this.subTransactionType,
this.subTransactionTypeDescription,
this.transactionDate});
PointsDetails.fromJson(Map<String, dynamic> json) {
accNumber = json['AccNumber'];
accountStatus = json['AccountStatus'];
amount = json['Amount'];
lineItemNo = json['LineItemNo'];
operationType = json['OperationType'];
points = json['Points'];
purchasePoints = json['PurchasePoints'];
subTransactionType = json['SubTransactionType'];
subTransactionTypeDescription = json['SubTransactionTypeDescription'];
transactionDate = json['TransactionDate'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['AccNumber'] = this.accNumber;
data['AccountStatus'] = this.accountStatus;
data['Amount'] = this.amount;
data['LineItemNo'] = this.lineItemNo;
data['OperationType'] = this.operationType;
data['Points'] = this.points;
data['PurchasePoints'] = this.purchasePoints;
data['SubTransactionType'] = this.subTransactionType;
data['SubTransactionTypeDescription'] = this.subTransactionTypeDescription;
data['TransactionDate'] = this.transactionDate;
return data;
}
}

@ -0,0 +1,99 @@
class GetPharmacyInvoicesResponseModel {
String? setupId;
int? projectID;
int? pharmacyProjectID;
int? patientID;
int? appointmentNo;
String? appointmentDate;
dynamic appointmentDateN;
int? clinicID;
int? doctorID;
int? invoiceNo;
int? status;
String? arrivedOn;
String? doctorName;
String? doctorNameN;
String? displayInvoiceNo;
String? clinicName;
num? decimalDoctorRate;
String? doctorImageURL;
num? doctorRate;
num? patientNumber;
String? projectName;
dynamic locationID;
GetPharmacyInvoicesResponseModel(
{this.setupId,
this.projectID,
this.pharmacyProjectID,
this.patientID,
this.appointmentNo,
this.appointmentDate,
this.appointmentDateN,
this.clinicID,
this.doctorID,
this.invoiceNo,
this.status,
this.arrivedOn,
this.doctorName,
this.doctorNameN,
this.displayInvoiceNo,
this.clinicName,
this.decimalDoctorRate,
this.doctorImageURL,
this.doctorRate,
this.patientNumber,
this.projectName, this.locationID});
GetPharmacyInvoicesResponseModel.fromJson(Map<String, dynamic> json) {
setupId = json['SetupId'];
projectID = json['ProjectID'];
pharmacyProjectID = json['PharmacyProjectID'];
patientID = json['PatientID'];
appointmentNo = json['AppointmentNo'];
appointmentDate = json['AppointmentDate'];
appointmentDateN = json['AppointmentDateN'];
clinicID = json['ClinicID'];
doctorID = json['DoctorID'];
invoiceNo = json['InvoiceNo'];
status = json['Status'];
arrivedOn = json['ArrivedOn'];
doctorName = json['DoctorName'];
doctorNameN = json['DoctorNameN'];
displayInvoiceNo = json['DisplayInvoiceNo'];
clinicName = json['ClinicName'];
decimalDoctorRate = json['DecimalDoctorRate'];
doctorImageURL = json['DoctorImageURL'];
doctorRate = json['DoctorRate'];
patientNumber = json['PatientNumber'];
projectName = json['ProjectName'];
locationID = json['LocationId'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['SetupId'] = this.setupId;
data['ProjectID'] = this.projectID;
data['PharmacyProjectID'] = this.pharmacyProjectID;
data['PatientID'] = this.patientID;
data['AppointmentNo'] = this.appointmentNo;
data['AppointmentDate'] = this.appointmentDate;
data['AppointmentDateN'] = this.appointmentDateN;
data['ClinicID'] = this.clinicID;
data['DoctorID'] = this.doctorID;
data['InvoiceNo'] = this.invoiceNo;
data['Status'] = this.status;
data['ArrivedOn'] = this.arrivedOn;
data['DoctorName'] = this.doctorName;
data['DoctorNameN'] = this.doctorNameN;
data['DisplayInvoiceNo'] = this.displayInvoiceNo;
data['ClinicName'] = this.clinicName;
data['DecimalDoctorRate'] = this.decimalDoctorRate;
data['DoctorImageURL'] = this.doctorImageURL;
data['DoctorRate'] = this.doctorRate;
data['PatientNumber'] = this.patientNumber;
data['ProjectName'] = this.projectName;
data['LocationId'] = this.locationID;
return data;
}
}

@ -5,16 +5,21 @@ import 'package:hmg_patient_app_new/core/common_models/generic_api_model.dart';
import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart';
import 'package:hmg_patient_app_new/features/my_invoices/models/get_invoice_details_response_model.dart';
import 'package:hmg_patient_app_new/features/my_invoices/models/get_invoices_list_response_model.dart';
import 'package:hmg_patient_app_new/features/my_invoices/models/get_pharmacy_invoices_response_model.dart';
import 'package:hmg_patient_app_new/services/logger_service.dart';
abstract class MyInvoicesRepo {
Future<Either<Failure, GenericApiModel<List<GetInvoicesListResponseModel>>>> getAllInvoicesList();
Future<Either<Failure, GenericApiModel<List<GetPharmacyInvoicesResponseModel>>>> getAllPharmacyInvoices();
Future<Either<Failure, GenericApiModel<GetInvoiceDetailsResponseModel>>> getInvoiceDetails({required num appointmentNo, required num invoiceNo, required int projectID});
Future<Either<Failure, GenericApiModel<dynamic>>> sendInvoiceEmail({required num appointmentNo, required int projectID});
Future<Either<Failure, GenericApiModel<dynamic>>> downloadInvoice({required String setupId, required int invoiceNo, required int projectID});
Future<Either<Failure, GenericApiModel<dynamic>>> downloadPharmacyInvoice({required String setupId, required int invoiceNo, required int projectID, required dynamic locationID});
}
class MyInvoicesRepoImp implements MyInvoicesRepo {
@ -61,6 +66,47 @@ class MyInvoicesRepoImp implements MyInvoicesRepo {
}
}
@override
Future<Either<Failure, GenericApiModel<List<GetPharmacyInvoicesResponseModel>>>> getAllPharmacyInvoices() async {
Map<String, dynamic> mapDevice = {};
try {
GenericApiModel<List<GetPharmacyInvoicesResponseModel>>? apiResponse;
Failure? failure;
await apiClient.post(
GET_ALL_PHARMACY_INVOICES,
body: mapDevice,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
final list = response['List_AllPharmacyInvoices'] ?? response['PharmacyInvoicesList'] ?? [];
final invoicesList = list
.map((item) => GetPharmacyInvoicesResponseModel.fromJson(item as Map<String, dynamic>))
.toList()
.cast<GetPharmacyInvoicesResponseModel>();
apiResponse = GenericApiModel<List<GetPharmacyInvoicesResponseModel>>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: invoicesList,
);
} catch (e) {
failure = DataParsingFailure(e.toString());
}
},
);
if (failure != null) return Left(failure!);
if (apiResponse == null) return Left(ServerFailure("Unknown error"));
return Right(apiResponse!);
} catch (e) {
return Left(UnknownFailure(e.toString()));
}
}
@override
Future<Either<Failure, GenericApiModel<GetInvoiceDetailsResponseModel>>> getInvoiceDetails({required num appointmentNo, required num invoiceNo, required int projectID}) async {
Map<String, dynamic> mapDevice = {
@ -178,4 +224,43 @@ class MyInvoicesRepoImp implements MyInvoicesRepo {
return Left(UnknownFailure(e.toString()));
}
}
@override
Future<Either<Failure, GenericApiModel>> downloadPharmacyInvoice({required String setupId, required int invoiceNo, required int projectID, required dynamic locationID}) async {
Map<String, dynamic> mapDevice = {
"SetupID": setupId,
"ProjectID": projectID,
"InvoiceNo": invoiceNo,
"LocationID": locationID,
};
try {
GenericApiModel<dynamic>? apiResponse;
Failure? failure;
await apiClient.post(
DOWNLOAD_PHARMACY_INVOICE_PDF,
body: mapDevice,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
apiResponse = GenericApiModel(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: response["InvoiceReportPDFContent"],
);
} catch (e) {
failure = DataParsingFailure(e.toString());
}
},
);
if (failure != null) return Left(failure!);
if (apiResponse == null) return Left(ServerFailure("Unknown error"));
return Right(apiResponse!);
} catch (e) {
return Left(UnknownFailure(e.toString()));
}
}
}

@ -3,6 +3,7 @@ import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/features/my_invoices/models/get_invoice_details_response_model.dart';
import 'package:hmg_patient_app_new/features/my_invoices/models/get_invoices_list_response_model.dart';
import 'package:hmg_patient_app_new/features/my_invoices/models/get_pharmacy_invoices_response_model.dart';
import 'package:hmg_patient_app_new/features/my_invoices/my_invoices_repo.dart';
import 'package:hmg_patient_app_new/services/error_handler_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
@ -12,6 +13,7 @@ enum InvoiceFilterType { all, hospital, clinic, doctor }
class MyInvoicesViewModel extends ChangeNotifier {
bool isInvoicesListLoading = false;
bool isInvoiceDetailsLoading = false;
bool isPharmacyInvoicesLoading = false;
MyInvoicesRepo myInvoicesRepo;
ErrorHandlerService errorHandlerService;
@ -19,6 +21,8 @@ class MyInvoicesViewModel extends ChangeNotifier {
List<GetInvoicesListResponseModel> allInvoicesList = [];
List<GetInvoicesListResponseModel> _originalInvoicesList = [];
List<GetPharmacyInvoicesResponseModel> allPharmacyInvoicesList = [];
List<GetPharmacyInvoicesResponseModel> _originalPharmacyInvoicesList = [];
InvoiceFilterType currentFilter = InvoiceFilterType.all;
late GetInvoiceDetailsResponseModel invoiceDetailsResponseModel;
@ -29,14 +33,20 @@ class MyInvoicesViewModel extends ChangeNotifier {
String? selectedFilterItem;
String? downloadInvoicePDFBase64 = "";
String? downloadPharmacyInvoicePDFBase64 = "";
int selectedTabIndex = 0;
MyInvoicesViewModel({required this.myInvoicesRepo, required this.errorHandlerService, required this.navServices});
setInvoicesListLoading() {
isInvoicesListLoading = true;
isPharmacyInvoicesLoading = true;
allInvoicesList.clear();
allPharmacyInvoicesList.clear();
_originalInvoicesList.clear();
currentFilter = InvoiceFilterType.all;
onTabChanged(0);
notifyListeners();
}
@ -45,6 +55,11 @@ class MyInvoicesViewModel extends ChangeNotifier {
notifyListeners();
}
void onTabChanged(int index) {
selectedTabIndex = index;
notifyListeners();
}
Future<void> getAllInvoicesList({Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result = await myInvoicesRepo.getAllInvoicesList();
@ -72,6 +87,40 @@ class MyInvoicesViewModel extends ChangeNotifier {
);
}
Future<void> getAllPharmacyInvoices({Function(dynamic)? onSuccess, Function(String)? onError}) async {
isPharmacyInvoicesLoading = true;
allPharmacyInvoicesList.clear();
notifyListeners();
final result = await myInvoicesRepo.getAllPharmacyInvoices();
result.fold(
(failure) async {
isPharmacyInvoicesLoading = false;
notifyListeners();
if (onError != null) {
onError(failure.message);
}
},
(apiResponse) {
_originalPharmacyInvoicesList = [];
allPharmacyInvoicesList = [];
if (apiResponse.messageStatus == 2) {
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
} else if (apiResponse.messageStatus == 1) {
_originalPharmacyInvoicesList = apiResponse.data ?? [];
_originalPharmacyInvoicesList.sort((a, b) => (b.appointmentDate ?? '').compareTo(a.appointmentDate ?? ''));
allPharmacyInvoicesList = List.from(_originalPharmacyInvoicesList);
isPharmacyInvoicesLoading = false;
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
}
}
},
);
}
Future<void> getInvoiceDetails({required num appointmentNo, required num invoiceNo, required int projectID, Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result = await myInvoicesRepo.getInvoiceDetails(appointmentNo: appointmentNo, invoiceNo: invoiceNo, projectID: projectID);
result.fold(
@ -144,6 +193,32 @@ class MyInvoicesViewModel extends ChangeNotifier {
);
}
Future<void> downloadPharmacyInvoicePDF({required String setupId, required int invoiceNo, required int projectID, required dynamic locationID, Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result = await myInvoicesRepo.downloadPharmacyInvoice(setupId: setupId, invoiceNo: invoiceNo, projectID: projectID, locationID: locationID);
result.fold(
(failure) async {
if (onError != null) {
onError(failure.message);
}
},
(apiResponse) {
if (apiResponse.messageStatus == 2) {
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
} else if (apiResponse.messageStatus == 1) {
if (apiResponse.data != null && apiResponse.data!.isNotEmpty) {
downloadPharmacyInvoicePDFBase64 = apiResponse.data!;
} else {
downloadPharmacyInvoicePDFBase64 = "";
}
if (onSuccess != null) {
onSuccess(apiResponse);
}
}
},
);
}
void filterInvoices(InvoiceFilterType filterType) {
currentFilter = filterType;

@ -0,0 +1,133 @@
class PaytabsTransactionResponseModel {
int? tranTotal;
String? transactionReference;
String? cartCurrency;
String? cartDescription;
String? cartID;
String? tranCurrency;
String? payResponseReturn;
bool? isPending;
bool? isOnHold;
String? token;
String? transactionType;
bool? isAuthorized;
String? trace;
dynamic cartAmount;
int? merchantId;
int? profileId;
bool? isProcessed;
int? serviceId;
PaymentInfo? paymentInfo;
bool? isSuccess;
PaytabsTransactionResponseModel(
{this.tranTotal,
this.transactionReference,
this.cartCurrency,
this.cartDescription,
this.cartID,
this.tranCurrency,
this.payResponseReturn,
this.isPending,
this.isOnHold,
this.token,
this.transactionType,
this.isAuthorized,
this.trace,
this.cartAmount,
this.merchantId,
this.profileId,
this.isProcessed,
this.serviceId,
this.paymentInfo,
this.isSuccess});
PaytabsTransactionResponseModel.fromJson(Map<String, dynamic> json) {
tranTotal = json['tran_total'];
transactionReference = json['transactionReference'];
cartCurrency = json['cartCurrency'];
cartDescription = json['cartDescription'];
cartID = json['cartID'];
tranCurrency = json['tran_currency'];
payResponseReturn = json['payResponseReturn'];
isPending = json['isPending'];
isOnHold = json['isOnHold'];
token = json['token'];
transactionType = json['transactionType'];
isAuthorized = json['isAuthorized'];
trace = json['trace'];
cartAmount = json['cartAmount'];
merchantId = json['merchantId'];
profileId = json['profileId'];
isProcessed = json['isProcessed'];
serviceId = json['serviceId'];
paymentInfo = json['paymentInfo'] != null
? PaymentInfo.fromJson(Map<String, dynamic>.from(json['paymentInfo'] as Map))
: null;
isSuccess = json['isSuccess'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['tran_total'] = this.tranTotal;
data['transactionReference'] = this.transactionReference;
data['cartCurrency'] = this.cartCurrency;
data['cartDescription'] = this.cartDescription;
data['cartID'] = this.cartID;
data['tran_currency'] = this.tranCurrency;
data['payResponseReturn'] = this.payResponseReturn;
data['isPending'] = this.isPending;
data['isOnHold'] = this.isOnHold;
data['token'] = this.token;
data['transactionType'] = this.transactionType;
data['isAuthorized'] = this.isAuthorized;
data['trace'] = this.trace;
data['cartAmount'] = this.cartAmount;
data['merchantId'] = this.merchantId;
data['profileId'] = this.profileId;
data['isProcessed'] = this.isProcessed;
data['serviceId'] = this.serviceId;
if (this.paymentInfo != null) {
data['paymentInfo'] = this.paymentInfo!.toJson();
}
data['isSuccess'] = this.isSuccess;
return data;
}
}
class PaymentInfo {
String? cardScheme;
String? cardType;
int? expiryMonth;
int? expiryYear;
String? paymentDescription;
String? paymentMethod;
PaymentInfo(
{this.cardScheme,
this.cardType,
this.expiryMonth,
this.expiryYear,
this.paymentDescription,
this.paymentMethod});
PaymentInfo.fromJson(Map<String, dynamic> json) {
cardScheme = json['cardScheme'];
cardType = json['cardType'];
expiryMonth = json['expiryMonth'];
expiryYear = json['expiryYear'];
paymentDescription = json['paymentDescription'];
paymentMethod = json['payment_method'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['cardScheme'] = this.cardScheme;
data['cardType'] = this.cardType;
data['expiryMonth'] = this.expiryMonth;
data['expiryYear'] = this.expiryYear;
data['paymentDescription'] = this.paymentDescription;
data['payment_method'] = this.paymentMethod;
return data;
}
}

@ -0,0 +1,23 @@
import 'package:dartz/dartz.dart';
import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart';
abstract class PaytabsService {
Future<Either<Failure, dynamic>> getAskDoctorAppointmentsList();
Future<Either<Failure, dynamic>> getDoctorResponse();
}
class PaytabsServiceImp implements PaytabsService {
@override
Future<Either<Failure, dynamic>> getAskDoctorAppointmentsList() {
// TODO: implement getAskDoctorAppointmentsList
throw UnimplementedError();
}
@override
Future<Either<Failure, dynamic>> getDoctorResponse() {
// TODO: implement getDoctorResponse
throw UnimplementedError();
}
}

@ -0,0 +1,136 @@
import 'package:flutter/material.dart';
import 'package:flutter_paytabs_bridge/BaseBillingShippingInfo.dart';
import 'package:flutter_paytabs_bridge/IOSThemeConfiguration.dart';
import 'package:flutter_paytabs_bridge/PaymentSdkConfigurationDetails.dart';
import 'package:flutter_paytabs_bridge/PaymentSdkLocale.dart';
import 'package:flutter_paytabs_bridge/PaymentSdkTokeniseType.dart';
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/enums.dart';
import 'package:hmg_patient_app_new/core/paytabs_config.dart';
import 'package:hmg_patient_app_new/features/paytabs/models/paytabs_transaction_response_model.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
class PayTabsViewModel extends ChangeNotifier {
late PaymentSdkConfigurationDetails paymentConfiguration;
final AppState appState;
late PaytabsTransactionResponseModel paytabsTransactionResponseModel;
PayTabsViewModel({
required this.appState,
});
setPaymentConfiguration(String paymentDescription, num amount) {
String cartID = DateTime.now().millisecondsSinceEpoch.toString();
paymentConfiguration = PaymentSdkConfigurationDetails(
profileId: PaymentSdkDefaultConfig.profileID,
serverKey: PaymentSdkDefaultConfig.serverKey,
clientKey: PaymentSdkDefaultConfig.clientKey,
transactionType: PaymentSdkTransactionType.SALE,
cartId: cartID.substring((cartID.length - 5), cartID.length),
cartDescription: paymentDescription,
merchantName: PaymentSdkDefaultConfig.defaultMerchantName,
locale: appState.isArabic() ? PaymentSdkLocale.AR : PaymentSdkLocale.EN,
merchantApplePayIndentifier:
ApiConsts.appEnvironmentType == AppEnvironmentTypeEnum.uat ? PaymentSdkDefaultConfig.defaultMerchantAppleBundleIDUAT : PaymentSdkDefaultConfig.defaultMerchantAppleBundleID,
// merchantApplePayIndentifier: PaymentSdkDefaultConfig.defaultMerchantAppleBundleIDUAT,
screentTitle: paymentDescription,
amount: amount.toDouble(),
showBillingInfo: false,
forceShippingInfo: false,
currencyCode: PaymentSdkDefaultConfig.defaultCurrency,
merchantCountryCode: PaymentSdkDefaultConfig.defaultMerchantCountryCode,
billingDetails: BillingDetails(
"${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}" ?? "HMG Patient", "CustID_${appState.getAuthenticatedUser()!.patientId.toString()}@HMG.com",
appState.getAuthenticatedUser()!.mobileNumber ?? "0000000000", "Riyadh", "SA", "Riyadh", "Riyadh", "12626"),
shippingDetails: ShippingDetails("${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}" ?? "HMG Patient", "CustID_${appState.getAuthenticatedUser()!.patientId.toString()}@HMG.com",
appState.getAuthenticatedUser()!.mobileNumber ?? "0000000000", "Riyadh", "SA", "Riyadh", "Riyadh", "12626"),
alternativePaymentMethods: [],
linkBillingNameWithCardHolderName: true,
simplifyApplePayValidation: true);
paymentConfiguration.iOSThemeConfigurations = IOSThemeConfigurations(
// logoImage: "assets/images/png/hmg_logo.png",
backgroundColor: "ffffff",
backgroundColorDark: "ffffff",
secondaryColor: "191919",
secondaryColorDark: "191919",
primaryColor: "ffffff",
primaryColorDark: "ffffff",
// primaryFont: "Poppins",
titleFontColor: "2E3039",
titleFontColorDark: "2E3039",
// titleFont: "Poppins",
primaryFontColor: "2E3039",
primaryFontColorDark: "2E3039",
inputFieldBackgroundColor: "ffffff",
inputFieldBackgroundColorDark: "ffffff",
placeholderColor: "2E3039",
placeholderColorDark: "2E3039",
buttonColor: "ED1C2B",
buttonColorDark: "ED1C2B",
// buttonFont: "Poppins",
);
paymentConfiguration.tokeniseType = PaymentSdkTokeniseType.MERCHANT_MANDATORY;
notifyListeners();
}
startApplePayPayment({Function(PaytabsTransactionResponseModel)? onSuccess, Function(String)? onError}) {
FlutterPaytabsBridge.startApplePayPayment(paymentConfiguration, (event) {
final transactionDetails = event["data"] as Map<Object?, Object?>?;
if (event["status"] == "success") {
// 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);
}
} else if (event["status"] == "error") {
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") {
final eventMessage = event["message"] ?? "Event occurred";
debugPrint("Event occurred: $eventMessage");
}
});
}
startCardPayment({Function(PaytabsTransactionResponseModel)? onSuccess, Function(String)? onError}) {
FlutterPaytabsBridge.startCardPayment(paymentConfiguration, (event) {
final transactionDetails = event["data"] as Map<Object?, Object?>?;
if (event["status"] == "success") {
// 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);
}
} else if (event["status"] == "error") {
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") {
final eventMessage = event["message"] ?? "Event occurred";
debugPrint("Event occurred: $eventMessage");
}
});
}
}

@ -347,8 +347,11 @@ class PrescriptionsViewModel extends ChangeNotifier {
result.fold(
(failure) async {
isPrescriptionsDeliveryOrdersLoading = false;
prescriptionsOrderList.clear();
notifyListeners();
onError!(failure.message);
if(onError != null) {
onError(failure.message);
}
},
(apiResponse) {
if (apiResponse.messageStatus == 2) {

@ -40,7 +40,7 @@ class ProfileSettingsViewModel extends ChangeNotifier {
bool isDeactivateAccountSuccess = false;
String? deactivateAccountError;
late GetPatientInfoForUpdate getPatientInfoForUpdate;
GetPatientInfoForUpdate? getPatientInfoForUpdate;
bool isPatientProfileLoading = false;
// Profile image state

@ -1,4 +1,3 @@
import 'package:hmg_patient_app_new/core/enums.dart';
import 'package:hmg_patient_app_new/features/symptoms_checker/models/organ_model.dart';
@ -37,12 +36,65 @@ class OrganData {
id: 'nose_mouth',
name: 'oral_cavity',
nameAr: 'تجويف_الفم',
description: 'Nose/Mouth',
descriptionAr: 'الأنف/الفم',
description: 'Mouth',
descriptionAr: 'الفم',
bodyView: BodyView.front,
position: OrganPosition(x: 0.50, y: 0.135),
),
// ========== EARS (NEW) ==========
OrganModel(
id: 'left_ear',
name: 'ears',
nameAr: 'الأذن',
description: 'Left Ear',
descriptionAr: 'الأذن اليسرى',
bodyView: BodyView.front,
position: OrganPosition(x: 0.4, y: 0.1),
),
OrganModel(
id: 'right_ear',
name: 'ears',
nameAr: 'الأذن',
description: 'Right Ear',
descriptionAr: 'الأذن اليمنى',
bodyView: BodyView.front,
position: OrganPosition(x: 0.64, y: 0.1),
),
// ========== NOSE (NEW) ==========
OrganModel(
id: 'nose',
name: 'nose',
nameAr: 'الأنف',
description: 'Nose',
descriptionAr: 'الأنف',
bodyView: BodyView.front,
position: OrganPosition(x: 0.50, y: 0.09),
position: OrganPosition(x: 0.50, y: 0.095),
),
// ========== HEAD SPECIFIC (NEW) ==========
// OrganModel(
// id: 'front_head',
// name: 'head',
// nameAr: 'الرأس',
// description: 'Front Head',
// descriptionAr: 'مقدمة الرأس',
// bodyView: BodyView.front,
// position: OrganPosition(x: 0.50, y: 0.04),
// ),
// // ========== BEHAVIOR / THOUGHTS (NEW) ==========
// OrganModel(
// id: 'behavior_thoughts',
// name: 'problem_concerning_behavior_or_thoughts',
// nameAr: 'مشكلة_سلوكية_أو_نفسية',
// description: 'Behavior / Thoughts',
// descriptionAr: 'سلوك / أفكار',
// bodyView: BodyView.front,
// position: OrganPosition(x: 0.50, y: 0.02),
// ),
// ========== NECK ==========
OrganModel(
id: 'throat',
@ -51,7 +103,27 @@ class OrganData {
description: 'Throat',
descriptionAr: 'الحلق',
bodyView: BodyView.front,
position: OrganPosition(x: 0.50, y: 0.15),
position: OrganPosition(x: 0.50, y: 0.17),
),
// ========== HALF NECK (NEW) ==========
OrganModel(
id: 'left_halfneck',
name: 'halfneck',
nameAr: 'نصف_الرقبة',
description: 'Left Half Neck',
descriptionAr: 'نصف الرقبة الأيسر',
bodyView: BodyView.front,
position: OrganPosition(x: 0.42, y: 0.15),
),
OrganModel(
id: 'right_halfneck',
name: 'halfneck',
nameAr: 'نصف_الرقبة',
description: 'Right Half Neck',
descriptionAr: 'نصف الرقبة الأيمن',
bodyView: BodyView.front,
position: OrganPosition(x: 0.58, y: 0.15),
),
// ========== SHOULDERS ==========
@ -74,6 +146,26 @@ class OrganData {
position: OrganPosition(x: 0.8, y: 0.22),
),
// ========== UPPER LIMB (NEW) ==========
OrganModel(
id: 'left_upper_limb',
name: 'upper_limb',
nameAr: 'الطرف_العلوي',
description: 'Left Upper Limb',
descriptionAr: 'الطرف العلوي الأيسر',
bodyView: BodyView.front,
position: OrganPosition(x: 0.20, y: 0.30),
),
OrganModel(
id: 'right_upper_limb',
name: 'upper_limb',
nameAr: 'الطرف_العلوي',
description: 'Right Upper Limb',
descriptionAr: 'الطرف العلوي الأيمن',
bodyView: BodyView.front,
position: OrganPosition(x: 0.80, y: 0.30),
),
// ========== CHEST ==========
OrganModel(
id: 'left_chest',
@ -152,6 +244,28 @@ class OrganData {
position: OrganPosition(x: 0.51, y: 0.44),
),
// ========== ABDOMEN GENERAL (NEW) ==========
// OrganModel(
// id: 'abdomin',
// name: 'abdomin',
// nameAr: 'البطن_العام',
// description: 'Abdomen',
// descriptionAr: 'البطن',
// bodyView: BodyView.front,
// position: OrganPosition(x: 0.51, y: 0.36),
// ),
// ========== SKIN (NEW) ==========
// OrganModel(
// id: 'skin',
// name: 'skin',
// nameAr: 'الجلد',
// description: 'Skin',
// descriptionAr: 'الجلد',
// bodyView: BodyView.front,
// position: OrganPosition(x: 0.50, y: 0.50),
// ),
// ========== PELVIS ==========
OrganModel(
id: 'left_groin',
@ -173,7 +287,6 @@ class OrganData {
),
// ========== LEFT ARM ==========
OrganModel(
id: 'left_elbow',
name: 'elbow',
@ -203,7 +316,6 @@ class OrganData {
),
// ========== RIGHT ARM ==========
OrganModel(
id: 'right_elbow',
name: 'elbow',
@ -307,7 +419,69 @@ class OrganData {
bodyView: BodyView.front,
position: OrganPosition(x: 0.57, y: 0.91),
),
// ========== UPPER LEG (NEW) ==========
OrganModel(
id: 'left_upperleg',
name: 'upperleg',
nameAr: 'الجزء_العلوي_من_الساق',
description: 'Left Upper Leg',
descriptionAr: 'الجزء العلوي من الساق الأيسر',
bodyView: BodyView.front,
position: OrganPosition(x: 0.40, y: 0.63),
),
OrganModel(
id: 'right_upperleg',
name: 'upperleg',
nameAr: 'الجزء_العلوي_من_الساق',
description: 'Right Upper Leg',
descriptionAr: 'الجزء العلوي من الساق الأيمن',
bodyView: BodyView.front,
position: OrganPosition(x: 0.60, y: 0.63),
),
// ========== LOWER LEG lowerleg key (NEW) ==========
OrganModel(
id: 'left_lowerleg',
name: 'lowerleg',
nameAr: 'الجزء_السفلي_من_الساق',
description: 'Left Lower Leg',
descriptionAr: 'الجزء السفلي من الساق الأيسر',
bodyView: BodyView.front,
position: OrganPosition(x: 0.41, y: 0.78),
),
OrganModel(
id: 'right_lowerleg',
name: 'lowerleg',
nameAr: 'الجزء_السفلي_من_الساق',
description: 'Right Lower Leg',
descriptionAr: 'الجزء السفلي من الساق الأيمن',
bodyView: BodyView.front,
position: OrganPosition(x: 0.57, y: 0.78),
),
// ========== LOWER LIMB (NEW) ==========
OrganModel(
id: 'left_lower_limb',
name: 'lower_limb',
nameAr: 'الطرف_السفلي',
description: 'Left Lower Limb',
descriptionAr: 'الطرف السفلي الأيسر',
bodyView: BodyView.front,
position: OrganPosition(x: 0.40, y: 0.67),
),
OrganModel(
id: 'right_lower_limb',
name: 'lower_limb',
nameAr: 'الطرف_السفلي',
description: 'Right Lower Limb',
descriptionAr: 'الطرف السفلي الأيمن',
bodyView: BodyView.front,
position: OrganPosition(x: 0.60, y: 0.67),
),
];
static const List<OrganModel> frontViewOrgansForFemale = [
// ========== HEAD & FACE ==========
OrganModel(
@ -341,12 +515,65 @@ class OrganData {
id: 'nose_mouth',
name: 'oral_cavity',
nameAr: 'تجويف_الفم',
description: 'Nose/Mouth',
description: 'Mouth',
descriptionAr: 'الأنف/الفم',
bodyView: BodyView.front,
position: OrganPosition(x: 0.54, y: 0.12),
position: OrganPosition(x: 0.54, y: 0.14),
),
// ========== EARS (NEW) ==========
OrganModel(
id: 'left_ear',
name: 'ears',
nameAr: 'الأذن',
description: 'Left Ear',
descriptionAr: 'الأذن اليسرى',
bodyView: BodyView.front,
position: OrganPosition(x: 0.43, y: 0.12),
),
OrganModel(
id: 'right_ear',
name: 'ears',
nameAr: 'الأذن',
description: 'Right Ear',
descriptionAr: 'الأذن اليمنى',
bodyView: BodyView.front,
position: OrganPosition(x: 0.656, y: 0.12),
),
// ========== NOSE (NEW) ==========
OrganModel(
id: 'nose',
name: 'nose',
nameAr: 'الأنف',
description: 'Nose',
descriptionAr: 'الأنف',
bodyView: BodyView.front,
position: OrganPosition(x: 0.54, y: 0.115),
),
// ========== HEAD SPECIFIC (NEW) ==========
OrganModel(
id: 'front_head',
name: 'head',
nameAr: 'الرأس',
description: 'Front Head',
descriptionAr: 'مقدمة الرأس',
bodyView: BodyView.front,
position: OrganPosition(x: 0.54, y: 0.05),
),
// // ========== BEHAVIOR / THOUGHTS (NEW) ==========
// OrganModel(
// id: 'behavior_thoughts',
// name: 'problem_concerning_behavior_or_thoughts',
// nameAr: 'مشكلة_سلوكية_أو_نفسية',
// description: 'Behavior / Thoughts',
// descriptionAr: 'سلوك / أفكار',
// bodyView: BodyView.front,
// position: OrganPosition(x: 0.54, y: 0.02),
// ),
// ========== NECK ==========
OrganModel(
id: 'throat',
@ -358,6 +585,26 @@ class OrganData {
position: OrganPosition(x: 0.54, y: 0.17),
),
// ========== HALF NECK (NEW) ==========
OrganModel(
id: 'left_halfneck',
name: 'halfneck',
nameAr: 'نصف_الرقبة',
description: 'Left Half Neck',
descriptionAr: 'نصف الرقبة الأيسر',
bodyView: BodyView.front,
position: OrganPosition(x: 0.48, y: 0.175),
),
OrganModel(
id: 'right_halfneck',
name: 'halfneck',
nameAr: 'نصف_الرقبة',
description: 'Right Half Neck',
descriptionAr: 'نصف الرقبة الأيمن',
bodyView: BodyView.front,
position: OrganPosition(x: 0.60, y: 0.175),
),
// ========== SHOULDERS ==========
OrganModel(
id: 'left_shoulder',
@ -378,6 +625,26 @@ class OrganData {
position: OrganPosition(x: 0.75, y: 0.22),
),
// ========== UPPER LIMB (NEW) ==========
OrganModel(
id: 'left_upper_limb',
name: 'upper_limb',
nameAr: 'الطرف_العلوي',
description: 'Left Upper Limb',
descriptionAr: 'الطرف العلوي الأيسر',
bodyView: BodyView.front,
position: OrganPosition(x: 0.26, y: 0.30),
),
OrganModel(
id: 'right_upper_limb',
name: 'upper_limb',
nameAr: 'الطرف_العلوي',
description: 'Right Upper Limb',
descriptionAr: 'الطرف العلوي الأيمن',
bodyView: BodyView.front,
position: OrganPosition(x: 0.76, y: 0.30),
),
// ========== CHEST ==========
OrganModel(
id: 'left_chest',
@ -407,6 +674,26 @@ class OrganData {
position: OrganPosition(x: 0.65, y: 0.22),
),
// ========== BREASTS (NEW female only) ==========
OrganModel(
id: 'left_breast',
name: 'breasts',
nameAr: 'الثدي',
description: 'Left Breast',
descriptionAr: 'الثدي الأيسر',
bodyView: BodyView.front,
position: OrganPosition(x: 0.4, y: 0.27),
),
OrganModel(
id: 'right_breast',
name: 'breasts',
nameAr: 'الثدي',
description: 'Right Breast',
descriptionAr: 'الثدي الأيمن',
bodyView: BodyView.front,
position: OrganPosition(x: 0.62, y: 0.27),
),
// ========== RIBS ==========
OrganModel(
id: 'left_ribs',
@ -456,6 +743,28 @@ class OrganData {
position: OrganPosition(x: 0.51, y: 0.44),
),
// ========== ABDOMEN GENERAL (NEW) ==========
// OrganModel(
// id: 'abdomin',
// name: 'abdomin',
// nameAr: 'البطن_العام',
// description: 'Abdomen',
// descriptionAr: 'البطن',
// bodyView: BodyView.front,
// position: OrganPosition(x: 0.54, y: 0.36),
// ),
// ========== SKIN (NEW) ==========
// OrganModel(
// id: 'skin',
// name: 'skin',
// nameAr: 'الجلد',
// description: 'Skin',
// descriptionAr: 'الجلد',
// bodyView: BodyView.front,
// position: OrganPosition(x: 0.54, y: 0.50),
// ),
// ========== PELVIS ==========
OrganModel(
id: 'left_groin',
@ -477,7 +786,6 @@ class OrganData {
),
// ========== LEFT ARM ==========
OrganModel(
id: 'left_elbow',
name: 'elbow',
@ -507,7 +815,6 @@ class OrganData {
),
// ========== RIGHT ARM ==========
OrganModel(
id: 'right_elbow',
name: 'elbow',
@ -611,6 +918,68 @@ class OrganData {
bodyView: BodyView.front,
position: OrganPosition(x: 0.57, y: 0.91),
),
// ========== UPPER LEG (NEW) ==========
OrganModel(
id: 'left_upperleg',
name: 'upperleg',
nameAr: 'الجزء_العلوي_من_الساق',
description: 'Left Upper Leg',
descriptionAr: 'الجزء العلوي من الساق الأيسر',
bodyView: BodyView.front,
position: OrganPosition(x: 0.40, y: 0.63),
),
OrganModel(
id: 'right_upperleg',
name: 'upperleg',
nameAr: 'الجزء_العلوي_من_الساق',
description: 'Right Upper Leg',
descriptionAr: 'الجزء العلوي من الساق الأيمن',
bodyView: BodyView.front,
position: OrganPosition(x: 0.65, y: 0.63),
),
// ========== LOWER LEG lowerleg key (NEW) ==========
OrganModel(
id: 'left_lowerleg',
name: 'lowerleg',
nameAr: 'الجزء_السفلي_من_الساق',
description: 'Left Lower Leg',
descriptionAr: 'الجزء السفلي من الساق الأيسر',
bodyView: BodyView.front,
position: OrganPosition(x: 0.41, y: 0.78),
),
OrganModel(
id: 'right_lowerleg',
name: 'lowerleg',
nameAr: 'الجزء_السفلي_من_الساق',
description: 'Right Lower Leg',
descriptionAr: 'الجزء السفلي من الساق الأيمن',
bodyView: BodyView.front,
position: OrganPosition(x: 0.59, y: 0.78),
),
// ========== LOWER LIMB (NEW) ==========
OrganModel(
id: 'left_lower_limb',
name: 'lower_limb',
nameAr: 'الطرف_السفلي',
description: 'Left Lower Limb',
descriptionAr: 'الطرف السفلي الأيسر',
bodyView: BodyView.front,
position: OrganPosition(x: 0.40, y: 0.67),
),
OrganModel(
id: 'right_lower_limb',
name: 'lower_limb',
nameAr: 'الطرف_السفلي',
description: 'Right Lower Limb',
descriptionAr: 'الطرف السفلي الأيمن',
bodyView: BodyView.front,
position: OrganPosition(x: 0.65, y: 0.67),
),
];
// ==================== BACK VIEW ====================
@ -742,6 +1111,16 @@ class OrganData {
position: OrganPosition(x: 0.60, y: 0.43),
),
// ========== ANUS (NEW) ==========
OrganModel(
id: 'anus',
name: 'anus',
nameAr: 'فتحة_الشرج',
description: 'Anus',
descriptionAr: 'فتحة الشرج',
bodyView: BodyView.back,
position: OrganPosition(x: 0.525, y: 0.5),
),
// ========== GLUTES ==========
OrganModel(
id: 'sacrum',
@ -750,7 +1129,7 @@ class OrganData {
description: 'Sacrum',
descriptionAr: 'العجز',
bodyView: BodyView.back,
position: OrganPosition(x: 0.50, y: 0.52),
position: OrganPosition(x: 0.50, y: 0.46),
),
OrganModel(
id: 'left_glute',
@ -809,6 +1188,7 @@ class OrganData {
position: OrganPosition(x: 0.57, y: 0.79),
),
];
static const List<OrganModel> backViewOrgansForFemale = [
// ========== HEAD & NECK ==========
OrganModel(
@ -879,6 +1259,17 @@ class OrganData {
position: OrganPosition(x: 0.62, y: 0.28),
),
// // ========== SKIN BACK (NEW) ==========
// OrganModel(
// id: 'skin_back',
// name: 'skin',
// nameAr: 'الجلد',
// description: 'Skin',
// descriptionAr: 'الجلد',
// bodyView: BodyView.back,
// position: OrganPosition(x: 0.50, y: 0.30),
// ),
// ========== MID BACK ==========
OrganModel(
id: 'mid_spine',
@ -945,7 +1336,7 @@ class OrganData {
description: 'Sacrum',
descriptionAr: 'العجز',
bodyView: BodyView.back,
position: OrganPosition(x: 0.50, y: 0.5),
position: OrganPosition(x: 0.50, y: 0.47),
),
OrganModel(
id: 'left_glute',
@ -1010,7 +1401,6 @@ class OrganData {
static List<OrganModel> getOrgansForView(BodyView view, {String? genderKey}) {
final bool isFemale = genderKey != null && (genderKey.toLowerCase() == 'female' || genderKey == 'أنثى' || genderKey == '2');
if (view == BodyView.front) {
return isFemale ? frontViewOrgansForFemale : frontViewOrgansForMale;
} else {
@ -1039,4 +1429,4 @@ class OrganData {
return null;
}
}
}
}

@ -51,8 +51,12 @@ class TodoSectionViewModel extends ChangeNotifier {
// if (apiResponse.messageStatus == 2) {
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
// } else if (apiResponse.messageStatus == 1) {
notificationsCount =
apiResponse['List_PatientDashboard'][0]['UnreadPatientNotificationCount'] > 99 ? '99+' : apiResponse['List_PatientDashboard'][0]['UnreadPatientNotificationCount'].toString();
if (apiResponse['List_PatientDashboard'] != null && apiResponse['List_PatientDashboard'] is List && (apiResponse['List_PatientDashboard'] as List).isNotEmpty) {
notificationsCount =
apiResponse['List_PatientDashboard'][0]['UnreadPatientNotificationCount'] > 99 ? '99+' : apiResponse['List_PatientDashboard'][0]['UnreadPatientNotificationCount'].toString();
} else {
notificationsCount = "0";
}
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);

@ -1838,5 +1838,10 @@ abstract class LocaleKeys {
static const fileSizeExceedsLimit = 'fileSizeExceedsLimit';
static const completedPrescriptionOrder = 'completedPrescriptionOrder';
static const submitEReferral = 'submitEReferral';
static const redeem = 'redeem';
static const points = 'points';
static const transactions = 'transactions';
static const pharmacy = 'pharmacy';
static const visitsOrders = 'visitsOrders';
}

@ -10,6 +10,7 @@ import 'package:flutter/services.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/features/paytabs/paytabs_view_model.dart';
import 'package:hmg_patient_app_new/features/weather/weather_view_model.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/features/ask_doctor/ask_doctor_view_model.dart';
@ -270,6 +271,8 @@ void main() async {
create: (_) => getIt.get<WeatherMonitorViewModel>(),
), ChangeNotifierProvider<DateRangCalenderModel>(
create: (_) => getIt.get<DateRangCalenderModel>(),
), ChangeNotifierProvider<PayTabsViewModel>(
create: (_) => getIt.get<PayTabsViewModel>(),
)
], child: MyApp()),
), // Wrap your app

@ -838,40 +838,42 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
iconSize: 36.w,
isLocked: false,
).toShimmer2()
: MedicalFileCard(
label: !((myAppointmentsVM.appointmentRatedResponseModel!).isAllowedToRate ?? false)
? LocaleKeys.ratingSubmitted.tr(context: context)
: LocaleKeys.doctorRatingAppointment.tr(context: context),
textColor: AppColors.blackColor,
backgroundColor: AppColors.whiteColor,
svgIcon: AppAssets.appointmentRatingIcon,
isLargeText: true,
iconSize: 36.w,
isLocked: !(myAppointmentsVM.appointmentRatedResponseModel!.isAllowedToRate ?? false),
).onPress(() {
if ((myAppointmentsVM.appointmentRatedResponseModel!.isAllowedToRate ?? true)) {
showCommonBottomSheetWithoutHeight(
context,
title: LocaleKeys.doctorRating.tr(context: context),
isCloseButtonVisible: true,
child: StatefulBuilder(
builder: (context, setState) {
return AppointmentRatingWidget(
patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel,
);
},
),
callBackFunc: () {
myAppointmentsViewModel.getDoctorsRatingCheck(
doctorID: widget.patientAppointmentHistoryResponseModel.doctorID,
clinicID: widget.patientAppointmentHistoryResponseModel.clinicID,
projectID: widget.patientAppointmentHistoryResponseModel.projectID,
appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo);
},
isFullScreen: false,
);
}
}),
: myAppointmentsVM.appointmentRatedResponseModel != null
? MedicalFileCard(
label: !(myAppointmentsVM.appointmentRatedResponseModel!.isAllowedToRate ?? false)
? LocaleKeys.ratingSubmitted.tr(context: context)
: LocaleKeys.doctorRatingAppointment.tr(context: context),
textColor: AppColors.blackColor,
backgroundColor: AppColors.whiteColor,
svgIcon: AppAssets.appointmentRatingIcon,
isLargeText: true,
iconSize: 36.w,
isLocked: !(myAppointmentsVM.appointmentRatedResponseModel!.isAllowedToRate ?? false),
).onPress(() {
if (myAppointmentsVM.appointmentRatedResponseModel?.isAllowedToRate ?? false) {
showCommonBottomSheetWithoutHeight(
context,
title: LocaleKeys.doctorRating.tr(context: context),
isCloseButtonVisible: true,
child: StatefulBuilder(
builder: (context, setState) {
return AppointmentRatingWidget(
patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel,
);
},
),
callBackFunc: () {
myAppointmentsViewModel.getDoctorsRatingCheck(
doctorID: widget.patientAppointmentHistoryResponseModel.doctorID,
clinicID: widget.patientAppointmentHistoryResponseModel.clinicID,
projectID: widget.patientAppointmentHistoryResponseModel.projectID,
appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo);
},
isFullScreen: false,
);
}
})
: SizedBox.shrink(),
],
);
}),

@ -17,6 +17,8 @@ import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/payfort/models/apple_pay_request_insert_model.dart';
import 'package:hmg_patient_app_new/features/payfort/payfort_view_model.dart';
import 'package:hmg_patient_app_new/features/paytabs/models/paytabs_transaction_response_model.dart';
import 'package:hmg_patient_app_new/features/paytabs/paytabs_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart';
import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.dart';
@ -43,6 +45,7 @@ class _AppointmentPaymentPageState extends State<AppointmentPaymentPage> {
late MyAppointmentsViewModel myAppointmentsViewModel;
late PayfortViewModel payfortViewModel;
late AppState appState;
late PayTabsViewModel paytabsViewModel;
MyInAppBrowser? browser;
String selectedPaymentMethod = "";
@ -83,6 +86,7 @@ class _AppointmentPaymentPageState extends State<AppointmentPaymentPage> {
appState = getIt.get<AppState>();
myAppointmentsViewModel = Provider.of<MyAppointmentsViewModel>(context);
payfortViewModel = Provider.of<PayfortViewModel>(context);
paytabsViewModel = Provider.of<PayTabsViewModel>(context, listen: false);
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: Consumer<MyAppointmentsViewModel>(builder: (context, myAppointmentsVM, child) {
@ -130,7 +134,86 @@ class _AppointmentPaymentPageState extends State<AppointmentPaymentPage> {
).paddingSymmetrical(16.h, 16.h),
).paddingSymmetrical(24.h, 0.h).onPress(() {
selectedPaymentMethod = "MADA";
openPaymentURL("mada");
if (appState.isPaytabsEnabled) {
paytabsViewModel.setPaymentConfiguration(
"Appointment Payment",
num.parse(myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString()),
);
paytabsViewModel.startCardPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr(context: context));
await myAppointmentsViewModel.createAdvancePayment(
paymentMethodName: selectedPaymentMethod,
projectID: widget.patientAppointmentHistoryResponseModel.projectID,
clinicID: widget.patientAppointmentHistoryResponseModel.clinicID,
appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(),
payedAmount: num.parse(myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString()),
paymentReference: transactionData.transactionReference!,
patientID: appState.getAuthenticatedUser()!.patientId.toString(),
patientType: appState.getAuthenticatedUser()!.patientType!,
onSuccess: (value) async {
print(value);
await myAppointmentsViewModel.addAdvanceNumberRequest(
advanceNumber: Utils.isVidaPlusProject(widget.patientAppointmentHistoryResponseModel.projectID)
? value.data['OnlineCheckInAppointments'][0]['AdvanceNumber_VP'].toString()
: value.data['OnlineCheckInAppointments'][0]['AdvanceNumber'].toString(),
paymentReference: transactionData.transactionReference!,
appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(),
onSuccess: (value) async {
if (widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment!) {
//TODO: Implement LiveCare Check-In API Call
await myAppointmentsViewModel.insertLiveCareVIDARequest(
clientRequestID: transID,
patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel,
onSuccess: (apiResponse) {
Future.delayed(Duration(milliseconds: 500), () {
myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true);
myAppointmentsViewModel.initAppointmentsViewModel();
myAppointmentsViewModel.getPatientAppointments(true, false);
LoaderBottomSheet.hideLoader();
Navigator.pushAndRemoveUntil(
context,
CustomPageRoute(
page: LandingNavigation(),
),
(r) => false);
});
},
onError: (error) {});
} else {
await myAppointmentsViewModel.generateAppointmentQR(
clinicID: widget.patientAppointmentHistoryResponseModel.clinicID,
projectID: widget.patientAppointmentHistoryResponseModel.projectID,
appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(),
isFollowUp: myAppointmentsViewModel.patientAppointmentShareResponseModel!.isFollowup!,
onSuccess: (apiResponse) {
Future.delayed(Duration(milliseconds: 500), () {
myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true);
myAppointmentsViewModel.initAppointmentsViewModel();
myAppointmentsViewModel.getPatientAppointments(true, false);
LoaderBottomSheet.hideLoader();
Navigator.pushAndRemoveUntil(
context,
CustomPageRoute(
page: LandingNavigation(),
),
(r) => false);
});
});
}
});
});
}, onError: (err) {
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: err.toString()),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
} else {
openPaymentURL("mada");
}
}),
SizedBox(height: 16.h),
Container(
@ -174,7 +257,86 @@ class _AppointmentPaymentPageState extends State<AppointmentPaymentPage> {
).paddingSymmetrical(16.h, 16.h),
).paddingSymmetrical(24.h, 0.h).onPress(() {
selectedPaymentMethod = "VISA";
openPaymentURL("visa");
if (appState.isPaytabsEnabled) {
paytabsViewModel.setPaymentConfiguration(
"Appointment Payment",
num.parse(myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString()),
);
paytabsViewModel.startCardPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr(context: context));
await myAppointmentsViewModel.createAdvancePayment(
paymentMethodName: selectedPaymentMethod,
projectID: widget.patientAppointmentHistoryResponseModel.projectID,
clinicID: widget.patientAppointmentHistoryResponseModel.clinicID,
appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(),
payedAmount: num.parse(myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString()),
paymentReference: transactionData.transactionReference!,
patientID: appState.getAuthenticatedUser()!.patientId.toString(),
patientType: appState.getAuthenticatedUser()!.patientType!,
onSuccess: (value) async {
print(value);
await myAppointmentsViewModel.addAdvanceNumberRequest(
advanceNumber: Utils.isVidaPlusProject(widget.patientAppointmentHistoryResponseModel.projectID)
? value.data['OnlineCheckInAppointments'][0]['AdvanceNumber_VP'].toString()
: value.data['OnlineCheckInAppointments'][0]['AdvanceNumber'].toString(),
paymentReference: transactionData.transactionReference!,
appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(),
onSuccess: (value) async {
if (widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment!) {
//TODO: Implement LiveCare Check-In API Call
await myAppointmentsViewModel.insertLiveCareVIDARequest(
clientRequestID: transID,
patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel,
onSuccess: (apiResponse) {
Future.delayed(Duration(milliseconds: 500), () {
myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true);
myAppointmentsViewModel.initAppointmentsViewModel();
myAppointmentsViewModel.getPatientAppointments(true, false);
LoaderBottomSheet.hideLoader();
Navigator.pushAndRemoveUntil(
context,
CustomPageRoute(
page: LandingNavigation(),
),
(r) => false);
});
},
onError: (error) {});
} else {
await myAppointmentsViewModel.generateAppointmentQR(
clinicID: widget.patientAppointmentHistoryResponseModel.clinicID,
projectID: widget.patientAppointmentHistoryResponseModel.projectID,
appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(),
isFollowUp: myAppointmentsViewModel.patientAppointmentShareResponseModel!.isFollowup!,
onSuccess: (apiResponse) {
Future.delayed(Duration(milliseconds: 500), () {
myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true);
myAppointmentsViewModel.initAppointmentsViewModel();
myAppointmentsViewModel.getPatientAppointments(true, false);
LoaderBottomSheet.hideLoader();
Navigator.pushAndRemoveUntil(
context,
CustomPageRoute(
page: LandingNavigation(),
),
(r) => false);
});
});
}
});
});
}, onError: (err) {
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: err.toString()),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
} else {
openPaymentURL("visa");
}
}),
SizedBox(height: 16.h),
isShowTamara
@ -632,37 +794,117 @@ class _AppointmentPaymentPageState extends State<AppointmentPaymentPage> {
//TODO: Need to pass dynamic params to the Apple Pay instead of static values
await payfortViewModel.applePayRequestInsert(applePayInsertRequest: applePayInsertRequest).then((value) {
payfortViewModel.paymentWithApplePay(
customerName: "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}",
// customerEmail: projectViewModel.authenticatedUserObject.user.emailAddress,
customerEmail: "CustID_${appState.getAuthenticatedUser()!.patientId.toString()}@HMG.com",
orderDescription: "Appointment Payment",
orderAmount: double.parse(myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString()),
merchantReference: transID,
merchantIdentifier: payfortViewModel.payfortProjectDetailsRespModel!.merchantIdentifier,
applePayAccessCode: payfortViewModel.payfortProjectDetailsRespModel!.accessCode,
applePayShaRequestPhrase: payfortViewModel.payfortProjectDetailsRespModel!.shaRequest,
currency: appState.getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED",
onFailed: (failureResult) async {
log("failureResult: ${failureResult.message.toString()}");
LoaderBottomSheet.hideLoader();
if (appState.isPaytabsEnabled) {
LoaderBottomSheet.hideLoader();
paytabsViewModel.setPaymentConfiguration(
"Appointment Payment",
num.parse(myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString()),
);
paytabsViewModel.startApplePayPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr(context: context));
await myAppointmentsViewModel.createAdvancePayment(
paymentMethodName: selectedPaymentMethod,
projectID: widget.patientAppointmentHistoryResponseModel.projectID,
clinicID: widget.patientAppointmentHistoryResponseModel.clinicID,
appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(),
payedAmount: num.parse(myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString()),
paymentReference: transactionData.transactionReference!,
patientID: appState.getAuthenticatedUser()!.patientId.toString(),
patientType: appState.getAuthenticatedUser()!.patientType!,
onSuccess: (value) async {
print(value);
await myAppointmentsViewModel.addAdvanceNumberRequest(
advanceNumber: Utils.isVidaPlusProject(widget.patientAppointmentHistoryResponseModel.projectID)
? value.data['OnlineCheckInAppointments'][0]['AdvanceNumber_VP'].toString()
: value.data['OnlineCheckInAppointments'][0]['AdvanceNumber'].toString(),
paymentReference: transactionData.transactionReference!,
appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(),
onSuccess: (value) async {
if (widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment!) {
//TODO: Implement LiveCare Check-In API Call
await myAppointmentsViewModel.insertLiveCareVIDARequest(
clientRequestID: transID,
patientAppointmentHistoryResponseModel: widget.patientAppointmentHistoryResponseModel,
onSuccess: (apiResponse) {
Future.delayed(Duration(milliseconds: 500), () {
myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true);
myAppointmentsViewModel.initAppointmentsViewModel();
myAppointmentsViewModel.getPatientAppointments(true, false);
LoaderBottomSheet.hideLoader();
Navigator.pushAndRemoveUntil(
context,
CustomPageRoute(
page: LandingNavigation(),
),
(r) => false);
});
},
onError: (error) {});
} else {
await myAppointmentsViewModel.generateAppointmentQR(
clinicID: widget.patientAppointmentHistoryResponseModel.clinicID,
projectID: widget.patientAppointmentHistoryResponseModel.projectID,
appointmentNo: widget.patientAppointmentHistoryResponseModel.appointmentNo.toString(),
isFollowUp: myAppointmentsViewModel.patientAppointmentShareResponseModel!.isFollowup!,
onSuccess: (apiResponse) {
Future.delayed(Duration(milliseconds: 500), () {
myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true);
myAppointmentsViewModel.initAppointmentsViewModel();
myAppointmentsViewModel.getPatientAppointments(true, false);
LoaderBottomSheet.hideLoader();
Navigator.pushAndRemoveUntil(
context,
CustomPageRoute(
page: LandingNavigation(),
),
(r) => false);
});
});
}
});
});
}, onError: (err) {
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: failureResult.message.toString()),
child: Utils.getErrorWidget(loadingText: err.toString()),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
},
onSucceeded: (successResult) async {
LoaderBottomSheet.hideLoader();
log("successResult: ${successResult.responseMessage.toString()}");
selectedPaymentMethod = successResult.paymentOption ?? "VISA";
checkPaymentStatus();
},
// projectId: appo.projectID,
// serviceTypeEnum: ServiceTypeEnum.appointmentPayment,
);
});
} else {
payfortViewModel.paymentWithApplePay(
customerName: "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}",
// customerEmail: projectViewModel.authenticatedUserObject.user.emailAddress,
customerEmail: "CustID_${appState.getAuthenticatedUser()!.patientId.toString()}@HMG.com",
orderDescription: "Appointment Payment",
orderAmount: double.parse(myAppointmentsViewModel.patientAppointmentShareResponseModel!.patientShareWithTax!.toString()),
merchantReference: transID,
merchantIdentifier: payfortViewModel.payfortProjectDetailsRespModel!.merchantIdentifier,
applePayAccessCode: payfortViewModel.payfortProjectDetailsRespModel!.accessCode,
applePayShaRequestPhrase: payfortViewModel.payfortProjectDetailsRespModel!.shaRequest,
currency: appState.getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED",
onFailed: (failureResult) async {
log("failureResult: ${failureResult.message.toString()}");
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: failureResult.message.toString()),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
},
onSucceeded: (successResult) async {
LoaderBottomSheet.hideLoader();
log("successResult: ${successResult.responseMessage.toString()}");
selectedPaymentMethod = successResult.paymentOption ?? "VISA";
checkPaymentStatus();
},
// projectId: appo.projectID,
// serviceTypeEnum: ServiceTypeEnum.appointmentPayment,
);
}
});
}
}

@ -274,9 +274,8 @@ class _AppointmentCardState extends State<AppointmentCard> {
children: [
(widget.isLoading ? 'Dr' : "${widget.patientAppointmentHistoryResponseModel.doctorTitle}").toText16(isBold: true, maxlines: 1),
Expanded(
child: (widget.isLoading ? 'John Doe' : " ${widget.patientAppointmentHistoryResponseModel.doctorNameObj!.truncate(20)}")
.toText16(isBold: true, maxlines: 2,
textOverflow: TextOverflow.ellipsis, isEnglishOnly: !Utils.isArabicText(widget.patientAppointmentHistoryResponseModel.doctorNameObj ?? "John Doe")),
child: (widget.isLoading ? 'John Doe' : " ${widget.patientAppointmentHistoryResponseModel.doctorNameObj!.truncate(30)}").toText16(
isBold: true, maxlines: 2, textOverflow: TextOverflow.ellipsis, isEnglishOnly: !Utils.isArabicText(widget.patientAppointmentHistoryResponseModel.doctorNameObj ?? "John Doe")),
),
SizedBox(width: 12.w),
(widget.patientAppointmentHistoryResponseModel.doctorNationalityFlagURL != null &&

@ -186,7 +186,6 @@ class LoginScreenState extends State<LoginScreen> {
constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width),
backgroundColor: AppColors.transparent,
child: StatefulBuilder(builder: (BuildContext context, StateSetter setModalState) {
// Helper function to validate phone number with error handling
void validatePhoneAndProceed(OTPTypeEnum otpType) {
// Use ViewModel validation method

@ -170,7 +170,7 @@ class DoctorProfilePage extends StatelessWidget {
icon: AppAssets.doctor_profile_reviews_icon, width: 48.w, height: 48.h, fit: BoxFit.contain, applyThemeColor: false),
SizedBox(height: 16.h),
LocaleKeys.reviews.tr(context: context).toText12(isBold: true, color: AppColors.greyTextColor),
NumberFormat.decimalPattern()
NumberFormat.decimalPattern("en_US")
.format(bookAppointmentsViewModel.doctorsProfileResponseModel.noOfPatientsRate ?? 0)
.toText16(
isBold: true,

@ -444,7 +444,8 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget {
// cameraGranted = newStatuses[Permission.camera]?.isGranted ?? cameraGranted;
// micGranted = newStatuses[Permission.microphone]?.isGranted ?? micGranted;
notifGranted = newStatuses[Permission.notification]?.isGranted ?? notifGranted;
alertWindowGranted = newStatuses[Permission.systemAlertWindow]?.isGranted ?? alertWindowGranted;
// alertWindowGranted = newStatuses[Permission.systemAlertWindow]?.isGranted ?? alertWindowGranted;
alertWindowGranted = true;
// If any requested permission is now permanently denied -> open settings
final newlyPermanent = missing.where((p) => (newStatuses[p]?.isPermanentlyDenied ?? false) || (newStatuses[p]?.isRestricted ?? false)).toList();

@ -19,6 +19,8 @@ import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/payfort/payfort_view_model.dart';
import 'package:hmg_patient_app_new/features/paytabs/models/paytabs_transaction_response_model.dart';
import 'package:hmg_patient_app_new/features/paytabs/paytabs_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/book_appointment/livecare/immediate_livecare_pending_request_page.dart';
import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart';
@ -45,6 +47,7 @@ class _ImmediateLiveCarePaymentPageState extends State<ImmediateLiveCarePaymentP
late ImmediateLiveCareViewModel immediateLiveCareViewModel;
late MyAppointmentsViewModel myAppointmentsViewModel;
late AppState appState;
late PayTabsViewModel paytabsViewModel;
MyInAppBrowser? browser;
String selectedPaymentMethod = "";
@ -77,6 +80,7 @@ class _ImmediateLiveCarePaymentPageState extends State<ImmediateLiveCarePaymentP
myAppointmentsViewModel = Provider.of<MyAppointmentsViewModel>(context, listen: false);
immediateLiveCareViewModel = Provider.of<ImmediateLiveCareViewModel>(context, listen: false);
payfortViewModel = Provider.of<PayfortViewModel>(context, listen: false);
paytabsViewModel = Provider.of<PayTabsViewModel>(context, listen: false);
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: Consumer<MyAppointmentsViewModel>(builder: (context, myAppointmentsVM, child) {
@ -121,9 +125,52 @@ class _ImmediateLiveCarePaymentPageState extends State<ImmediateLiveCarePaymentP
),
],
).paddingSymmetrical(16.h, 16.h),
).paddingSymmetrical(24.h, 0.h).onPress(() {
).paddingSymmetrical(24.h, 0.h).onPress(() async {
selectedPaymentMethod = "MADA";
openPaymentURL("mada");
if (appState.isPaytabsEnabled) {
paytabsViewModel.setPaymentConfiguration(
"LiveCare Payment",
num.parse(immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.total ?? "0.0"),
);
paytabsViewModel.startCardPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr());
await immediateLiveCareViewModel.addNewCallRequestForImmediateLiveCare(transactionData.transactionReference!);
await immediateLiveCareViewModel.getPatientLiveCareHistory();
LoaderBottomSheet.hideLoader();
if (immediateLiveCareViewModel.patientHasPendingLiveCareRequest) {
Navigator.pushAndRemoveUntil(
context,
CustomPageRoute(
page: LandingNavigation(),
),
(r) => false);
Navigator.of(context).push(
CustomPageRoute(
page: ImmediateLiveCarePendingRequestPage(),
),
);
} else {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: "Unknown error occurred..."),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
}
}, onError: (err) {
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: err.toString()),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
} else {
openPaymentURL("mada");
}
}),
SizedBox(height: 16.h),
Container(
@ -165,7 +212,50 @@ class _ImmediateLiveCarePaymentPageState extends State<ImmediateLiveCarePaymentP
).paddingSymmetrical(16.h, 16.h),
).paddingSymmetrical(24.h, 0.h).onPress(() {
selectedPaymentMethod = "VISA";
openPaymentURL("visa");
if (appState.isPaytabsEnabled) {
paytabsViewModel.setPaymentConfiguration(
"LiveCare Payment",
num.parse(immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.total ?? "0.0"),
);
paytabsViewModel.startCardPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr());
await immediateLiveCareViewModel.addNewCallRequestForImmediateLiveCare(transactionData.transactionReference!);
await immediateLiveCareViewModel.getPatientLiveCareHistory();
LoaderBottomSheet.hideLoader();
if (immediateLiveCareViewModel.patientHasPendingLiveCareRequest) {
Navigator.pushAndRemoveUntil(
context,
CustomPageRoute(
page: LandingNavigation(),
),
(r) => false);
Navigator.of(context).push(
CustomPageRoute(
page: ImmediateLiveCarePendingRequestPage(),
),
);
} else {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: "Unknown error occurred..."),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
}
}, onError: (err) {
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: err.toString()),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
} else {
openPaymentURL("visa");
}
}),
SizedBox(height: 16.h),
isShowTamara
@ -357,7 +447,6 @@ class _ImmediateLiveCarePaymentPageState extends State<ImmediateLiveCarePaymentP
void checkPaymentStatus() async {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr());
if (selectedPaymentMethod == "TAMARA") {
await payfortViewModel.checkTamaraPaymentStatus(
transactionID: transID,
@ -510,7 +599,7 @@ class _ImmediateLiveCarePaymentPageState extends State<ImmediateLiveCarePaymentP
applePayInsertRequest.voipToken = await Utils.getStringFromPrefs(CacheConst.voipToken);
applePayInsertRequest.doctorID = 0;
applePayInsertRequest.projectID = "12";
applePayInsertRequest.serviceID = ServiceTypeEnum.appointmentPayment.getIdFromServiceEnum().toString();
applePayInsertRequest.serviceID = ServiceTypeEnum.liveCareAppointment.getIdFromServiceEnum().toString();
applePayInsertRequest.channelID = 3;
applePayInsertRequest.patientID = appState.getAuthenticatedUser()!.patientId.toString();
applePayInsertRequest.patientTypeID = appState.getAuthenticatedUser()!.patientType;
@ -542,37 +631,78 @@ class _ImmediateLiveCarePaymentPageState extends State<ImmediateLiveCarePaymentP
//TODO: Need to pass dynamic params to the Apple Pay instead of static values
await payfortViewModel.applePayRequestInsert(applePayInsertRequest: applePayInsertRequest).then((value) {
payfortViewModel.paymentWithApplePay(
customerName: "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}",
// customerEmail: projectViewModel.authenticatedUserObject.user.emailAddress,
customerEmail: "CustID_${appState.getAuthenticatedUser()!.patientId.toString()}@HMG.com",
orderDescription: "LiveCare Payment",
orderAmount: double.parse((immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.total ?? "0.0")),
merchantReference: transID,
merchantIdentifier: payfortViewModel.payfortProjectDetailsRespModel!.merchantIdentifier,
applePayAccessCode: payfortViewModel.payfortProjectDetailsRespModel!.accessCode,
applePayShaRequestPhrase: payfortViewModel.payfortProjectDetailsRespModel!.shaRequest,
currency: appState.getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED",
onFailed: (failureResult) async {
log("failureResult: ${failureResult.message.toString()}");
if (appState.isPaytabsEnabled) {
LoaderBottomSheet.hideLoader();
paytabsViewModel.setPaymentConfiguration("Advance Payment", num.parse((immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.total ?? "0.0")));
paytabsViewModel.startApplePayPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr());
await immediateLiveCareViewModel.addNewCallRequestForImmediateLiveCare(transactionData.transactionReference!);
await immediateLiveCareViewModel.getPatientLiveCareHistory();
LoaderBottomSheet.hideLoader();
if (immediateLiveCareViewModel.patientHasPendingLiveCareRequest) {
Navigator.pushAndRemoveUntil(
context,
CustomPageRoute(
page: LandingNavigation(),
),
(r) => false);
Navigator.of(context).push(
CustomPageRoute(
page: ImmediateLiveCarePendingRequestPage(),
),
);
} else {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: "Unknown error occurred..."),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
}
}, onError: (err) {
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: failureResult.message.toString()),
child: Utils.getErrorWidget(loadingText: err.toString()),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
},
onSucceeded: (successResult) async {
LoaderBottomSheet.hideLoader();
log("successResult: ${successResult.responseMessage.toString()}");
selectedPaymentMethod = successResult.paymentOption ?? "VISA";
checkPaymentStatus();
},
// projectId: appo.projectID,
// serviceTypeEnum: ServiceTypeEnum.appointmentPayment,
);
});
} else {
payfortViewModel.paymentWithApplePay(
customerName: "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}",
// customerEmail: projectViewModel.authenticatedUserObject.user.emailAddress,
customerEmail: "CustID_${appState.getAuthenticatedUser()!.patientId.toString()}@HMG.com",
orderDescription: "LiveCare Payment",
orderAmount: num.parse((immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.total ?? "0.0")),
merchantReference: transID,
merchantIdentifier: payfortViewModel.payfortProjectDetailsRespModel!.merchantIdentifier,
applePayAccessCode: payfortViewModel.payfortProjectDetailsRespModel!.accessCode,
applePayShaRequestPhrase: payfortViewModel.payfortProjectDetailsRespModel!.shaRequest,
currency: appState.getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED",
onFailed: (failureResult) async {
log("failureResult: ${failureResult.message.toString()}");
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: failureResult.message.toString()),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
},
onSucceeded: (successResult) async {
LoaderBottomSheet.hideLoader();
log("successResult: ${successResult.responseMessage.toString()}");
selectedPaymentMethod = successResult.paymentOption ?? "VISA";
checkPaymentStatus();
},
// projectId: appo.projectID,
// serviceTypeEnum: ServiceTypeEnum.appointmentPayment,
);
}
});
}
}

@ -19,6 +19,8 @@ import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/payfort/models/apple_pay_request_insert_model.dart';
import 'package:hmg_patient_app_new/features/payfort/payfort_view_model.dart';
import 'package:hmg_patient_app_new/features/paytabs/models/paytabs_transaction_response_model.dart';
import 'package:hmg_patient_app_new/features/paytabs/paytabs_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart';
import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.dart';
@ -45,6 +47,7 @@ class _WaitingAppointmentPaymentPageState extends State<WaitingAppointmentPaymen
late BookAppointmentsViewModel bookAppointmentsViewModel;
late MyAppointmentsViewModel myAppointmentsViewModel;
late PayfortViewModel payfortViewModel;
late PayTabsViewModel paytabsViewModel;
late AppState appState;
MyInAppBrowser? browser;
@ -87,6 +90,7 @@ class _WaitingAppointmentPaymentPageState extends State<WaitingAppointmentPaymen
bookAppointmentsViewModel = Provider.of<BookAppointmentsViewModel>(context);
myAppointmentsViewModel = Provider.of<MyAppointmentsViewModel>(context);
payfortViewModel = Provider.of<PayfortViewModel>(context);
paytabsViewModel = Provider.of<PayTabsViewModel>(context, listen: false);
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: Consumer<BookAppointmentsViewModel>(builder: (context, bookAppointmentsVM, child) {
@ -133,7 +137,50 @@ class _WaitingAppointmentPaymentPageState extends State<WaitingAppointmentPaymen
).paddingSymmetrical(16.h, 16.h),
).paddingSymmetrical(24.h, 0.h).onPress(() {
selectedPaymentMethod = "MADA";
openPaymentURL("mada");
if (appState.isPaytabsEnabled) {
paytabsViewModel.setPaymentConfiguration(
"Appointment Payment",
bookAppointmentsViewModel.patientWalkInAppointmentShareResponseModel!.patientShareWithTax!,
);
paytabsViewModel.startCardPayment(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(
paymentMethodName: selectedPaymentMethod,
projectID: bookAppointmentsViewModel.waitingAppointmentProjectID,
clinicID: bookAppointmentsViewModel.selectedDoctor.clinicID!,
appointmentNo: appointmentNo,
payedAmount: bookAppointmentsViewModel.patientWalkInAppointmentShareResponseModel!.patientShareWithTax!,
paymentReference: transactionData.transactionReference!,
patientID: appState.getAuthenticatedUser()!.patientId.toString(),
patientType: appState.getAuthenticatedUser()!.patientType!,
onSuccess: (value) async {
print(value);
await myAppointmentsViewModel.addAdvanceNumberRequest(
advanceNumber: Utils.isVidaPlusProject(bookAppointmentsViewModel.waitingAppointmentProjectID)
? value.data['OnlineCheckInAppointments'][0]['AdvanceNumber_VP'].toString()
: value.data['OnlineCheckInAppointments'][0]['AdvanceNumber'].toString(),
paymentReference: transactionData.transactionReference!,
appointmentNo: appointmentNo,
onSuccess: (value) async {
LoaderBottomSheet.hideLoader();
sendCheckInRequest(bookAppointmentsViewModel.waitingAppointmentNFCCode, appointmentNo, context);
});
});
});
}, onError: (err) {
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: err.toString()),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
} else {
openPaymentURL("mada");
}
}),
SizedBox(height: 16.h),
Container(
@ -175,7 +222,50 @@ class _WaitingAppointmentPaymentPageState extends State<WaitingAppointmentPaymen
).paddingSymmetrical(16.h, 16.h),
).paddingSymmetrical(24.h, 0.h).onPress(() {
selectedPaymentMethod = "VISA";
openPaymentURL("visa");
if (appState.isPaytabsEnabled) {
paytabsViewModel.setPaymentConfiguration(
"Appointment Payment",
bookAppointmentsViewModel.patientWalkInAppointmentShareResponseModel!.patientShareWithTax!,
);
paytabsViewModel.startCardPayment(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(
paymentMethodName: selectedPaymentMethod,
projectID: bookAppointmentsViewModel.waitingAppointmentProjectID,
clinicID: bookAppointmentsViewModel.selectedDoctor.clinicID!,
appointmentNo: appointmentNo,
payedAmount: bookAppointmentsViewModel.patientWalkInAppointmentShareResponseModel!.patientShareWithTax!,
paymentReference: transactionData.transactionReference!,
patientID: appState.getAuthenticatedUser()!.patientId.toString(),
patientType: appState.getAuthenticatedUser()!.patientType!,
onSuccess: (value) async {
print(value);
await myAppointmentsViewModel.addAdvanceNumberRequest(
advanceNumber: Utils.isVidaPlusProject(bookAppointmentsViewModel.waitingAppointmentProjectID)
? value.data['OnlineCheckInAppointments'][0]['AdvanceNumber_VP'].toString()
: value.data['OnlineCheckInAppointments'][0]['AdvanceNumber'].toString(),
paymentReference: transactionData.transactionReference!,
appointmentNo: appointmentNo,
onSuccess: (value) async {
LoaderBottomSheet.hideLoader();
sendCheckInRequest(bookAppointmentsViewModel.waitingAppointmentNFCCode, appointmentNo, context);
});
});
});
}, onError: (err) {
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: err.toString()),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
} else {
openPaymentURL("visa");
}
}),
SizedBox(height: 16.h),
isShowTamara
@ -531,14 +621,7 @@ class _WaitingAppointmentPaymentPageState extends State<WaitingAppointmentPaymen
}
startApplePay() async {
showCommonBottomSheet(context,
child: Utils.getLoadingWidget(),
callBackFunc: (str) {},
title: "",
height: ResponsiveExtension.screenHeight * 0.3,
isCloseButtonVisible: false,
isDismissible: false,
isFullScreen: false);
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.loadingText.tr(context: context));
transID = Utils.getAppointmentTransID(
bookAppointmentsViewModel.selectedDoctor.projectID!,
bookAppointmentsViewModel.selectedDoctor.clinicID!,
@ -596,36 +679,80 @@ class _WaitingAppointmentPaymentPageState extends State<WaitingAppointmentPaymen
//TODO: Need to pass dynamic params to the Apple Pay instead of static values
await payfortViewModel.applePayRequestInsert(applePayInsertRequest: applePayInsertRequest).then((value) {
payfortViewModel.paymentWithApplePay(
customerName: "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}",
// customerEmail: projectViewModel.authenticatedUserObject.user.emailAddress,
customerEmail: "CustID_${appState.getAuthenticatedUser()!.patientId.toString()}@HMG.com",
orderDescription: "Appointment Payment",
orderAmount: double.parse(bookAppointmentsViewModel.patientWalkInAppointmentShareResponseModel!.patientShareWithTax!.toString()),
merchantReference: transID,
merchantIdentifier: payfortViewModel.payfortProjectDetailsRespModel!.merchantIdentifier,
applePayAccessCode: payfortViewModel.payfortProjectDetailsRespModel!.accessCode,
applePayShaRequestPhrase: payfortViewModel.payfortProjectDetailsRespModel!.shaRequest,
currency: appState.getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED",
onFailed: (failureResult) async {
log("failureResult: ${failureResult.message.toString()}");
Navigator.of(context).pop();
if (appState.isPaytabsEnabled) {
LoaderBottomSheet.hideLoader();
paytabsViewModel.setPaymentConfiguration(
"Appointment Payment",
bookAppointmentsViewModel.patientWalkInAppointmentShareResponseModel!.patientShareWithTax!,
);
paytabsViewModel.startApplePayPayment(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(
paymentMethodName: selectedPaymentMethod,
projectID: bookAppointmentsViewModel.waitingAppointmentProjectID,
clinicID: bookAppointmentsViewModel.selectedDoctor.clinicID!,
appointmentNo: appointmentNo,
payedAmount: bookAppointmentsViewModel.patientWalkInAppointmentShareResponseModel!.patientShareWithTax!,
paymentReference: transactionData.transactionReference!,
patientID: appState.getAuthenticatedUser()!.patientId.toString(),
patientType: appState.getAuthenticatedUser()!.patientType!,
onSuccess: (value) async {
print(value);
await myAppointmentsViewModel.addAdvanceNumberRequest(
advanceNumber: Utils.isVidaPlusProject(bookAppointmentsViewModel.waitingAppointmentProjectID)
? value.data['OnlineCheckInAppointments'][0]['AdvanceNumber_VP'].toString()
: value.data['OnlineCheckInAppointments'][0]['AdvanceNumber'].toString(),
paymentReference: transactionData.transactionReference!,
appointmentNo: appointmentNo,
onSuccess: (value) async {
LoaderBottomSheet.hideLoader();
sendCheckInRequest(bookAppointmentsViewModel.waitingAppointmentNFCCode, appointmentNo, context);
});
});
});
}, onError: (err) {
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: failureResult.message.toString()),
child: Utils.getErrorWidget(loadingText: err.toString()),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
},
onSucceeded: (successResult) async {
log("successResult: ${successResult.responseMessage.toString()}");
selectedPaymentMethod = successResult.paymentOption ?? "VISA";
checkPaymentStatus();
},
// projectId: appo.projectID,
// serviceTypeEnum: ServiceTypeEnum.appointmentPayment,
);
});
} else {
payfortViewModel.paymentWithApplePay(
customerName: "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}",
// customerEmail: projectViewModel.authenticatedUserObject.user.emailAddress,
customerEmail: "CustID_${appState.getAuthenticatedUser()!.patientId.toString()}@HMG.com",
orderDescription: "Appointment Payment",
orderAmount: double.parse(bookAppointmentsViewModel.patientWalkInAppointmentShareResponseModel!.patientShareWithTax!.toString()),
merchantReference: transID,
merchantIdentifier: payfortViewModel.payfortProjectDetailsRespModel!.merchantIdentifier,
applePayAccessCode: payfortViewModel.payfortProjectDetailsRespModel!.accessCode,
applePayShaRequestPhrase: payfortViewModel.payfortProjectDetailsRespModel!.shaRequest,
currency: appState.getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED",
onFailed: (failureResult) async {
log("failureResult: ${failureResult.message.toString()}");
Navigator.of(context).pop();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: failureResult.message.toString()),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
},
onSucceeded: (successResult) async {
log("successResult: ${successResult.responseMessage.toString()}");
selectedPaymentMethod = successResult.paymentOption ?? "VISA";
checkPaymentStatus();
},
// projectId: appo.projectID,
// serviceTypeEnum: ServiceTypeEnum.appointmentPayment,
);
}
});
}

@ -22,6 +22,14 @@ class ClinicCard extends StatelessWidget {
@override
Widget build(BuildContext context) {
AppState appState = getIt.get<AppState>();
// Get clinic icon path with fallback
String getClinicIconPath() {
final clinicId = clinicsListResponseModel.clinicID ?? 1;
// Try to use specific clinic icon, fallback to default if not found
return "assets/images/clinicIcons/$clinicId.svg";
}
return Container(
padding: EdgeInsets.all(16.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
@ -32,7 +40,19 @@ class ClinicCard extends StatelessWidget {
child: Column(
children: [
Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [
Utils.buildSvgWithAssets(icon: "assets/images/clinicIcons/${clinicsListResponseModel.clinicID ?? 1}.svg", width: 24.w, height: 24.h, fit: BoxFit.contain).toShimmer2(isShow: isLoading),
// Use Image.asset with error builder for fallback
SizedBox(
width: 24.w,
height: 24.h,
child: Utils.buildSvgWithAssets(
icon: getClinicIconPath(),
width: 24.w,
height: 24.h,
fit: BoxFit.contain,
// Note: Add error handling in the Utils.buildSvgWithAssets if possible
// or use a try-catch wrapper
),
).toShimmer2(isShow: isLoading),
(clinicsListResponseModel.isLiveCareClinicAndOnline ?? true)
? Utils.buildSvgWithAssets(icon: AppAssets.livecare_clinic_icon, width: 32.w, height: 32.h, fit: BoxFit.contain, applyThemeColor: false).toShimmer2(isShow: isLoading)
: SizedBox.shrink(),

@ -258,7 +258,7 @@ class LiveChatPage extends StatelessWidget {
workGroup: workGroup,
onSuccess: (response) {
debugPrint("Chat Request ID received: ${contactUsVM.chatRequestID}");
chatURL = "https://chat.hmg.com/Index.aspx?RequestedId=${contactUsVM.chatRequestID}";
chatURL = "https://chat.hmg.com/geneysChat/Index.aspx?RequestedId=${contactUsVM.chatRequestID}";
debugPrint("Chat URL: $chatURL");
},
onError: (error) {

@ -16,6 +16,8 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/emergency_services/emergency_services_view_model.dart';
import 'package:hmg_patient_app_new/features/payfort/models/apple_pay_request_insert_model.dart';
import 'package:hmg_patient_app_new/features/payfort/payfort_view_model.dart';
import 'package:hmg_patient_app_new/features/paytabs/models/paytabs_transaction_response_model.dart';
import 'package:hmg_patient_app_new/features/paytabs/paytabs_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/home/navigation_screen.dart';
import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.dart';
@ -39,6 +41,7 @@ class ErOnlineCheckinPaymentPage extends StatefulWidget {
class _ErOnlineCheckinPaymentPageState extends State<ErOnlineCheckinPaymentPage> {
late PayfortViewModel payfortViewModel;
late EmergencyServicesViewModel emergencyServicesViewModel;
late PayTabsViewModel paytabsViewModel;
late AppState appState;
@ -75,6 +78,7 @@ class _ErOnlineCheckinPaymentPageState extends State<ErOnlineCheckinPaymentPage>
appState = getIt.get<AppState>();
payfortViewModel = Provider.of<PayfortViewModel>(context, listen: false);
emergencyServicesViewModel = Provider.of<EmergencyServicesViewModel>(context, listen: false);
paytabsViewModel = Provider.of<PayTabsViewModel>(context, listen: false);
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: Column(
@ -119,7 +123,54 @@ class _ErOnlineCheckinPaymentPageState extends State<ErOnlineCheckinPaymentPage>
).paddingSymmetrical(16.h, 16.h),
).paddingSymmetrical(24.h, 0.h).onPress(() {
selectedPaymentMethod = "MADA";
openPaymentURL("mada");
if (appState.isPaytabsEnabled) {
paytabsViewModel.setPaymentConfiguration(
"ER Online Check-In Payment",
emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientShareWithTax!,
);
paytabsViewModel.startCardPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async {
if (emergencyServicesViewModel.isERBookAppointment) {
await emergencyServicesViewModel.ER_CreateAdvancePayment(
paymentMethodName: selectedPaymentMethod,
paymentReference: transactionData.transactionReference!,
onSuccess: (value) async {
await emergencyServicesViewModel.addAdvanceNumberRequest(
advanceNumber: value,
paymentReference: transactionData.transactionReference!,
appointmentNo: "0",
onSuccess: (val) {
LoaderBottomSheet.hideLoader();
if (emergencyServicesViewModel.isERBookAppointment) {
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getSuccessWidget(loadingText: LocaleKeys.erAppointmentBookedSuccess.tr(context: context)),
callBackFunc: () {
Navigator.pushAndRemoveUntil(
context,
CustomPageRoute(
page: LandingNavigation(),
),
(r) => false);
},
isFullScreen: false,
isCloseButtonVisible: true,
);
} else {}
});
});
} else {}
}, onError: (errMsg) {
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: errMsg.toString()),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
} else {
openPaymentURL("mada");
}
}),
SizedBox(height: 16.h),
Container(
@ -161,7 +212,54 @@ class _ErOnlineCheckinPaymentPageState extends State<ErOnlineCheckinPaymentPage>
).paddingSymmetrical(16.h, 16.h),
).paddingSymmetrical(24.h, 0.h).onPress(() {
selectedPaymentMethod = "VISA";
openPaymentURL("visa");
if (appState.isPaytabsEnabled) {
paytabsViewModel.setPaymentConfiguration(
"ER Online Check-In Payment",
emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientShareWithTax!,
);
paytabsViewModel.startCardPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async {
if (emergencyServicesViewModel.isERBookAppointment) {
await emergencyServicesViewModel.ER_CreateAdvancePayment(
paymentMethodName: selectedPaymentMethod,
paymentReference: transactionData.transactionReference!,
onSuccess: (value) async {
await emergencyServicesViewModel.addAdvanceNumberRequest(
advanceNumber: value,
paymentReference: transactionData.transactionReference!,
appointmentNo: "0",
onSuccess: (val) {
LoaderBottomSheet.hideLoader();
if (emergencyServicesViewModel.isERBookAppointment) {
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getSuccessWidget(loadingText: LocaleKeys.erAppointmentBookedSuccess.tr(context: context)),
callBackFunc: () {
Navigator.pushAndRemoveUntil(
context,
CustomPageRoute(
page: LandingNavigation(),
),
(r) => false);
},
isFullScreen: false,
isCloseButtonVisible: true,
);
} else {}
});
});
} else {}
}, onError: (errMsg) {
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: errMsg.toString()),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
} else {
openPaymentURL("visa");
}
}),
SizedBox(height: 16.h),
isShowTamara
@ -393,35 +491,84 @@ class _ErOnlineCheckinPaymentPageState extends State<ErOnlineCheckinPaymentPage>
//TODO: Need to pass dynamic params to the Apple Pay instead of static values
await payfortViewModel.applePayRequestInsert(applePayInsertRequest: applePayInsertRequest).then((value) {
payfortViewModel.paymentWithApplePay(
customerName: "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}",
// customerEmail: projectViewModel.authenticatedUserObject.user.emailAddress,
customerEmail: "CustID_${appState.getAuthenticatedUser()!.patientId.toString()}@HMG.com",
orderDescription: "Appointment Payment",
orderAmount: double.parse(emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientShareWithTax!.toString()),
merchantReference: transID,
merchantIdentifier: payfortViewModel.payfortProjectDetailsRespModel!.merchantIdentifier,
applePayAccessCode: payfortViewModel.payfortProjectDetailsRespModel!.accessCode,
applePayShaRequestPhrase: payfortViewModel.payfortProjectDetailsRespModel!.shaRequest,
currency: appState.getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED",
onFailed: (failureResult) async {
LoaderBottomSheet.hideLoader();
log("failureResult: ${failureResult.message.toString()}");
if (appState.isPaytabsEnabled) {
LoaderBottomSheet.hideLoader();
paytabsViewModel.setPaymentConfiguration(
"ER Online Check-In Payment",
emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientShareWithTax!,
);
paytabsViewModel.startApplePayPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingPaymentStatusPleaseWait.tr(context: context));
if (emergencyServicesViewModel.isERBookAppointment) {
await emergencyServicesViewModel.ER_CreateAdvancePayment(
paymentMethodName: selectedPaymentMethod,
paymentReference: transactionData.transactionReference!,
onSuccess: (value) async {
await emergencyServicesViewModel.addAdvanceNumberRequest(
advanceNumber: value,
paymentReference: transactionData.transactionReference!,
appointmentNo: "0",
onSuccess: (val) {
LoaderBottomSheet.hideLoader();
if (emergencyServicesViewModel.isERBookAppointment) {
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getSuccessWidget(loadingText: LocaleKeys.erAppointmentBookedSuccess.tr(context: context)),
callBackFunc: () {
Navigator.pushAndRemoveUntil(
context,
CustomPageRoute(
page: LandingNavigation(),
),
(r) => false);
},
isFullScreen: false,
isCloseButtonVisible: true,
);
} else {}
});
});
} else {}
}, onError: (err) {
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: failureResult.message.toString()),
child: Utils.getErrorWidget(loadingText: err.toString()),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
},
onSucceeded: (successResult) async {
LoaderBottomSheet.hideLoader();
log("successResult: ${successResult.responseMessage.toString()}");
selectedPaymentMethod = successResult.paymentOption ?? "VISA";
checkPaymentStatus();
},
);
});
} else {
payfortViewModel.paymentWithApplePay(
customerName: "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}",
// customerEmail: projectViewModel.authenticatedUserObject.user.emailAddress,
customerEmail: "CustID_${appState.getAuthenticatedUser()!.patientId.toString()}@HMG.com",
orderDescription: "ER Online Check-In Payment",
orderAmount: double.parse(emergencyServicesViewModel.erOnlineCheckInPaymentDetailsResponse.patientShareWithTax!.toString()),
merchantReference: transID,
merchantIdentifier: payfortViewModel.payfortProjectDetailsRespModel!.merchantIdentifier,
applePayAccessCode: payfortViewModel.payfortProjectDetailsRespModel!.accessCode,
applePayShaRequestPhrase: payfortViewModel.payfortProjectDetailsRespModel!.shaRequest,
currency: appState.getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED",
onFailed: (failureResult) async {
LoaderBottomSheet.hideLoader();
log("failureResult: ${failureResult.message.toString()}");
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: failureResult.message.toString()),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
},
onSucceeded: (successResult) async {
LoaderBottomSheet.hideLoader();
log("successResult: ${successResult.responseMessage.toString()}");
selectedPaymentMethod = successResult.paymentOption ?? "VISA";
checkPaymentStatus();
},
);
}
});
}

@ -33,6 +33,7 @@ class _HabibWalletState extends State<HabibWalletPage> {
scheduleMicrotask(() async {
habibWalletVM.initHabibWalletProvider();
habibWalletVM.getPatientBalanceAmount();
habibWalletVM.getLakumAccountInformation();
});
super.initState();
@ -123,6 +124,7 @@ class _HabibWalletState extends State<HabibWalletPage> {
.then((val) {
habibWalletVM.initHabibWalletProvider();
habibWalletVM.getPatientBalanceAmount();
habibWalletVM.getLakumAccountInformation();
});
},
),

@ -16,7 +16,10 @@ import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart';
import 'package:hmg_patient_app_new/features/payfort/models/apple_pay_request_insert_model.dart';
import 'package:hmg_patient_app_new/features/payfort/payfort_view_model.dart';
import 'package:hmg_patient_app_new/features/paytabs/models/paytabs_transaction_response_model.dart';
import 'package:hmg_patient_app_new/features/paytabs/paytabs_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
@ -34,6 +37,7 @@ class WalletPaymentConfirmPage extends StatefulWidget {
class _WalletPaymentConfirmPageState extends State<WalletPaymentConfirmPage> {
late PayfortViewModel payfortViewModel;
late PayTabsViewModel paytabsViewModel;
late AppState appState;
late HabibWalletViewModel habibWalletVM;
@ -55,6 +59,7 @@ class _WalletPaymentConfirmPageState extends State<WalletPaymentConfirmPage> {
appState = getIt.get<AppState>();
habibWalletVM = Provider.of<HabibWalletViewModel>(context, listen: false);
payfortViewModel = Provider.of<PayfortViewModel>(context, listen: false);
paytabsViewModel = Provider.of<PayTabsViewModel>(context, listen: false);
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
body: Column(
@ -101,7 +106,66 @@ class _WalletPaymentConfirmPageState extends State<WalletPaymentConfirmPage> {
).paddingSymmetrical(16.h, 16.h),
).paddingSymmetrical(24.h, 0.h).onPress(() {
selectedPaymentMethod = "MADA";
openPaymentURL("mada");
if (appState.isPaytabsEnabled) {
paytabsViewModel.setPaymentConfiguration("Advance Payment", habibWalletVM.walletRechargeAmount);
paytabsViewModel.startCardPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async {
LoaderBottomSheet.showLoader();
await habibWalletVM.HISCreateAdvancePayment(
paymentMethodName: selectedPaymentMethod,
paidAmount: habibWalletVM.walletRechargeAmount,
paymentReference: transactionData.transactionReference!,
patientID: habibWalletVM.fileNumber,
projectID: habibWalletVM.selectedHospital!.iD!,
depositorName: habibWalletVM.depositorName,
onSuccess: (value) async {
await habibWalletVM.addAdvanceNumberRequest(
advanceNumber: Utils.isVidaPlusProject(habibWalletVM.selectedHospital!.iD)
? value.data['OnlineCheckInAppointments'][0]['AdvanceNumber_VP'].toString()
: value.data['OnlineCheckInAppointments'][0]['AdvanceNumber'].toString(),
paymentReference: transactionData.transactionReference!,
onSuccess: (value) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(getIt.get<NavigationService>().navigatorKey.currentContext!, child: Utils.getSuccessWidget(loadingText: "Payment Successful!"),
callBackFunc: () {
habibWalletVM.initHabibWalletProvider();
habibWalletVM.getPatientBalanceAmount();
Navigator.of(getIt.get<NavigationService>().navigatorKey.currentContext!).pop();
Navigator.of(getIt.get<NavigationService>().navigatorKey.currentContext!).pop();
}, isFullScreen: false, isCloseButtonVisible: true, isAutoDismiss: true);
},
onError: (err) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: LocaleKeys.paymentFailedPleaseTryAgain.tr(context: context)),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
},
onError: (err) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: err),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
}, onError: (err) {
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: err.toString()),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
} else {
openPaymentURL("mada");
}
}),
SizedBox(height: 16.h),
Container(
@ -143,7 +207,66 @@ class _WalletPaymentConfirmPageState extends State<WalletPaymentConfirmPage> {
).paddingSymmetrical(16.h, 16.h),
).paddingSymmetrical(24.h, 0.h).onPress(() {
selectedPaymentMethod = "VISA";
openPaymentURL("visa");
if (appState.isPaytabsEnabled) {
paytabsViewModel.setPaymentConfiguration("Advance Payment", habibWalletVM.walletRechargeAmount);
paytabsViewModel.startCardPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async {
LoaderBottomSheet.showLoader();
await habibWalletVM.HISCreateAdvancePayment(
paymentMethodName: selectedPaymentMethod,
paidAmount: habibWalletVM.walletRechargeAmount,
paymentReference: transactionData.transactionReference!,
patientID: habibWalletVM.fileNumber,
projectID: habibWalletVM.selectedHospital!.iD!,
depositorName: habibWalletVM.depositorName,
onSuccess: (value) async {
await habibWalletVM.addAdvanceNumberRequest(
advanceNumber: Utils.isVidaPlusProject(habibWalletVM.selectedHospital!.iD)
? value.data['OnlineCheckInAppointments'][0]['AdvanceNumber_VP'].toString()
: value.data['OnlineCheckInAppointments'][0]['AdvanceNumber'].toString(),
paymentReference: transactionData.transactionReference!,
onSuccess: (value) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(getIt.get<NavigationService>().navigatorKey.currentContext!, child: Utils.getSuccessWidget(loadingText: "Payment Successful!"),
callBackFunc: () {
habibWalletVM.initHabibWalletProvider();
habibWalletVM.getPatientBalanceAmount();
Navigator.of(getIt.get<NavigationService>().navigatorKey.currentContext!).pop();
Navigator.of(getIt.get<NavigationService>().navigatorKey.currentContext!).pop();
}, isFullScreen: false, isCloseButtonVisible: true, isAutoDismiss: true);
},
onError: (err) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: LocaleKeys.paymentFailedPleaseTryAgain.tr(context: context)),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
},
onError: (err) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: err),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
}, onError: (err) {
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: err.toString()),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
} else {
openPaymentURL("visa");
}
}),
],
),
@ -196,9 +319,7 @@ class _WalletPaymentConfirmPageState extends State<WalletPaymentConfirmPage> {
).paddingSymmetrical(24.h, 0.h),
SizedBox(height: 12.h),
Platform.isIOS
? Utils.buildSvgWithAssets(
icon: AppAssets.apple_pay_button,
width: 200.h, height: 56.h, fit: BoxFit.contain, applyThemeColor: false).paddingSymmetrical(24.h, 0.h).onPress(() {
? Utils.buildSvgWithAssets(icon: AppAssets.apple_pay_button, width: 200.h, height: 56.h, fit: BoxFit.contain, applyThemeColor: false).paddingSymmetrical(24.h, 0.h).onPress(() {
if (Utils.havePrivilege(103)) {
startApplePay();
} else {
@ -267,33 +388,93 @@ class _WalletPaymentConfirmPageState extends State<WalletPaymentConfirmPage> {
//TODO: Need to pass dynamic params to the Apple Pay instead of static values
await payfortViewModel.applePayRequestInsert(applePayInsertRequest: applePayInsertRequest).then((value) {
payfortViewModel.paymentWithApplePay(
customerName: "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}",
// customerEmail: projectViewModel.authenticatedUserObject.user.emailAddress,
customerEmail: "CustID_${appState.getAuthenticatedUser()!.patientId.toString()}@HMG.com",
orderDescription: "Appointment Payment",
orderAmount: double.parse(habibWalletVM.walletRechargeAmount.toString()),
merchantReference: transID,
merchantIdentifier: payfortViewModel.payfortProjectDetailsRespModel!.merchantIdentifier,
applePayAccessCode: payfortViewModel.payfortProjectDetailsRespModel!.accessCode,
applePayShaRequestPhrase: payfortViewModel.payfortProjectDetailsRespModel!.shaRequest,
currency: appState.getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED",
onFailed: (failureResult) async {
log("failureResult: ${failureResult.message.toString()}");
if(appState.isPaytabsEnabled) {
LoaderBottomSheet.hideLoader();
paytabsViewModel.setPaymentConfiguration("Advance Payment", habibWalletVM.walletRechargeAmount);
paytabsViewModel.startApplePayPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async {
LoaderBottomSheet.showLoader();
await habibWalletVM.HISCreateAdvancePayment(
paymentMethodName: selectedPaymentMethod,
paidAmount: habibWalletVM.walletRechargeAmount,
paymentReference: transactionData.transactionReference!,
patientID: habibWalletVM.fileNumber,
projectID: habibWalletVM.selectedHospital!.iD!,
depositorName: habibWalletVM.depositorName,
onSuccess: (value) async {
await habibWalletVM.addAdvanceNumberRequest(
advanceNumber: Utils.isVidaPlusProject(habibWalletVM.selectedHospital!.iD)
? value.data['OnlineCheckInAppointments'][0]['AdvanceNumber_VP'].toString()
: value.data['OnlineCheckInAppointments'][0]['AdvanceNumber'].toString(),
paymentReference: transactionData.transactionReference!,
onSuccess: (value) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(getIt.get<NavigationService>().navigatorKey.currentContext!, child: Utils.getSuccessWidget(loadingText: "Payment Successful!"),
callBackFunc: () {
habibWalletVM.initHabibWalletProvider();
habibWalletVM.getPatientBalanceAmount();
Navigator.of(getIt.get<NavigationService>().navigatorKey.currentContext!).pop();
Navigator.of(getIt.get<NavigationService>().navigatorKey.currentContext!).pop();
}, isFullScreen: false, isCloseButtonVisible: true, isAutoDismiss: true);
},
onError: (err) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: LocaleKeys.paymentFailedPleaseTryAgain.tr(context: context)),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
},
onError: (err) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: err),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
}, onError: (err) {
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: failureResult.message.toString()),
child: Utils.getErrorWidget(loadingText: err.toString()),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
},
onSucceeded: (successResult) async {
log("successResult: ${successResult.responseMessage.toString()}");
selectedPaymentMethod = successResult.paymentOption ?? "VISA";
checkPaymentStatus();
},
);
});
} else {
payfortViewModel.paymentWithApplePay(
customerName: "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}",
// customerEmail: projectViewModel.authenticatedUserObject.user.emailAddress,
customerEmail: "CustID_${appState.getAuthenticatedUser()!.patientId.toString()}@HMG.com",
orderDescription: "Appointment Payment",
orderAmount: double.parse(habibWalletVM.walletRechargeAmount.toString()),
merchantReference: transID,
merchantIdentifier: payfortViewModel.payfortProjectDetailsRespModel!.merchantIdentifier,
applePayAccessCode: payfortViewModel.payfortProjectDetailsRespModel!.accessCode,
applePayShaRequestPhrase: payfortViewModel.payfortProjectDetailsRespModel!.shaRequest,
currency: appState.getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED",
onFailed: (failureResult) async {
log("failureResult: ${failureResult.message.toString()}");
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: failureResult.message.toString()),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
},
onSucceeded: (successResult) async {
log("successResult: ${successResult.responseMessage.toString()}");
selectedPaymentMethod = successResult.paymentOption ?? "VISA";
checkPaymentStatus();
},
);
}
});
}
@ -319,19 +500,13 @@ class _WalletPaymentConfirmPageState extends State<WalletPaymentConfirmPage> {
paymentReference: payfortViewModel.payfortCheckPaymentStatusResponseModel!.fortId!,
onSuccess: (value) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getSuccessWidget(loadingText: "Payment Successful!"),
callBackFunc: () {
habibWalletVM.initHabibWalletProvider();
habibWalletVM.getPatientBalanceAmount();
Navigator.of(context).pop();
Navigator.of(context).pop();
},
isFullScreen: false,
isCloseButtonVisible: true,
isAutoDismiss: true
);
showCommonBottomSheetWithoutHeight(getIt.get<NavigationService>().navigatorKey.currentContext!, child: Utils.getSuccessWidget(loadingText: "Payment Successful!"),
callBackFunc: () {
habibWalletVM.initHabibWalletProvider();
habibWalletVM.getPatientBalanceAmount();
Navigator.of(getIt.get<NavigationService>().navigatorKey.currentContext!).pop();
Navigator.of(getIt.get<NavigationService>().navigatorKey.currentContext!).pop();
}, isFullScreen: false, isCloseButtonVisible: true, isAutoDismiss: true);
},
onError: (err) {
LoaderBottomSheet.hideLoader();
@ -344,7 +519,16 @@ class _WalletPaymentConfirmPageState extends State<WalletPaymentConfirmPage> {
);
});
},
onError: (err) {});
onError: (err) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: err),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
} else {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(

@ -0,0 +1,438 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.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_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart';
import 'package:hmg_patient_app_new/features/habib_wallet/models/lakum_inquiry_information_response_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart';
class LakumWalletDetails extends StatelessWidget {
const LakumWalletDetails({super.key});
@override
Widget build(BuildContext context) {
AppState appState = getIt.get<AppState>();
return CollapsingListView(
title: LocaleKeys.lakumPoints.tr(context: context),
child: Consumer<HabibWalletViewModel>(
builder: (context, viewModel, child) {
// Check if loading
if (viewModel.isLakumAccountInfoLoading) {
return Center(
child: Padding(
padding: EdgeInsets.all(32.w),
child: CircularProgressIndicator(
color: AppColors.primaryRedColor,
),
),
);
}
// Check if account number is not available
if (viewModel.yahalaAccountNumber == "0" || viewModel.yahalaAccountNumber.isEmpty) {
return Utils.getNoDataWidget(context);
// return Center(
// child: Padding(
// padding: EdgeInsets.all(32.w),
// child: LocaleKeys.lakumMsg.tr().toText14(
// color: AppColors.greyTextColor,
// ),
// ),
// );
}
// Get all transactions
List<PointsDetails> transactions = viewModel.getAllLakumTransactions();
// Display main UI
return SingleChildScrollView(
child: Padding(
padding: EdgeInsets.all(16.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Main Lakum Card with gradient and user info
_buildLakumMainCard(viewModel.lakumAccountInfo, appState),
SizedBox(height: 24.h),
// Redeem Button
_buildRedeemButton(context),
SizedBox(height: 24.h),
// Transaction List Header
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
LocaleKeys.transactions.tr(context: context).toText16(isBold: true),
// if (transactions.isNotEmpty) "${transactions.length} Records".toText12(color: AppColors.greyTextColor),
],
),
SizedBox(height: 12.h),
// Transaction List Container
if (transactions.isEmpty) _buildEmptyTransactions() else _buildTransactionsList(transactions),
SizedBox(height: 24.h),
],
),
),
);
},
),
);
}
Widget _buildLakumMainCard(LakumInquiryInformationResponseModel? accountInfo, AppState appState) {
return Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [
AppColors.habibPharmacyColor,
AppColors.habibPharmacyColor.withAlpha(220),
],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
borderRadius: BorderRadius.circular(24.r),
boxShadow: [
BoxShadow(
color: AppColors.habibPharmacyColor.withAlpha(76),
blurRadius: 16,
offset: Offset(0, 8),
),
],
),
child: Padding(
padding: EdgeInsets.all(20.w),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Top Row: User Info and Grid Icon
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
"${appState.getAuthenticatedUser()?.firstName ?? ''} ${appState.getAuthenticatedUser()?.lastName ?? ''}".toText18(
isBold: true,
color: Colors.white,
),
SizedBox(height: 4.h),
(accountInfo?.accountNumber ?? 0).toString().toText14(
color: Colors.white.withAlpha(230),
),
],
),
),
Utils.buildImgWithAssets(
icon: AppAssets.lakumLogoWhite,
width: 110.h,
height: 100.h,
),
],
),
// SizedBox(height: 24.h),
LocaleKeys.lakumPoints.tr().toText18(color: Colors.white.withAlpha(230), isBold: true),
SizedBox(height: 8.h),
// Points Balance
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
(accountInfo?.pointsBalance?.toString() ?? "0").toText44(
isBold: true,
color: Colors.white,
),
SizedBox(width: 8.w),
LocaleKeys.points
.tr()
.toText18(
color: Colors.white.withAlpha(230),
isBold: true,
)
.paddingOnly(bottom: 8.h),
],
),
SizedBox(height: 4.h),
// Balance Amount in SAR
"${accountInfo?.pointsBalanceAmount?.toStringAsFixed(2) ?? "0.00"} SAR".toText18(
color: Colors.white.withAlpha(204),
),
SizedBox(height: 20.h),
// Stats Row
Row(
children: [
Expanded(
child: _buildStatChip(
LocaleKeys.gained.tr(),
accountInfo?.gainedPoints?.toString() ?? "0",
Icons.arrow_circle_up,
),
),
SizedBox(width: 8.w),
Expanded(
child: _buildStatChip(
LocaleKeys.consumed.tr(),
accountInfo?.consumedPoints?.toString() ?? "0",
Icons.arrow_circle_down,
),
),
SizedBox(width: 8.w),
Expanded(
child: _buildStatChip(
LocaleKeys.expired.tr(), accountInfo?.expiredPoints?.toString() ?? "0", Icons.calendar_month,
isSvg: true
),
),
],
),
],
),
),
);
}
Widget _buildStatChip(String label, String value, IconData icon, {bool isSvg = false}) {
return Container(
padding: EdgeInsets.symmetric(horizontal: 12.w, vertical: 10.h),
decoration: BoxDecoration(
color: Colors.white.withAlpha(51),
borderRadius: BorderRadius.circular(12.r),
),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
isSvg ? Utils.buildSvgWithAssets(icon: AppAssets.expired_icon, width: 15.w, height: 15.h, applyThemeColor: false) : Icon(icon, color: Colors.white, size: 16.f),
SizedBox(width: 6.w),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
mainAxisSize: MainAxisSize.min,
children: [
value.toText14(color: Colors.white, isBold: true),
label.toText10(color: Colors.white.withAlpha(204)),
],
),
],
),
);
}
Widget _buildRedeemButton(BuildContext context) {
return CustomButton(
height: 46.h,
icon: AppAssets.redeem_icon,
iconColor: AppColors.whiteColor,
iconSize: 24.h,
backgroundColor: AppColors.habibPharmacyColor,
textColor: AppColors.whiteColor,
text: LocaleKeys.redeem.tr(context: context),
borderWidth: 0.w,
fontWeight: FontWeight.w600,
borderColor: AppColors.habibPharmacyColor.withAlpha(15),
padding: EdgeInsets.fromLTRB(4, 0, 12, 0),
fontSize: 14.f,
onPressed: () {
Uri uri = Uri.parse(PHARMACY_REDIRECT_URL);
launchUrl(uri, mode: LaunchMode.externalApplication);
},
);
}
Widget _buildEmptyTransactions() {
return Container(
padding: EdgeInsets.all(32.w),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 16.r,
hasShadow: false,
),
child: Center(
child: Column(
children: [
Icon(Icons.receipt_long_outlined, size: 48.f, color: AppColors.greyTextColor),
SizedBox(height: 12.h),
"No transactions found".toText14(
color: AppColors.greyTextColor,
isCenter: true,
),
],
),
),
);
}
Widget _buildTransactionsList(List<PointsDetails> transactions) {
return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 16.r,
hasShadow: false,
),
child: ListView.separated(
padding: EdgeInsets.all(16.w),
shrinkWrap: true,
physics: NeverScrollableScrollPhysics(),
itemCount: transactions.length,
separatorBuilder: (context, index) => Divider(
color: AppColors.dividerColor,
height: 24.h,
),
itemBuilder: (context, index) {
return _buildTransactionItem(transactions[index]);
},
),
);
}
Widget _buildTransactionItem(PointsDetails transaction) {
// Determine if points were gained or used
bool isGained = transaction.operationType?.toLowerCase() == 'gain' || transaction.operationType?.toLowerCase() == 'credit' || (transaction.points ?? 0) > 0;
Color transactionColor = isGained ? AppColors.habibPharmacyColor : AppColors.primaryRedColor;
String pointsText = "${isGained ? '+' : '-'}${transaction.points?.abs() ?? 0}";
// Format date
String formattedDate = "";
if (transaction.transactionDate != null) {
try {
formattedDate = DateUtil.formatDateToDate(
DateUtil.convertStringToDate(transaction.transactionDate!),
false,
);
} catch (e) {
formattedDate = transaction.transactionDate ?? "";
}
}
return Row(
children: [
// Transaction Icon
// Container(
// width: 44.w,
// height: 44.h,
// decoration: BoxDecoration(
// color: transactionColor.withAlpha(25),
// borderRadius: BorderRadius.circular(12.r),
// ),
// child: Icon(
// isGained ? Icons.add_rounded : Icons.remove_rounded,
// color: transactionColor,
// size: 24.f,
// ),
// ),
// SizedBox(width: 12.w),
// Transaction Details
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
(transaction.subTransactionTypeDescription ?? transaction.operationType ?? "Transaction").toText14(
isBold: true,
maxlines: 2,
),
SizedBox(height: 2.h),
formattedDate.toText12(color: AppColors.greyTextColor),
],
),
),
// Points Value
Column(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
pointsText.toText18(
isBold: true,
color: transactionColor,
),
if (transaction.amount != null && transaction.amount! > 0)
"${transaction.amount?.toStringAsFixed(2) ?? "0.00"} SAR".toText11(
color: AppColors.greyTextColor,
),
],
),
],
);
}
}
// Custom painter for wavy background pattern
class _WavyBackgroundPainter extends CustomPainter {
@override
void paint(Canvas canvas, Size size) {
// Base color from AppColors
final baseColor = AppColors.habibPharmacyColor;
final paint = Paint()
..color = Colors.white.withAlpha(25)
..style = PaintingStyle.fill;
// Draw multiple wave patterns
final path1 = Path();
path1.moveTo(0, size.height * 0.3);
path1.quadraticBezierTo(
size.width * 0.25,
size.height * 0.2,
size.width * 0.5,
size.height * 0.3,
);
path1.quadraticBezierTo(
size.width * 0.75,
size.height * 0.4,
size.width,
size.height * 0.3,
);
path1.lineTo(size.width, 0);
path1.lineTo(0, 0);
path1.close();
canvas.drawPath(path1, paint);
// Second wave with darker shade
paint.color = Colors.black.withAlpha(15);
final path2 = Path();
path2.moveTo(0, size.height * 0.6);
path2.quadraticBezierTo(
size.width * 0.3,
size.height * 0.5,
size.width * 0.6,
size.height * 0.6,
);
path2.quadraticBezierTo(
size.width * 0.8,
size.height * 0.7,
size.width,
size.height * 0.6,
);
path2.lineTo(size.width, size.height);
path2.lineTo(0, size.height);
path2.close();
canvas.drawPath(path2, paint);
// Decorative circles with white overlay
paint.color = Colors.white.withAlpha(20);
canvas.drawCircle(Offset(size.width * 0.85, size.height * 0.15), 30, paint);
paint.color = Colors.white.withAlpha(10);
canvas.drawCircle(Offset(size.width * 0.15, size.height * 0.75), 40, paint);
}
@override
bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}

@ -30,6 +30,7 @@ import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/notifications/notifications_view_model.dart';
import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_view_model.dart';
import 'package:hmg_patient_app_new/features/paytabs/paytabs_view_model.dart';
import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/appointments/appointment_queue_page.dart';
@ -140,6 +141,7 @@ class _LandingPageState extends State<LandingPage> {
if (appState.isAuthenticated) {
habibWalletVM.initHabibWalletProvider();
habibWalletVM.getPatientBalanceAmount();
habibWalletVM.getLakumAccountInformation();
todoSectionViewModel.initializeTodoSectionViewModel();
immediateLiveCareViewModel.initImmediateLiveCare();
immediateLiveCareViewModel.getPatientLiveCareHistory();
@ -203,6 +205,7 @@ class _LandingPageState extends State<LandingPage> {
// Refresh Appointments Data
habibWalletVM.initHabibWalletProvider();
habibWalletVM.getPatientBalanceAmount();
habibWalletVM.getLakumAccountInformation();
// Refresh Ancillary Orders
todoSectionViewModel.initializeTodoSectionViewModel();
@ -292,7 +295,7 @@ class _LandingPageState extends State<LandingPage> {
await authVM.onLoginPressed();
}
}),
(appState.isAuthenticated && (int.parse(todoSectionVM.notificationsCount ?? "0") > 0))
(appState.isAuthenticated && todoSectionVM.notificationsCount != null && todoSectionVM.notificationsCount != "0")
? Positioned(
// right: appState.isArabic() ? 8.w : -8.w,
top: -8.h,
@ -307,7 +310,7 @@ class _LandingPageState extends State<LandingPage> {
borderRadius: BorderRadius.circular(20.r),
),
child: Text(
todoSectionVM.notificationsCount.toString(),
todoSectionVM.notificationsCount ?? "0",
style: TextStyle(
color: Colors.white,
fontFamily: "Poppins",
@ -451,6 +454,9 @@ class _LandingPageState extends State<LandingPage> {
myAppointmentsViewModel.onTabChange(0);
myAppointmentsViewModel.updateListWRTTab(0);
Navigator.of(context).push(CustomPageRoute(page: MyAppointmentsPage()));
// getIt.get<PayTabsViewModel>().setPaymentConfiguration("Appointment Payment", 15.5);
// getIt.get<PayTabsViewModel>().startCardPayment();
}),
Consumer3<MyAppointmentsViewModel, ImmediateLiveCareViewModel, TodoSectionViewModel>(
builder: (context, myAppointmentsVM, immediateLiveCareVM, todoSectionVM, child) {
@ -864,6 +870,7 @@ class _LandingPageState extends State<LandingPage> {
children: [
LocaleKeys.myBalanceSubtitle.tr(context: context).toText16(isBold: true).paddingSymmetrical(24.h, 0.h),
SizedBox(
// height: 200.h,
height: 170.h,
child: ListView.separated(
scrollDirection: Axis.horizontal,

@ -86,6 +86,7 @@ class HabibWalletCard extends StatelessWidget {
Consumer<HabibWalletViewModel>(builder: (context, habibWalletVM, child) {
return Row(
mainAxisSize: MainAxisSize.max,
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Row(
children: [
@ -104,8 +105,8 @@ class HabibWalletCard extends StatelessWidget {
.toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.h, width: 80.h, height: 40.h),
],
),
// Spacer(),
Utils.getPaymentMethods(),
Spacer(),
Utils.getPaymentMethods(spacing: 3, width: 30, height: 30),
],
);
}),

@ -1,5 +1,6 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.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_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
@ -11,10 +12,12 @@ import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_mode
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/habib_wallet/habib_wallet_page.dart';
import 'package:hmg_patient_app_new/presentation/habib_wallet/recharge_wallet_page.dart';
import 'package:hmg_patient_app_new/presentation/home/lakum_wallet_details.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'package:provider/provider.dart';
import 'package:url_launcher/url_launcher.dart';
class LakumWalletCard extends StatelessWidget {
const LakumWalletCard({super.key});
@ -64,7 +67,7 @@ class LakumWalletCard extends StatelessWidget {
// mainAxisAlignment: MainAxisAlignment.spaceBetween,
// children: [
// LocaleKeys.habibWallet.tr(context: context).toText16(isBold: true, letterSpacing: -0.2),
"Lakum Wallet".toText16(isBold: true, letterSpacing: -0.2),
LocaleKeys.lakumPoints.tr(context: context).toText16(isBold: true, letterSpacing: -0.2),
// Container(
// height: 40.h,
// width: 40.h,
@ -84,7 +87,7 @@ class LakumWalletCard extends StatelessWidget {
// ),
// ],
// ),
SizedBox(height: 4.h),
SizedBox(height: 8.h),
Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
@ -93,24 +96,23 @@ class LakumWalletCard extends StatelessWidget {
mainAxisSize: MainAxisSize.max,
children: [
Row(
crossAxisAlignment: CrossAxisAlignment.end,
children: [
Utils.buildSvgWithAssets(
icon: AppAssets.saudi_riyal_icon,
iconColor: AppColors.inputLabelTextColor,
width: 24.h,
height: 24.h,
fit: BoxFit.contain,
),
SizedBox(width: 8.h),
NumberFormat.decimalPattern()
.format(habibWalletVM.habibWalletAmount)
.format(habibWalletVM.isLakumAccountInfoLoading
? 123
: habibWalletVM.yahalaAccountNumber == "0"
? 0
: (habibWalletVM.lakumAccountInfo?.pointsBalance ?? 0))
.toString()
.toText32(isBold: true, isEnglishOnly: true)
.toShimmer2(isShow: habibWalletVM.isWalletAmountLoading, radius: 12.h, width: 80.h, height: 40.h),
.toShimmer2(isShow: habibWalletVM.isLakumAccountInfoLoading, radius: 12.h, width: 80.h, height: 40.h),
SizedBox(width: 4.h),
LocaleKeys.points.tr().toText12(color: AppColors.inputLabelTextColor, isBold: true)
],
),
// Spacer(),
Utils.getPaymentMethods(),
Spacer(),
Utils.buildSvgWithAssets(icon: AppAssets.hmg_pharmacy_logo, width: 32.h, height: 32.h, fit: BoxFit.contain, applyThemeColor: false),
],
);
}),
@ -123,19 +125,21 @@ class LakumWalletCard extends StatelessWidget {
flex: 6,
child: CustomButton(
height: 40.h,
icon: AppAssets.recharge_icon,
iconColor: AppColors.infoColor,
icon: AppAssets.redeem_icon,
iconColor: AppColors.habibPharmacyColor,
iconSize: 24.h,
backgroundColor: AppColors.infoColor.withAlpha(15),
textColor: AppColors.infoColor,
text: LocaleKeys.recharge.tr(context: context),
backgroundColor: AppColors.habibPharmacyColor.withAlpha(15),
textColor: AppColors.habibPharmacyColor,
text: LocaleKeys.redeem.tr(context: context),
borderWidth: 0.w,
fontWeight: FontWeight.w600,
borderColor: AppColors.infoColor.withAlpha(15),
borderColor: AppColors.habibPharmacyColor.withAlpha(15),
padding: EdgeInsets.fromLTRB(4, 0, 12, 0),
fontSize: 14.f,
onPressed: () {
Navigator.of(context).push(CustomPageRoute(page: RechargeWalletPage()));
Uri uri = Uri.parse(PHARMACY_REDIRECT_URL);
launchUrl(uri, mode: LaunchMode.externalApplication);
// Navigator.of(context).push(CustomPageRoute(page: RechargeWalletPage()));
},
),
),
@ -165,7 +169,7 @@ class LakumWalletCard extends StatelessWidget {
).onPress(() {
Navigator.of(context).push(
CustomPageRoute(
page: HabibWalletPage(),
page: LakumWalletDetails(),
),
);
}),
@ -179,7 +183,7 @@ class LakumWalletCard extends StatelessWidget {
).paddingSymmetrical(0.h, 0.h).onPress(() {
Navigator.of(context).push(
CustomPageRoute(
page: HabibWalletPage(),
page: LakumWalletDetails(),
),
);
}),

@ -593,7 +593,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
)
: Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.bgRedLightColor,
// color: AppColors.bgRedLightColor,
borderRadius: 12.r,
hasShadow: false,
),

@ -157,13 +157,14 @@ class _FamilyCardsState extends State<FamilyCards> {
// ),
// SizedBox(height: 24.h),
CustomExpandableList(
expansionMode: ExpansionMode.exactlyOne,
expansionMode: ExpansionMode.multiple,
dividerColor: AppColors.dividerColor,
itemPadding: EdgeInsets.symmetric(vertical: 16.h, horizontal: 14.h),
items: [
ExpandableListItem(
title: LocaleKeys.whoCanViewMyMedicalFile.tr(context: context).toText18(isBold: true),
expandedBackgroundColor: Colors.transparent,
initiallyExpanded: true,
children: [
SizedBox(height: 10.h),
manageFamily()
@ -612,6 +613,7 @@ class _FamilyCardsState extends State<FamilyCards> {
: Expanded(
child: CustomButton(
height: 40.h,
fontSize: 14.f,
text: LocaleKeys.acceptLbl.tr(),
onPressed: () {
navigationService.pop();
@ -628,6 +630,7 @@ class _FamilyCardsState extends State<FamilyCards> {
Expanded(
child: CustomButton(
height: 40.h,
fontSize: 14.f,
text: profile.status == FamilyFileEnum.active.toInt ? LocaleKeys.removeMember.tr() : LocaleKeys.rejectView.tr(),
onPressed: () {
navigationService.pop();

@ -5,21 +5,18 @@ import 'package:flutter/material.dart';
import 'package:flutter_staggered_animations/flutter_staggered_animations.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart';
import 'package:hmg_patient_app_new/features/my_invoices/my_invoices_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/presentation/appointments/widgets/appointment_card.dart';
import 'package:hmg_patient_app_new/presentation/lab/lab_result_item_view.dart';
import 'package:hmg_patient_app_new/presentation/my_invoices/my_invoices_details_page.dart';
import 'package:hmg_patient_app_new/presentation/my_invoices/widgets/invoice_filter_bottom_sheet.dart';
import 'package:hmg_patient_app_new/presentation/my_invoices/widgets/invoice_list_card.dart';
import 'package:hmg_patient_app_new/presentation/my_invoices/widgets/pharmacy_invoice_card.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/custom_tab_bar.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'package:open_filex/open_filex.dart';
import 'package:provider/provider.dart';
@ -124,194 +121,302 @@ class _MyInvoicesListState extends State<MyInvoicesList> {
crossAxisAlignment: CrossAxisAlignment.start,
children: [
SizedBox(height: 16.h),
Row(
children: [
CustomButton(
text: LocaleKeys.allInvoices.tr(context: context),
onPressed: () {
myInvoicesViewModel.filterInvoices(InvoiceFilterType.all);
},
backgroundColor: myInvoicesVM.currentFilter == InvoiceFilterType.all ? AppColors.bgRedLightColor : AppColors.whiteColor,
borderColor: myInvoicesVM.currentFilter == InvoiceFilterType.all ? AppColors.primaryRedColor : AppColors.textColor.withOpacity(0.2),
textColor: myInvoicesVM.currentFilter == InvoiceFilterType.all ? AppColors.primaryRedColor : AppColors.blackColor,
fontSize: 12,
fontWeight: FontWeight.w600,
borderRadius: 10,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40.h,
),
SizedBox(width: 8.h),
CustomButton(
text: LocaleKeys.hospitals.tr(context: context),
onPressed: () {
if (myInvoicesVM.getOriginalInvoicesList().isEmpty) return;
_showHospitalFilterBottomSheet(context);
},
backgroundColor: myInvoicesVM.currentFilter == InvoiceFilterType.hospital ? AppColors.bgRedLightColor : AppColors.whiteColor,
borderColor: myInvoicesVM.currentFilter == InvoiceFilterType.hospital ? AppColors.primaryRedColor : AppColors.textColor.withOpacity(0.2),
textColor: myInvoicesVM.currentFilter == InvoiceFilterType.hospital ? AppColors.primaryRedColor : AppColors.blackColor,
fontSize: 12,
fontWeight: FontWeight.w600,
borderRadius: 10,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40.h,
),
SizedBox(width: 8.h),
CustomButton(
text: LocaleKeys.clinics.tr(context: context),
onPressed: () {
if (myInvoicesVM.getOriginalInvoicesList().isEmpty) return;
_showClinicFilterBottomSheet(context);
},
backgroundColor: myInvoicesVM.currentFilter == InvoiceFilterType.clinic ? AppColors.bgRedLightColor : AppColors.whiteColor,
borderColor: myInvoicesVM.currentFilter == InvoiceFilterType.clinic ? AppColors.primaryRedColor : AppColors.textColor.withOpacity(0.2),
textColor: myInvoicesVM.currentFilter == InvoiceFilterType.clinic ? AppColors.primaryRedColor : AppColors.blackColor,
fontSize: 12,
fontWeight: FontWeight.w600,
borderRadius: 10,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40.h,
),
SizedBox(width: 8.h),
CustomButton(
text: LocaleKeys.doctors.tr(context: context),
onPressed: () {
if (myInvoicesVM.getOriginalInvoicesList().isEmpty) return;
_showDoctorFilterBottomSheet(context);
},
backgroundColor: myInvoicesVM.currentFilter == InvoiceFilterType.doctor ? AppColors.bgRedLightColor : AppColors.whiteColor,
borderColor: myInvoicesVM.currentFilter == InvoiceFilterType.doctor ? AppColors.primaryRedColor : AppColors.textColor.withOpacity(0.2),
textColor: myInvoicesVM.currentFilter == InvoiceFilterType.doctor ? AppColors.primaryRedColor : AppColors.blackColor,
fontSize: 12,
fontWeight: FontWeight.w600,
borderRadius: 10,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40.h,
),
CustomTabBar(
activeTextColor: Color(0xffED1C2B),
activeBackgroundColor: Color(0xffED1C2B).withValues(alpha: .1),
initialIndex: 0,
tabs: [
CustomTabBarModel(null, LocaleKeys.visitsOrders.tr(context: context)),
CustomTabBarModel(null, LocaleKeys.pharmacy.tr(context: context)),
],
onTabChange: (index) {
myInvoicesVM.onTabChanged(index);
if (index == 1 && myInvoicesVM.allPharmacyInvoicesList.isEmpty) {
// Load pharmacy invoices when pharmacy tab is selected and list is empty
myInvoicesVM.getAllPharmacyInvoices();
}
},
).paddingSymmetrical(24.h, 0.h),
SizedBox(height: 16.h),
ListView.builder(
itemCount: myInvoicesVM.isInvoicesListLoading
? 4
: myInvoicesVM.allInvoicesList.isEmpty
? 1
: myInvoicesVM.allInvoicesList.length,
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
padding: EdgeInsetsGeometry.zero,
itemBuilder: (context, index) {
return myInvoicesVM.isInvoicesListLoading
? LabResultItemView(onTap: () {}, labOrder: null, index: index, isLoading: true)
: myInvoicesVM.allInvoicesList.isNotEmpty
? AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 500),
child: SlideAnimation(
verticalOffset: 100.0,
child: FadeInAnimation(
child: AnimatedContainer(
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
child: InvoiceListCard(
getInvoicesListResponseModel: myInvoicesVM.allInvoicesList[index],
onTap: () async {
if (Utils.isVidaPlusProject(myInvoicesVM.allInvoicesList[index].projectId ?? 0)) {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.sendingEmailPleaseWait.tr(context: context));
await myInvoicesViewModel.sendInvoiceEmail(
appointmentNo: myInvoicesVM.allInvoicesList[index].appointmentNo!,
projectID: myInvoicesVM.allInvoicesList[index].projectId!,
onSuccess: (val) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(context,
child: Utils.getSuccessWidget(loadingText: LocaleKeys.emailSentSuccessfullyMessage.tr(context: context)),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
isAutoDismiss: true);
},
onError: (err) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: err),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
} else {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.loadingText.tr(context: context));
myInvoicesViewModel.downloadInvoicePDF(
setupId: myInvoicesVM.allInvoicesList[index].setupId!,
invoiceNo: myInvoicesVM.allInvoicesList[index].invoiceNo!,
projectID: myInvoicesVM.allInvoicesList[index].projectId!,
onError: (err) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: err),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
},
onSuccess: (value) async {
LoaderBottomSheet.hideLoader();
if (myInvoicesViewModel.downloadInvoicePDFBase64!.isNotEmpty) {
String path = await Utils.createFileFromString(myInvoicesViewModel.downloadInvoicePDFBase64!, "pdf");
try {
OpenFilex.open(path);
} catch (ex) {
myInvoicesVM.selectedTabIndex == 0 ? Column(
children: [
SizedBox(height: 16.h),
Row(
children: [
CustomButton(
text: LocaleKeys.allInvoices.tr(context: context),
onPressed: () {
myInvoicesViewModel.filterInvoices(InvoiceFilterType.all);
},
backgroundColor: myInvoicesVM.currentFilter == InvoiceFilterType.all ? AppColors.bgRedLightColor : AppColors.whiteColor,
borderColor: myInvoicesVM.currentFilter == InvoiceFilterType.all ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2),
textColor: myInvoicesVM.currentFilter == InvoiceFilterType.all ? AppColors.primaryRedColor : AppColors.blackColor,
fontSize: 12,
fontWeight: FontWeight.w600,
borderRadius: 10,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40.h,
),
SizedBox(width: 8.h),
CustomButton(
text: LocaleKeys.hospitals.tr(context: context),
onPressed: () {
if (myInvoicesVM.getOriginalInvoicesList().isEmpty) return;
_showHospitalFilterBottomSheet(context);
},
backgroundColor: myInvoicesVM.currentFilter == InvoiceFilterType.hospital ? AppColors.bgRedLightColor : AppColors.whiteColor,
borderColor: myInvoicesVM.currentFilter == InvoiceFilterType.hospital ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2),
textColor: myInvoicesVM.currentFilter == InvoiceFilterType.hospital ? AppColors.primaryRedColor : AppColors.blackColor,
fontSize: 12,
fontWeight: FontWeight.w600,
borderRadius: 10,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40.h,
),
SizedBox(width: 8.h),
CustomButton(
text: LocaleKeys.clinics.tr(context: context),
onPressed: () {
if (myInvoicesVM.getOriginalInvoicesList().isEmpty) return;
_showClinicFilterBottomSheet(context);
},
backgroundColor: myInvoicesVM.currentFilter == InvoiceFilterType.clinic ? AppColors.bgRedLightColor : AppColors.whiteColor,
borderColor: myInvoicesVM.currentFilter == InvoiceFilterType.clinic ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2),
textColor: myInvoicesVM.currentFilter == InvoiceFilterType.clinic ? AppColors.primaryRedColor : AppColors.blackColor,
fontSize: 12,
fontWeight: FontWeight.w600,
borderRadius: 10,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40.h,
),
SizedBox(width: 8.h),
CustomButton(
text: LocaleKeys.doctors.tr(context: context),
onPressed: () {
if (myInvoicesVM.getOriginalInvoicesList().isEmpty) return;
_showDoctorFilterBottomSheet(context);
},
backgroundColor: myInvoicesVM.currentFilter == InvoiceFilterType.doctor ? AppColors.bgRedLightColor : AppColors.whiteColor,
borderColor: myInvoicesVM.currentFilter == InvoiceFilterType.doctor ? AppColors.primaryRedColor : AppColors.textColor.withValues(alpha: 0.2),
textColor: myInvoicesVM.currentFilter == InvoiceFilterType.doctor ? AppColors.primaryRedColor : AppColors.blackColor,
fontSize: 12,
fontWeight: FontWeight.w600,
borderRadius: 10,
padding: EdgeInsets.fromLTRB(10, 0, 10, 0),
height: 40.h,
),
],
).paddingSymmetrical(24.h, 0.h),
SizedBox(height: 16.h),
ListView.builder(
itemCount: myInvoicesVM.isInvoicesListLoading
? 4
: myInvoicesVM.allInvoicesList.isEmpty
? 1
: myInvoicesVM.allInvoicesList.length,
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
padding: EdgeInsetsGeometry.zero,
itemBuilder: (context, index) {
return myInvoicesVM.isInvoicesListLoading
? LabResultItemView(onTap: () {}, labOrder: null, index: index, isLoading: true)
: myInvoicesVM.allInvoicesList.isNotEmpty
? AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 500),
child: SlideAnimation(
verticalOffset: 100.0,
child: FadeInAnimation(
child: AnimatedContainer(
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
child: InvoiceListCard(
getInvoicesListResponseModel: myInvoicesVM.allInvoicesList[index],
onTap: () async {
if (Utils.isVidaPlusProject(myInvoicesVM.allInvoicesList[index].projectId ?? 0)) {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.sendingEmailPleaseWait.tr(context: context));
await myInvoicesViewModel.sendInvoiceEmail(
appointmentNo: myInvoicesVM.allInvoicesList[index].appointmentNo!,
projectID: myInvoicesVM.allInvoicesList[index].projectId!,
onSuccess: (val) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(context,
child: Utils.getSuccessWidget(loadingText: LocaleKeys.emailSentSuccessfullyMessage.tr(context: context)),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
isAutoDismiss: true);
},
onError: (err) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: err),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
} else {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.loadingText.tr(context: context));
myInvoicesViewModel.downloadInvoicePDF(
setupId: myInvoicesVM.allInvoicesList[index].setupId!,
invoiceNo: myInvoicesVM.allInvoicesList[index].invoiceNo!,
projectID: myInvoicesVM.allInvoicesList[index].projectId!,
onError: (err) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: "Cannot open file"),
child: Utils.getErrorWidget(loadingText: err),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
}
}
},
);
}
},
// onTap: () async {
// myInvoicesVM.setInvoiceDetailLoading();
// LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingInvoiceDetails.tr(context: context));
// await myInvoicesVM.getInvoiceDetails(
// appointmentNo: myInvoicesVM.allInvoicesList[index].appointmentNo!,
// invoiceNo: myInvoicesVM.allInvoicesList[index].invoiceNo!,
// projectID: myInvoicesVM.allInvoicesList[index].projectId!,
// onSuccess: (val) {
// LoaderBottomSheet.hideLoader();
// Navigator.of(context).push(
// CustomPageRoute(
// page: MyInvoicesDetailsPage(
// getInvoiceDetailsResponseModel: myInvoicesVM.invoiceDetailsResponseModel,
// getInvoicesListResponseModel: myInvoicesVM.allInvoicesList[index],
// ),
// ),
// );
// },
// onError: (err) {
// LoaderBottomSheet.hideLoader();
// showCommonBottomSheetWithoutHeight(
// context,
// child: Utils.getErrorWidget(loadingText: err),
// callBackFunc: () {},
// isFullScreen: false,
// isCloseButtonVisible: true,
// );
// });
// },
},
onSuccess: (value) async {
LoaderBottomSheet.hideLoader();
if (myInvoicesViewModel.downloadInvoicePDFBase64!.isNotEmpty) {
String path = await Utils.createFileFromString(myInvoicesViewModel.downloadInvoicePDFBase64!, "pdf");
try {
OpenFilex.open(path);
} catch (ex) {
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: "Cannot open file"),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
}
}
},
);
}
},
// onTap: () async {
// myInvoicesVM.setInvoiceDetailLoading();
// LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingInvoiceDetails.tr(context: context));
// await myInvoicesVM.getInvoiceDetails(
// appointmentNo: myInvoicesVM.allInvoicesList[index].appointmentNo!,
// invoiceNo: myInvoicesVM.allInvoicesList[index].invoiceNo!,
// projectID: myInvoicesVM.allInvoicesList[index].projectId!,
// onSuccess: (val) {
// LoaderBottomSheet.hideLoader();
// Navigator.of(context).push(
// CustomPageRoute(
// page: MyInvoicesDetailsPage(
// getInvoiceDetailsResponseModel: myInvoicesVM.invoiceDetailsResponseModel,
// getInvoicesListResponseModel: myInvoicesVM.allInvoicesList[index],
// ),
// ),
// );
// },
// onError: (err) {
// LoaderBottomSheet.hideLoader();
// showCommonBottomSheetWithoutHeight(
// context,
// child: Utils.getErrorWidget(loadingText: err),
// callBackFunc: () {},
// isFullScreen: false,
// isCloseButtonVisible: true,
// );
// });
// },
),
),
),
),
),
),
)
: Utils.getNoDataWidget(context);
}).paddingSymmetrical(24.w, 0.h),
)
: Utils.getNoDataWidget(context);
}).paddingSymmetrical(24.w, 0.h),
],
)
: Column(
children: [
SizedBox(height: 16.h),
ListView.builder(
itemCount: myInvoicesVM.isPharmacyInvoicesLoading
? 4
: myInvoicesVM.allPharmacyInvoicesList.isEmpty
? 1
: myInvoicesVM.allPharmacyInvoicesList.length,
physics: NeverScrollableScrollPhysics(),
shrinkWrap: true,
padding: EdgeInsetsGeometry.zero,
itemBuilder: (context, index) {
return myInvoicesVM.isPharmacyInvoicesLoading
? LabResultItemView(onTap: () {}, labOrder: null, index: index, isLoading: true)
: myInvoicesVM.allPharmacyInvoicesList.isNotEmpty
? AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 500),
child: SlideAnimation(
verticalOffset: 100.0,
child: FadeInAnimation(
child: AnimatedContainer(
duration: Duration(milliseconds: 300),
curve: Curves.easeInOut,
child: PharmacyInvoiceCard(
pharmacyInvoice: myInvoicesVM.allPharmacyInvoicesList[index],
onTap: () async {
// if (Utils.isVidaPlusProject(myInvoicesVM.allPharmacyInvoicesList[index].projectID ?? 0)) {
// // For Vida+ projects, send email (if API is available)
// showCommonBottomSheetWithoutHeight(
// context,
// child: Utils.getSuccessWidget(
// loadingText: LocaleKeys.comingSoon.tr(context: context),
// ),
// callBackFunc: () {},
// isFullScreen: false,
// isCloseButtonVisible: true,
// isAutoDismiss: true,
// );
// } else {
// Download pharmacy invoice PDF
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.loadingText.tr(context: context));
myInvoicesViewModel.downloadPharmacyInvoicePDF(
setupId: myInvoicesVM.allPharmacyInvoicesList[index].setupId ?? "",
invoiceNo: myInvoicesVM.allPharmacyInvoicesList[index].invoiceNo ?? 0,
projectID: myInvoicesVM.allPharmacyInvoicesList[index].pharmacyProjectID ?? 0,
locationID: myInvoicesVM.allPharmacyInvoicesList[index].locationID ?? 0,
onError: (err) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: err),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
},
onSuccess: (value) async {
LoaderBottomSheet.hideLoader();
if (myInvoicesViewModel.downloadPharmacyInvoicePDFBase64!.isNotEmpty) {
String path = await Utils.createFileFromString(myInvoicesViewModel.downloadPharmacyInvoicePDFBase64!, "pdf");
try {
OpenFilex.open(path);
} catch (ex) {
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: "Cannot open file"),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
}
}
},
);
// }
},
),
),
),
),
)
: Utils.getNoDataWidget(context);
}).paddingSymmetrical(24.w, 0.h),
],
),
],
);
}),

@ -0,0 +1,186 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_state.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/my_invoices/models/get_pharmacy_invoices_response_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
import 'dart:ui' as ui;
class PharmacyInvoiceCard extends StatelessWidget {
final GetPharmacyInvoicesResponseModel pharmacyInvoice;
final VoidCallback onTap;
const PharmacyInvoiceCard({
super.key,
required this.pharmacyInvoice,
required this.onTap,
});
@override
Widget build(BuildContext context) {
final isArabic = getIt<AppState>().isArabic();
return Container(
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.whiteColor,
borderRadius: 20.h,
hasShadow: true,
),
child: Padding(
padding: EdgeInsets.all(14.h),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Type chips
// Wrap(
// alignment: WrapAlignment.start,
// direction: Axis.horizontal,
// spacing: 6.w,
// runSpacing: 6.h,
// children: [
// AppCustomChipWidget(
// icon: AppAssets.pharmacy_icon,
// iconColor: AppColors.primaryRedColor,
// labelText: LocaleKeys.pharmacy.tr(context: context),
// textColor: AppColors.primaryRedColor,
// ),
// if (pharmacyInvoice.totalAmount != null)
// AppCustomChipWidget(
// labelText: '${pharmacyInvoice.totalAmount!.toStringAsFixed(2)} ${LocaleKeys.sar.tr(context: context)}',
// backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.1),
// textColor: AppColors.primaryRedColor,
// isEnglishOnly: true,
// ),
// ],
// ),
SizedBox(height: 16.h),
// Main content row
Row(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Column(
children: [
Image.network(
pharmacyInvoice.doctorImageURL ?? 'https://hmgwebservices.com/Images/MobileImages/DUBAI/unkown_female.png',
width: 63.h,
height: 63.h,
fit: BoxFit.cover,
).circle(100.r),
Transform.translate(
offset: Offset(0.0, -20.h),
child: Container(
width: 40.w,
height: 40.h,
decoration: BoxDecoration(
color: AppColors.whiteColor,
shape: BoxShape.circle,
border: Border.all(
color: AppColors.scaffoldBgColor,
width: 1.5.w,
),
),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Utils.buildSvgWithAssets(icon: AppAssets.rating_icon, width: 15.w, height: 15.h, applyThemeColor: false),
SizedBox(height: 2.h),
"${pharmacyInvoice.decimalDoctorRate ?? '0.0'}".toText11(isBold: true, color: AppColors.textColor),
],
),
).circle(100),
),
],
),
SizedBox(width: 16.w),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
(getIt<AppState>().isArabic()
? (pharmacyInvoice.doctorNameN ?? pharmacyInvoice.doctorName ?? LocaleKeys.doctor.tr(context: context))
: (pharmacyInvoice.doctorName ?? pharmacyInvoice.doctorNameN ?? LocaleKeys.doctor.tr(context: context))
).toText16(isBold: true),
SizedBox(height: 8.h),
Wrap(
direction: Axis.horizontal,
spacing: 6.w,
runSpacing: 6.h,
children: [
AppCustomChipWidget(
labelText: "${LocaleKeys.invoiceNo.tr(context: context)}: ${pharmacyInvoice.invoiceNo ?? '-'}",
labelPadding: EdgeInsetsDirectional.only(start: 6.w, end: 6.w),
isEnglishOnly: true,
),
AppCustomChipWidget(
labelText: ((pharmacyInvoice.clinicName ?? LocaleKeys.clinic.tr(context: context)).length > 15
? '${(pharmacyInvoice.clinicName ?? LocaleKeys.clinic.tr(context: context)).substring(0, 12)}...'
: (pharmacyInvoice.clinicName ?? LocaleKeys.clinic.tr(context: context))),
labelPadding: EdgeInsetsDirectional.only(start: 4.w, end: 4.w),
),
AppCustomChipWidget(
labelText: pharmacyInvoice.projectName ?? LocaleKeys.hospital.tr(context: context),
labelPadding: EdgeInsetsDirectional.only(start: 6.w, end: 6.w),
),
if (pharmacyInvoice.appointmentDate != null)
Directionality(
textDirection: ui.TextDirection.ltr,
child: AppCustomChipWidget(
labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w),
icon: AppAssets.doctor_calendar_icon,
labelText: DateUtil.formatDateToDate(DateUtil.convertStringToDate(pharmacyInvoice.appointmentDate), false),
isEnglishOnly: true,
),
),
],
),
],
),
),
],
),
SizedBox(height: 16.h),
// Action button
CustomButton(
text: Utils.isVidaPlusProject(pharmacyInvoice.projectID ?? 0)
? LocaleKeys.sendEmail.tr(context: context)
: LocaleKeys.downloadInvoice.tr(context: context),
onPressed: onTap,
backgroundColor: AppColors.primaryRedColor.withValues(alpha: 0.1),
borderColor: AppColors.primaryRedColor.withValues(alpha: 0.01),
textColor: AppColors.primaryRedColor,
fontSize: 14.f,
fontWeight: FontWeight.w600,
borderRadius: 12.r,
padding: EdgeInsets.symmetric(horizontal: 10.w),
height: 40.h,
iconSize: 14.h,
),
],
),
),
).paddingOnly(bottom: 16.h);
}
String _formatDate(String? dateString) {
if (dateString == null || dateString.isEmpty) return '-';
try {
final date = DateUtil.convertStringToDate(dateString);
return DateUtil.formatDateToDate(date, false);
} catch (e) {
return dateString;
}
}
}

@ -53,11 +53,17 @@ class ProfileSettingsState extends State<ProfileSettings> {
@override
void initState() {
scheduleMicrotask(() {
insuranceViewModel.initInsuranceProvider();
});
_loadPermissions();
super.initState();
_loadPermissions();
// Load data after first frame
WidgetsBinding.instance.addPostFrameCallback((_) {
if (mounted) {
insuranceViewModel.initInsuranceProvider();
// Load profile settings for preferred language
profileSettingsViewModel.getProfileSettings();
}
});
}
@override
@ -96,12 +102,14 @@ class ProfileSettingsState extends State<ProfileSettings> {
final SwiperController _controller = SwiperController();
late InsuranceViewModel insuranceViewModel;
late ContactUsViewModel contactUsViewModel;
late ProfileSettingsViewModel profileSettingsViewModel;
String _permissionsLabel = "";
@override
Widget build(BuildContext context) {
insuranceViewModel = Provider.of<InsuranceViewModel>(context, listen: false);
contactUsViewModel = Provider.of<ContactUsViewModel>(context, listen: false);
profileSettingsViewModel = Provider.of<ProfileSettingsViewModel>(context, listen: false);
return CollapsingListView(
title: LocaleKeys.profileAndSettings.tr(context: context),
logout: () {
@ -268,9 +276,9 @@ class ProfileSettingsState extends State<ProfileSettings> {
actionItem(AppAssets.language, LocaleKeys.communicationLanguage.tr(context: context), () {
profileVm.openPreferredLanguageBottomSheet();
},
trailingLabel: profileVm.isPatientProfileLoading
trailingLabel: profileVm.isPatientProfileLoading || profileVm.getPatientInfoForUpdate == null
? "English"
: (profileVm.getPatientInfoForUpdate.preferredLanguage! == "1" ? LocaleKeys.arabic.tr(context: context) : LocaleKeys.english.tr(context: context)),
: (profileVm.getPatientInfoForUpdate!.preferredLanguage! == "1" ? LocaleKeys.arabic.tr(context: context) : LocaleKeys.english.tr(context: context)),
isShowLoading: profileVm.isPatientProfileLoading),
1.divider,
actionItem(AppAssets.smart_phone_fill, LocaleKeys.emrgNo.tr(context: context), () {

@ -29,7 +29,7 @@ class _PreferredLanguageWidgetState extends State<PreferredLanguageWidget> {
super.initState();
WidgetsBinding.instance.addPostFrameCallback((_) {
setState(() {
selectedValue = profileSettingsViewModel.getPatientInfoForUpdate.preferredLanguage!;
selectedValue = profileSettingsViewModel.getPatientInfoForUpdate?.preferredLanguage!;
});
});
}
@ -60,9 +60,9 @@ class _PreferredLanguageWidgetState extends State<PreferredLanguageWidget> {
profileSettingsViewModel!.updatePatientInfo(
patientInfo: {
"PreferredLanguage": selectedValue,
"EmailAddress": profileSettingsViewModel.getPatientInfoForUpdate.emailAddress,
"EmergencyContactName": profileSettingsViewModel.getPatientInfoForUpdate.emergencyContactName,
"EmergencyContactNo": profileSettingsViewModel.getPatientInfoForUpdate.emergencyContactNo,
"EmailAddress": profileSettingsViewModel.getPatientInfoForUpdate!.emailAddress,
"EmergencyContactName": profileSettingsViewModel.getPatientInfoForUpdate!.emergencyContactName,
"EmergencyContactNo": profileSettingsViewModel.getPatientInfoForUpdate!.emergencyContactNo,
"IsEmailAlertRequired": true,
"IsSMSAlertRequired": true,
},

@ -39,7 +39,7 @@ class _UpdateEmailDialogState extends State<UpdateEmailDialog> {
// Set the text
setState(() {
textController!.text = viewModel.getPatientInfoForUpdate.emailAddress ?? "";
textController!.text = viewModel.getPatientInfoForUpdate!.emailAddress ?? "";
});
});
@ -118,10 +118,10 @@ class _UpdateEmailDialogState extends State<UpdateEmailDialog> {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.updatingEmailAddress.tr(context: context));
profileSettingsViewModel!.updatePatientInfo(
patientInfo: {
"PreferredLanguage": profileSettingsViewModel!.getPatientInfoForUpdate.preferredLanguage,
"PreferredLanguage": profileSettingsViewModel!.getPatientInfoForUpdate!.preferredLanguage,
"EmailAddress": textController!.text,
"EmergencyContactName": profileSettingsViewModel!.getPatientInfoForUpdate.emergencyContactName,
"EmergencyContactNo": profileSettingsViewModel!.getPatientInfoForUpdate.emergencyContactNo,
"EmergencyContactName": profileSettingsViewModel!.getPatientInfoForUpdate!.emergencyContactName,
"EmergencyContactNo": profileSettingsViewModel!.getPatientInfoForUpdate!.emergencyContactNo,
"IsEmailAlertRequired": true,
"IsSMSAlertRequired": true,
},

@ -1,11 +1,13 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/features/profile_settings/profile_settings_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
@ -39,7 +41,7 @@ class _UpdateEmergencyContactDialogState extends State<UpdateEmergencyContactDia
// Set the text
setState(() {
textController!.text = viewModel.getPatientInfoForUpdate.emergencyContactNo ?? "";
textController!.text = viewModel.getPatientInfoForUpdate!.emergencyContactNo ?? "";
});
});
@ -118,9 +120,9 @@ class _UpdateEmergencyContactDialogState extends State<UpdateEmergencyContactDia
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.loadingText.tr(context: context));
profileSettingsViewModel!.updatePatientInfo(
patientInfo: {
"PreferredLanguage": profileSettingsViewModel!.getPatientInfoForUpdate.preferredLanguage,
"EmailAddress": profileSettingsViewModel!.getPatientInfoForUpdate.emailAddress,
"EmergencyContactName": profileSettingsViewModel!.getPatientInfoForUpdate.emergencyContactName,
"PreferredLanguage": profileSettingsViewModel!.getPatientInfoForUpdate!.preferredLanguage,
"EmailAddress": profileSettingsViewModel!.getPatientInfoForUpdate!.emailAddress,
"EmergencyContactName": profileSettingsViewModel!.getPatientInfoForUpdate!.emergencyContactName,
"EmergencyContactNo": textController!.text,
"IsEmailAlertRequired": true,
"IsSMSAlertRequired": true,
@ -130,7 +132,7 @@ class _UpdateEmergencyContactDialogState extends State<UpdateEmergencyContactDia
profileSettingsViewModel!.clearEmergencyContactError();
showCommonBottomSheetWithoutHeight(context, title: LocaleKeys.success.tr(context: context), child: Utils.getSuccessWidget(loadingText: LocaleKeys.success.tr()),
callBackFunc: () async {
Navigator.of(context).pop();
Navigator.of(getIt.get<NavigationService>().navigatorKey.currentContext!).pop();
profileSettingsViewModel!.getProfileSettings();
}, isFullScreen: false, isAutoDismiss: true);
},

@ -302,6 +302,18 @@ class _PossibleConditionsPageState extends State<PossibleConditionsPage> {
// Set flag to indicate booking is from symptoms checker
symptomsCheckerViewModel.setBookingFromSymptomsChecker(true);
// Check if clinicDetailsList is not empty before accessing
if (symptomsCheckerViewModel.clinicDetailsList.isEmpty) {
// Show error if no clinic details available
final dialogService = getIt<DialogService>();
dialogService.showErrorBottomSheet(
message: appState.isArabic()
? "لا توجد تفاصيل عيادة متاحة"
: "No clinic details available",
);
return;
}
// bookAppointmentsViewModel.getLocation();
bookAppointmentsViewModel.setSelectedClinic(GetClinicsListResponseModel(
clinicID: symptomsCheckerViewModel.clinicDetailsList.first.clinicID,
@ -395,9 +407,17 @@ class _PossibleConditionsPageState extends State<PossibleConditionsPage> {
//if the region screen is opened for the dental clinic then the project id will be in the hospital list as the list is formed form the get project api
var id = "";
if (data.regionBottomSheetType == RegionBottomSheetType.REGION_FOR_DENTAL_AND_LASER) {
id = regionalViewModel.selectedHospital?.hospitalList.first.iD?.toString() ?? "";
// Check if hospitalList is not empty before accessing first
if (regionalViewModel.selectedHospital?.hospitalList != null &&
regionalViewModel.selectedHospital!.hospitalList.isNotEmpty) {
id = regionalViewModel.selectedHospital!.hospitalList.first.iD?.toString() ?? "";
}
} else {
id = regionalViewModel.selectedHospital?.patientDoctorAppointmentList?.first.projectID?.toString() ?? "";
// Check if patientDoctorAppointmentList is not empty before accessing first
if (regionalViewModel.selectedHospital?.patientDoctorAppointmentList != null &&
regionalViewModel.selectedHospital!.patientDoctorAppointmentList!.isNotEmpty) {
id = regionalViewModel.selectedHospital!.patientDoctorAppointmentList!.first.projectID?.toString() ?? "";
}
}
bookAppointmentsViewModel.setProjectID(id);
return SizedBox.shrink();

@ -16,6 +16,8 @@ import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/payfort/models/apple_pay_request_insert_model.dart';
import 'package:hmg_patient_app_new/features/payfort/payfort_view_model.dart';
import 'package:hmg_patient_app_new/features/paytabs/models/paytabs_transaction_response_model.dart';
import 'package:hmg_patient_app_new/features/paytabs/paytabs_view_model.dart';
import 'package:hmg_patient_app_new/features/todo_section/models/resp_models/ancillary_order_procedures_detail_response_model.dart';
import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
@ -55,6 +57,7 @@ class _AncillaryOrderPaymentPageState extends State<AncillaryOrderPaymentPage> {
late PayfortViewModel payfortViewModel;
late AppState appState;
late TodoSectionViewModel todoSectionViewModel;
late PayTabsViewModel paytabsViewModel;
MyInAppBrowser? browser;
String selectedPaymentMethod = "";
@ -74,6 +77,7 @@ class _AncillaryOrderPaymentPageState extends State<AncillaryOrderPaymentPage> {
appState = getIt.get<AppState>();
todoSectionViewModel = Provider.of<TodoSectionViewModel>(context);
payfortViewModel = Provider.of<PayfortViewModel>(context);
paytabsViewModel = Provider.of<PayTabsViewModel>(context, listen: false);
return Scaffold(
backgroundColor: AppColors.bgScaffoldColor,
@ -125,7 +129,34 @@ class _AncillaryOrderPaymentPageState extends State<AncillaryOrderPaymentPage> {
).paddingSymmetrical(24.h, 0.h).onPress(() {
if (!todoVM.isProcessingPayment) {
selectedPaymentMethod = "MADA";
_openPaymentURL("mada");
if (appState.isPaytabsEnabled) {
paytabsViewModel.setPaymentConfiguration(
"Ancillary Orders Payment",
widget.totalAmount,
);
paytabsViewModel.startCardPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async {
final paymentAmount = widget.totalAmount;
final fortId = transactionData.transactionReference;
final paymentMethod = selectedPaymentMethod;
// Call createAdvancePayment with the payment details
_createAdvancePayment(
paymentAmount: paymentAmount.toDouble(),
paymentReference: fortId!,
paymentMethod: paymentMethod,
);
}, onError: (err) {
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: err.toString()),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
} else {
_openPaymentURL("mada");
}
}
}),
@ -172,7 +203,34 @@ class _AncillaryOrderPaymentPageState extends State<AncillaryOrderPaymentPage> {
).paddingSymmetrical(24.h, 0.h).onPress(() {
if (!todoVM.isProcessingPayment) {
selectedPaymentMethod = "VISA";
_openPaymentURL("visa");
if (appState.isPaytabsEnabled) {
paytabsViewModel.setPaymentConfiguration(
"Ancillary Orders Payment",
widget.totalAmount,
);
paytabsViewModel.startCardPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async {
final paymentAmount = widget.totalAmount;
final fortId = transactionData.transactionReference;
final paymentMethod = selectedPaymentMethod;
// Call createAdvancePayment with the payment details
_createAdvancePayment(
paymentAmount: paymentAmount.toDouble(),
paymentReference: fortId!,
paymentMethod: paymentMethod,
);
}, onError: (err) {
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: err.toString()),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
} else {
_openPaymentURL("visa");
}
}
}),
],
@ -573,33 +631,61 @@ class _AncillaryOrderPaymentPageState extends State<AncillaryOrderPaymentPage> {
return;
}
// Only proceed with Apple Pay if insert was successful
payfortViewModel.paymentWithApplePay(
customerName: "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}",
customerEmail: "CustID_${appState.getAuthenticatedUser()!.patientId.toString()}@HMG.com",
orderDescription: "Ancillary Order Payment",
orderAmount: widget.totalAmount,
merchantReference: transID,
merchantIdentifier: payfortViewModel.payfortProjectDetailsRespModel!.merchantIdentifier,
applePayAccessCode: payfortViewModel.payfortProjectDetailsRespModel!.accessCode,
applePayShaRequestPhrase: payfortViewModel.payfortProjectDetailsRespModel!.shaRequest,
currency: appState.getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED",
onFailed: (failureResult) async {
log("failureResult: ${failureResult.message.toString()}");
LoaderBottomSheet.hideLoader();
if(appState.isPaytabsEnabled) {
LoaderBottomSheet.hideLoader();
paytabsViewModel.setPaymentConfiguration(
"Ancillary Orders Payment",
widget.totalAmount,
);
paytabsViewModel.startApplePayPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async {
final paymentAmount = widget.totalAmount;
final fortId = transactionData.transactionReference;
final paymentMethod = selectedPaymentMethod;
// Call createAdvancePayment with the payment details
_createAdvancePayment(
paymentAmount: paymentAmount.toDouble(),
paymentReference: fortId!,
paymentMethod: paymentMethod,
);
}, onError: (err) {
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: failureResult.message.toString()),
child: Utils.getErrorWidget(loadingText: err.toString()),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
},
onSucceeded: (successResult) async {
LoaderBottomSheet.hideLoader();
log("successResult: ${successResult.responseMessage.toString()}");
selectedPaymentMethod = successResult.paymentOption ?? "VISA";
_checkPaymentStatus();
},
);
});
} else {
payfortViewModel.paymentWithApplePay(
customerName: "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}",
customerEmail: "CustID_${appState.getAuthenticatedUser()!.patientId.toString()}@HMG.com",
orderDescription: "Ancillary Order Payment",
orderAmount: widget.totalAmount,
merchantReference: transID,
merchantIdentifier: payfortViewModel.payfortProjectDetailsRespModel!.merchantIdentifier,
applePayAccessCode: payfortViewModel.payfortProjectDetailsRespModel!.accessCode,
applePayShaRequestPhrase: payfortViewModel.payfortProjectDetailsRespModel!.shaRequest,
currency: appState.getAuthenticatedUser()!.outSa! == 0 ? "SAR" : "AED",
onFailed: (failureResult) async {
log("failureResult: ${failureResult.message.toString()}");
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: failureResult.message.toString()),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
},
onSucceeded: (successResult) async {
LoaderBottomSheet.hideLoader();
log("successResult: ${successResult.responseMessage.toString()}");
selectedPaymentMethod = successResult.paymentOption ?? "VISA";
_checkPaymentStatus();
},
);
}
}
}

@ -40,6 +40,7 @@ class AppColors {
static Color get lightGreenColor => isDarkMode ? dark.lightGreenColor : const Color(0xFF0ccedde);
static Color get ratingColorYellow => isDarkMode ? dark.ratingColorYellow : const Color(0xFFFFAF15);
static Color get spacerLineColor => isDarkMode ? dark.spacerLineColor : const Color(0x2E30391A);
static Color get habibPharmacyColor => const Color(0xFF2d9137);
// Doctor Rating Colors
static const Color ratingStarIconColor = Color(0xFFFFA726); // Orange-amber for star icons

@ -5,6 +5,7 @@ import 'package:hmg_patient_app_new/extensions/int_extensions.dart';
import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/emergency_services/emergency_services_view_model.dart';
import 'package:hmg_patient_app_new/features/habib_wallet/habib_wallet_view_model.dart';
import 'package:hmg_patient_app_new/features/immediate_livecare/immediate_livecare_view_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_view_model.dart';
import 'package:hmg_patient_app_new/features/todo_section/todo_section_view_model.dart';
@ -29,6 +30,7 @@ class _AppLanguageChangeState extends State<AppLanguageChange> {
late ImmediateLiveCareViewModel immediateLiveCareViewModel;
late EmergencyServicesViewModel emergencyServicesViewModel;
late TodoSectionViewModel todoSectionViewModel;
late HabibWalletViewModel habibWalletVM;
@override
void initState() {
@ -48,6 +50,7 @@ class _AppLanguageChangeState extends State<AppLanguageChange> {
immediateLiveCareViewModel = Provider.of<ImmediateLiveCareViewModel>(context, listen: false);
emergencyServicesViewModel = Provider.of<EmergencyServicesViewModel>(context, listen: false);
todoSectionViewModel = Provider.of<TodoSectionViewModel>(context, listen: false);
habibWalletVM = context.read<HabibWalletViewModel>();
return Column(
spacing: 24.h,
@ -72,6 +75,10 @@ class _AppLanguageChangeState extends State<AppLanguageChange> {
myAppointmentsViewModel.initAppointmentsViewModel();
myAppointmentsViewModel.getPatientAppointments(true, false);
habibWalletVM.initHabibWalletProvider();
habibWalletVM.getPatientBalanceAmount();
habibWalletVM.getLakumAccountInformation();
//Reload Immediate LiveCare Data
immediateLiveCareViewModel.initImmediateLiveCare();
immediateLiveCareViewModel.getPatientLiveCareHistory();

@ -150,6 +150,9 @@ class GenericBottomSheetState extends State<GenericBottomSheet> {
keyboardType: widget.isForEmail ? TextInputType.emailAddress : TextInputType.number,
onChange: (value) {
if (widget.onChange != null) {
if (Utils.isArabicText(value!)) {
value = Utils.toEnglishNumbers(value);
}
widget.onChange!(value);
}
},

@ -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.30+29
version: 0.0.4+1
#version: 0.0.32+31
version: 0.0.5+2
environment:
sdk: ">=3.6.0 <4.0.0"
@ -101,11 +101,11 @@ dependencies:
flutter_widget_from_html: ^0.17.1
huawei_map: ^6.12.0+301
flutter_image_compress: ^2.3.0
scrollable_positioned_list: ^0.3.8
in_app_review: ^2.0.11
flutter_paytabs_bridge: ^2.7.13
dev_dependencies:
flutter_test:
sdk: flutter

Loading…
Cancel
Save