Merge branch 'master' into faiz_dev_symptoms_checker

# Conflicts:
#	assets/langs/ar-SA.json
#	lib/generated/locale_keys.g.dart
pull/327/head
faizatflutter 3 days ago
commit c8378278c9

@ -77,4 +77,12 @@
-keep class com.peng.pennavmap.db.** { *; }
-keep class com.hiennv.flutter_callkit_incoming.** { *; }
-keepattributes Signature, Annotation, InnerClasses, EnclosingMethod
-keepattributes Signature, Annotation, InnerClasses, EnclosingMethod
-keep class com.peng.pennavmap.** { *; }
-keep class com.peng.bus.** { *; }
# EventBus subscribers (reflection-based)
-keepclassmembers class * {
@com.peng.bus.PISubscribe <methods>;
}
-keep class com.peng.pennavmap.models.bus.BackButtonEventBusData { *; }

@ -41,6 +41,11 @@
android:name="android.permission.FOREGROUND_SERVICE_MICROPHONE"
tools:node="remove" />
<!-- Gallery/Photo permissions for Android 13+ (API 33+) -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES" tools:node="remove" />
<!-- <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" />-->
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO" tools:node="remove" />
<!-- Gallery/Photo permissions for Android 12 and below -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />

@ -0,0 +1,5 @@
<svg width="40" height="40" viewBox="0 0 40 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<rect width="40" height="40" rx="10" fill="#EFEFF0"/>
<path d="M21.0004 20.0004H18.0004V16.4004C18.0004 16.2034 18.0008 16.0957 18.005 16.0174L18.0057 16.0057L18.0174 16.005C18.0957 16.0008 18.2034 16.0004 18.4004 16.0004H21.0004C22.105 16.0004 23.0004 16.8958 23.0004 18.0004C23.0004 19.105 22.105 20.0004 21.0004 20.0004Z" fill="#8F9AA3"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M20.0513 9.75H19.9487C17.8773 9.74999 16.2537 9.74998 14.9758 9.90522C13.6698 10.0639 12.6246 10.3941 11.7657 11.1277C11.5366 11.3233 11.3233 11.5366 11.1277 11.7657C10.3941 12.6246 10.0639 13.6698 9.90522 14.9758C9.74998 16.2538 9.74999 17.8773 9.75 19.9487V20.0513C9.74999 22.1227 9.74998 23.7462 9.90522 25.0242C10.0639 26.3302 10.3941 27.3754 11.1277 28.2343C11.3233 28.4634 11.5366 28.6767 11.7657 28.8723C12.6246 29.6059 13.6698 29.9361 14.9758 30.0948C16.2538 30.25 17.8773 30.25 19.9487 30.25H20.0513C22.1227 30.25 23.7462 30.25 25.0242 30.0948C26.3302 29.9361 27.3754 29.6059 28.2343 28.8723C28.4634 28.6767 28.6767 28.4634 28.8723 28.2343C29.6059 27.3754 29.9361 26.3302 30.0948 25.0242C30.25 23.7462 30.25 22.1227 30.25 20.0513V19.9487C30.25 17.8773 30.25 16.2538 30.0948 14.9758C29.9361 13.6698 29.6059 12.6246 28.8723 11.7657C28.6767 11.5366 28.4634 11.3233 28.2343 11.1277C27.3754 10.3941 26.3302 10.0639 25.0242 9.90522C23.7463 9.74998 22.1227 9.74999 20.0513 9.75ZM18.4004 14.0004L18.3194 14.0002C18.0377 13.9992 17.6913 13.9979 17.3824 14.0983C16.7735 14.2961 16.2961 14.7735 16.0983 15.3824C15.9979 15.6913 15.9992 16.0377 16.0002 16.3194L16.0004 16.4004V26.0004C16.0004 26.5527 16.4481 27.0004 17.0004 27.0004C17.5527 27.0004 18.0004 26.5527 18.0004 26.0004V22.0004H21.0004C23.2096 22.0004 25.0004 20.2096 25.0004 18.0004C25.0004 15.7913 23.2096 14.0004 21.0004 14.0004H18.4004Z" fill="#8F9AA3"/>
</svg>

After

Width:  |  Height:  |  Size: 1.9 KiB

@ -230,6 +230,17 @@
"companyName": "اسم الشركة:",
"receiptOn": "الإيصال على:",
"expiryDate": "تاريخ الانتهاء:",
"myCards": "بطاقاتي",
"savedCards": "البطاقات المحفوظة",
"addNewCard": "إضافة بطاقة جديدة",
"noSavedCards": "لا توجد بطاقات محفوظة",
"cardEndingWith": "البطاقة المنتهية بـ",
"expiry": "الصلاحية",
"defaultCard": "افتراضي",
"deleteCard": "حذف البطاقة؟",
"deleteCardConfirmation": "هل أنت متأكد من حذف هذه البطاقة المنتهية بـ",
"cardDeletedSuccessfully": "تم حذف البطاقة بنجاح",
"addCardComingSoon": "وظيفة إضافة البطاقة قريباً",
"expiryPoints": "منتهي الصلاحية",
"expiryOn": "ينتهي في:",
"procedureName": "اسم الإجراء:",
@ -1893,6 +1904,8 @@
"transactions": "المعاملات",
"pharmacy": "الصيدلية",
"visitsOrders": "الزيارات/الطلبات",
"earned": "حصل",
"getParkingQR": "الحصول على رمز الاستجابة السريعة لوقوف السيارات",
"cannotOpenThisFile": "لا يمكن فتح هذا الملف",
"refundDetails": "تفاصيل الاسترداد",
"requestNo": "رقم الطلب.",
@ -1900,5 +1913,8 @@
"advanceNumber": "رقم الدفعة المقدمة",
"amountOnly": "المبلغ",
"invoiceHistory": "سجل الفواتير",
"refund": "استرداد",
"invoiceHistory": "سجل الفواتير",
"thisInvoiceIsNotEligibleForRefund": "هذه الفاتورة غير مؤهلة للاسترداد",
"refund": "استرداد"
}

@ -227,6 +227,17 @@
"companyName": "Company Name:",
"receiptOn": "Receipt on:",
"expiryDate": "Expiry Date:",
"myCards": "My Cards",
"savedCards": "Saved Cards",
"addNewCard": "Add New Card",
"noSavedCards": "No saved cards",
"cardEndingWith": "Card ending with",
"expiry": "Expiry",
"defaultCard": "Default",
"deleteCard": "Delete Card?",
"deleteCardConfirmation": "Are you sure you want to delete this card ending in",
"cardDeletedSuccessfully": "Card deleted successfully",
"addCardComingSoon": "Add card functionality coming soon",
"expiryPoints": "Expired",
"expiryOn": "Expiry on:",
"procedureName": "Procedure Name:",
@ -1884,6 +1895,8 @@
"transactions": "Transactions",
"pharmacy": "Pharmacy",
"visitsOrders": "Visits/Orders",
"earned": "Earned",
"getParkingQR": "Get Parking QR",
"cannotOpenThisFile": "Cannot open this file",
"thisInvoiceIsNotEligibleForRefund": "This invoice is not eligible for refund",
"refundDetails": "Refund Details",

@ -1,5 +1,5 @@
# Uncomment this line to define a global platform for your project
platform :ios, '14.0'
platform :ios, '15.0'
# CocoaPods analytics sends network stats synchronously affecting flutter build latency.
ENV['COCOAPODS_DISABLE_STATS'] = 'true'
@ -54,6 +54,7 @@ post_install do |installer|
'PERMISSION_EVENTS_FULL_ACCESS=1',
## dart: PermissionGroup.reminders
'PERMISSION_REMINDERS=1',
'PERMISSION_PHOTOS=1',
## dart: PermissionGroup.notification
'PERMISSION_NOTIFICATIONS=1',
]

@ -4,6 +4,7 @@ import 'dart:developer';
import 'dart:io';
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/api/http_client_manager.dart';
import 'package:hmg_patient_app_new/core/api_consts.dart';
@ -243,10 +244,13 @@ class ApiClientImp implements ApiClient {
// Handle body encoding based on isBodyPlainText flag
final dynamic requestBody = isBodyPlainText ? body : json.encode(body);
debugPrint("uri: ${Uri.parse(url.trim())}");
var requestBodyJSON = json.encode(body);
// debugPrint("body: $requestBodyJSON", wrapWidth: 2048);
log("body: $requestBodyJSON");
if (kDebugMode) {
debugPrint("uri: ${Uri.parse(url.trim())}");
var requestBodyJSON = json.encode(body);
debugPrint("body: $requestBodyJSON", wrapWidth: 2048);
// log("body: $requestBodyJSON");
}
http.Response response;
try {

@ -290,7 +290,7 @@ class ApiConsts {
static String googleCloudStorageENTranslationFileBaseURL = "https://storage.googleapis.com/hmg-patientapp-translations";
// ************ static values for Api ****************
static final double appVersionID = 21.2;
static final double appVersionID = 21.3;
// static final double appVersionID = 50.7;
static final int appChannelId = 3;
@ -1007,6 +1007,8 @@ const DOWNLOAD_INVOICE_PDF = 'Services/Notifications.svc/REST/DownloadInvoiceRep
const DOWNLOAD_PHARMACY_INVOICE_PDF = 'Services/Notifications.svc/REST/DownloadInvoiceReport';
const GET_APPOINTMENT_PARKING_QR = 'Services/outps.svc/rest/pms_getParkingTicketByAppointmentNo';
class ApiKeyConstants {
static final String googleMapsApiKey = 'AIzaSyB6TERnxIr0yJ3qG4ULBZbu0sAD4tGqtng';
}

@ -256,6 +256,7 @@ class AppAssets {
static const String back_top_nav_icon = '$svgBasePath/back_top_nav_icon.svg';
static const String bluetooth = '$svgBasePath/bluetooth.svg';
static const String calendar_filled_icon = '$svgBasePath/calendar_filled_icon.svg';
static const String appointment_parking_icon = '$svgBasePath/appointment_parking_icon.svg';
//smartwatch
static const String watchActivity = '$svgBasePath/watch_activity.svg';

@ -48,6 +48,11 @@ class AppState {
bool isPaytabsEnabled = false;
void setIsPaytabsEnabled(bool value) {
isPaytabsEnabled = value;
// isPaytabsEnabled = false;
}
int? _superUserID;
bool isChildLoggedIn = false;
bool isGMSAvailable = true;

@ -18,7 +18,7 @@ class DateUtil {
final endIndex = date.indexOf(end, startIndex + start.length);
return DateTime.fromMillisecondsSinceEpoch(int.parse(
date.substring(startIndex + start.length, endIndex),
));
), isUtc: true).add(Duration(hours: 3));
}
static DateTime convertStringToDateSaudiTimezone(String date, int projectId) {

@ -49,10 +49,10 @@ _incomingCall(Map<String, dynamic> data) async {
log('the value of the _incomingCall remote message is $data');
// Check if there's already a call in progress to prevent duplicates
if (_isCallInProgress && _currentCallId != null) {
log('⚠️ Call already in progress (ID: $_currentCallId), ignoring duplicate notification');
return;
}
// if (_isCallInProgress && _currentCallId != null) {
// log('⚠️ Call already in progress (ID: $_currentCallId), ignoring duplicate notification');
// return;
// }
String roomID = data['session_id'] ?? '';
String callTypeID = data['AppointmentNo'] ?? '';

@ -771,6 +771,7 @@ class Utils {
double height = 24,
BoxFit fit = BoxFit.cover,
bool applyThemeColor = true,
String? errorAsset,
}) {
final Color? resolvedColor = iconColor ?? (applyThemeColor && AppColors.isDarkMode ? AppColors.textColor : null);
return SvgPicture.asset(
@ -779,6 +780,15 @@ class Utils {
width: width,
height: height,
fit: fit,
errorBuilder: errorAsset != null
? (context, error, stackTrace) => SvgPicture.asset(
errorAsset,
colorFilter: resolvedColor != null ? ColorFilter.mode(isDisabled ? resolvedColor.withOpacity(0.5) : resolvedColor, BlendMode.srcIn) : null,
width: width,
height: height,
fit: fit,
)
: null,
);
}
@ -1144,4 +1154,30 @@ class Utils {
}
return result;
}
/// Normalize Arabic text for search by handling Hamza variations and removing diacritics
/// This makes searching more flexible for Arabic text
static String normalizeArabicText(String text) {
if (text.isEmpty) return text;
String normalized = text;
// Normalize Hamza variations to a single character 'ا'
normalized = normalized.replaceAll('أ', 'ا'); // Hamza on Alif
normalized = normalized.replaceAll('إ', 'ا'); // Hamza below Alif
normalized = normalized.replaceAll('آ', 'ا'); // Madda on Alif
normalized = normalized.replaceAll('ء', 'ا'); // Hamza alone
normalized = normalized.replaceAll('ؤ', 'و'); // Hamza on Waw
normalized = normalized.replaceAll('ئ', 'ي'); // Hamza on Ya
// Normalize Alif Maqsura to Ya
normalized = normalized.replaceAll('ى', 'ي');
// Remove Arabic diacritics (Tashkeel)
normalized = normalized.replaceAll(RegExp(r'[\u064B-\u065F]'), ''); // Fatha, Damma, Kasra, etc.
normalized = normalized.replaceAll(RegExp(r'[\u0670]'), ''); // Superscript Alif
normalized = normalized.replaceAll(RegExp(r'[\u0640]'), ''); // Tatweel
return normalized;
}
}

@ -1221,6 +1221,7 @@ class AuthenticationViewModel extends ChangeNotifier {
_appState.setAuthenticatedUser(activation.list!.first);
_appState.setPrivilegeModelList(activation.list!.first.listPrivilege!);
_appState.setUserBloodGroup = activation.patientBlodType ?? "N/A";
_appState.setIsPaytabsEnabled(Utils.havePrivilege(120));
// Refresh privileges after user switching to ensure correct privileges are loaded
if (isSwitchUser) {
@ -1409,15 +1410,14 @@ class AuthenticationViewModel extends ChangeNotifier {
checkLastLoginStatus(Function() onSuccess) async {
Future.delayed(Duration(seconds: 1), () async {
if (cacheService.getBool(key: CacheConst.quickLoginEnabled) == null) {
if (_appState.getSelectDeviceByImeiRespModelElement != null &&
(_appState.getSelectDeviceByImeiRespModelElement!.logInType == 1 || _appState.getSelectDeviceByImeiRespModelElement!.logInType == 4)) {
final deviceElement = _appState.getSelectDeviceByImeiRespModelElement;
if (deviceElement != null && (deviceElement.logInType == 1 || deviceElement.logInType == 4)) {
phoneNumberController.text = (_appState.getAuthenticatedUser()!.mobileNumber!.startsWith("0")
? _appState.getAuthenticatedUser()!.mobileNumber!.replaceFirst("0", "")
: _appState.getAuthenticatedUser()!.mobileNumber)!;
nationalIdController.text = _appState.getAuthenticatedUser()!.patientIdentificationNo!;
onSuccess();
} else if ((loginTypeEnum == LoginTypeEnum.sms || loginTypeEnum == LoginTypeEnum.whatsapp && _appState.getSelectDeviceByImeiRespModelElement == null) &&
_appState.getAuthenticatedUser() != null) {
} else if ((loginTypeEnum == LoginTypeEnum.sms || loginTypeEnum == LoginTypeEnum.whatsapp && deviceElement == null) && _appState.getAuthenticatedUser() != null) {
phoneNumberController.text = (_appState.getAuthenticatedUser()!.mobileNumber!.startsWith("0")
? _appState.getAuthenticatedUser()!.mobileNumber!.replaceFirst("0", "")
: _appState.getAuthenticatedUser()!.mobileNumber)!;
@ -1668,14 +1668,26 @@ class AuthenticationViewModel extends ChangeNotifier {
patientId: _appState.getSelectDeviceByImeiRespModelElement!.patientId!,
patientType: _appState.getSelectDeviceByImeiRespModelElement!.patientType,
patientOutSa: _appState.getSelectDeviceByImeiRespModelElement!.outSa == true ? 1 : 0,
projectOutSa: _appState.getSelectDeviceByImeiRespModelElement!.outSa,
loginType: loginType,
languageId: _appState.getLanguageID(),
latitude: _appState.userLat,
longitude: _appState.userLong,
mobileNo: _appState.getSelectDeviceByImeiRespModelElement!.mobile!,
patientMobileNumber: int.parse(_appState.getSelectDeviceByImeiRespModelElement!.mobile!),
nationalId: _appState.getSelectDeviceByImeiRespModelElement!.identificationNo)
nationalId: _appState.getSelectDeviceByImeiRespModelElement!.identificationNo,
zipCode: _appState.getSelectDeviceByImeiRespModelElement!.isOther == true
? "0"
: _appState.getSelectDeviceByImeiRespModelElement!.outSa == true
? CountryEnum.unitedArabEmirates.countryCode
: CountryEnum.saudiArabia.countryCode,
isRegister: false,
logInTokenId: "",
searchType: 2,
patientIdentificationId: "0",
otpSendType: loginType)
.toJson());
resultEither.fold((failure) async => await _errorHandlerService.handleError(failure: failure), (apiResponse) async {
if (apiResponse.messageStatus == 1) {
dynamic deviceInfo = apiResponse.data['List_MobileLoginInfo'];
@ -1752,6 +1764,7 @@ class AuthenticationViewModel extends ChangeNotifier {
projectDetailListModel.add(ProjectDetailListModel.fromJson(v));
});
_appState.setProjectsDetailList(projectDetailListModel);
_appState.setIsPaytabsEnabled(Utils.havePrivilege(120));
}
},
);

@ -372,7 +372,15 @@ class BookAppointmentsViewModel extends ChangeNotifier {
_filteredClinicsList = List.from(clinicsList);
showSortFilterButtons = false;
} else {
_filteredClinicsList = clinicsList.where((clinic) => clinic.clinicDescription?.toLowerCase().contains(query!.toLowerCase()) ?? false).toList();
// Normalize the search query for better Arabic text matching
String normalizedQuery = Utils.normalizeArabicText(query.toLowerCase());
_filteredClinicsList = clinicsList.where((clinic) {
String clinicName = clinic.clinicDescription?.toLowerCase() ?? '';
String normalizedClinicName = Utils.normalizeArabicText(clinicName);
return normalizedClinicName.contains(normalizedQuery);
}).toList();
showSortFilterButtons = query.length >= 3;
}
notifyListeners();
@ -735,7 +743,9 @@ class BookAppointmentsViewModel extends ChangeNotifier {
result.fold(
(failure) async {
onError?.call(selectedClinic.clinicID == 23 ? failure.message : LocaleKeys.noDoctorFound.tr());
if (onError != null) {
onError.call(selectedClinic.clinicID == 23 ? failure.message : LocaleKeys.noDoctorFound.tr());
}
},
(apiResponse) async {
if (apiResponse.messageStatus == 2) {

@ -117,6 +117,13 @@ class EmergencyServicesViewModel extends ChangeNotifier {
bool historyLoading = false;
OrderDislpay currentlyDisplayedOrder = OrderDislpay.ALL;
bool isAdvanceERBalanceNeedToBeLoaded = true;
setIsAdvanceERBalanceNeedToBeLoaded(bool value) {
isAdvanceERBalanceNeedToBeLoaded = value;
notifyListeners();
}
setSelectedRRTProcedure(RRTProceduresResponseModel procedure) {
selectedRRTProcedure = procedure;
notifyListeners();
@ -331,6 +338,8 @@ class EmergencyServicesViewModel extends ChangeNotifier {
}
Future<void> checkPatientERAdvanceBalance({Function(dynamic)? onSuccess, Function(String)? onError}) async {
if (!isAdvanceERBalanceNeedToBeLoaded) return;
final result = await emergencyServicesRepo.checkPatientERAdvanceBalance();
result.fold(
@ -338,6 +347,7 @@ class EmergencyServicesViewModel extends ChangeNotifier {
(failure) {
patientHasAdvanceERBalance = false;
isERBookAppointment = true;
isAdvanceERBalanceNeedToBeLoaded = true;
if (onSuccess != null) {
onSuccess(failure.message);
}
@ -347,9 +357,11 @@ class EmergencyServicesViewModel extends ChangeNotifier {
// dialogService.showErrorDialog(message: apiResponse.errorMessage!, onOkPressed: () {});
patientHasAdvanceERBalance = false;
isERBookAppointment = true;
isAdvanceERBalanceNeedToBeLoaded = true;
} else if (apiResponse.messageStatus == 1) {
patientHasAdvanceERBalance = apiResponse.data;
isERBookAppointment = !patientHasAdvanceERBalance;
isAdvanceERBalanceNeedToBeLoaded = false;
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);

@ -30,6 +30,7 @@ class HabibWalletViewModel extends ChangeNotifier {
num habibWalletAmount = 0;
num walletRechargeAmount = 0;
String notesText = "";
bool isWalletAmountToBeLoaded = true;
bool isBottomSheetContentLoading = false;
@ -324,10 +325,12 @@ class HabibWalletViewModel extends ChangeNotifier {
}
initHabibWalletProvider() {
isWalletAmountLoading = true;
if (isWalletAmountToBeLoaded) {
isWalletAmountLoading = true;
habibWalletAmount = 0;
isLakumAccountInfoLoading = true;
}
isBottomSheetContentLoading = false;
isLakumAccountInfoLoading = true;
habibWalletAmount = 0;
walletRechargeAmount = 0;
selectedRechargeType = 1;
advancePaymentHospitals.clear();
@ -392,6 +395,11 @@ class HabibWalletViewModel extends ChangeNotifier {
notifyListeners();
}
setIsWalletAmountToBeLoaded(bool value) {
isWalletAmountToBeLoaded = value;
notifyListeners();
}
String getSelectedRechargeTypeValue() {
switch (selectedRechargeType) {
case 1:
@ -406,6 +414,9 @@ class HabibWalletViewModel extends ChangeNotifier {
}
Future<void> getPatientBalanceAmount({Function(dynamic)? onSuccess, Function(String)? onError}) async {
if (!isWalletAmountToBeLoaded) {
return;
}
isWalletAmountLoading = true;
notifyListeners();
@ -425,6 +436,7 @@ class HabibWalletViewModel extends ChangeNotifier {
habibWalletBalanceList.removeWhere((element) => element.patientAdvanceBalanceAmount == 0);
isWalletAmountLoading = false;
isWalletAmountToBeLoaded = false;
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
@ -462,12 +474,7 @@ class HabibWalletViewModel extends ChangeNotifier {
Function(dynamic)? onSuccess,
Function(String)? onError}) async {
final result = await habibWalletRepo.HISCreateAdvancePayment(
paymentMethodName: paymentMethodName,
paidAmount: paidAmount,
paymentReference: paymentReference,
patientID: patientID,
projectID: projectID,
depositorName: depositorName);
paymentMethodName: paymentMethodName, paidAmount: paidAmount, paymentReference: paymentReference, patientID: patientID, projectID: projectID, depositorName: depositorName);
result.fold(
// (failure) async => await errorHandlerService.handleError(failure: failure),
@ -489,8 +496,7 @@ class HabibWalletViewModel extends ChangeNotifier {
);
}
Future<void> addAdvanceNumberRequest(
{required String advanceNumber, required String paymentReference, Function(dynamic)? onSuccess, Function(String)? onError}) async {
Future<void> addAdvanceNumberRequest({required String advanceNumber, required String paymentReference, Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result = await habibWalletRepo.addAdvanceNumberRequest(advanceNumber: advanceNumber, paymentReference: paymentReference);
result.fold(
@ -534,12 +540,15 @@ class HabibWalletViewModel extends ChangeNotifier {
}
Future<void> getLakumAccountInformation({Function(dynamic)? onSuccess, Function(String)? onError}) async {
if (!isWalletAmountToBeLoaded) {
return;
}
isLakumAccountInfoLoading = true;
lakumAccountInfo = LakumInquiryInformationResponseModel();
notifyListeners();
final result = await habibWalletRepo.getLakumAccountInformation(
identificationNumber: getIt.get<AppState>().getAuthenticatedUser()!.patientIdentificationNo ?? "");
final result = await habibWalletRepo.getLakumAccountInformation(identificationNumber: getIt.get<AppState>().getAuthenticatedUser()!.patientIdentificationNo ?? "");
result.fold(
(failure) async => await errorHandlerService.handleError(failure: failure),

@ -99,7 +99,7 @@ class LakumInquiryInformationResponseModel {
memberName = json['MemberName'];
memberUniversalId = json['MemberUniversalId'];
mobileNumber = json['MobileNumber'];
pointsBalance = json['PointsBalance'];
pointsBalance = json['PointsBalance'] ?? 0;
pointsBalanceAmount = json['PointsBalanceAmount'];
pointsWillBeExpired = json['PointsWillBeExpired'];
prefLang = json['PrefLang'];

@ -0,0 +1,84 @@
class AppointmentParkingQRResponseModel {
String? admissionDate;
int? admissionNo;
String? appointmentDate;
int? appointmentNo;
int? createdBy;
String? createdOn;
String? gUID;
String? invoiceDate;
int? invoiceNo;
bool? isActive;
bool? isOutInPatient;
int? isPatient;
bool? isUsed;
int? patientID;
int? projectID;
dynamic ticketBase64;
int? ticketID;
String? ticketURL;
AppointmentParkingQRResponseModel(
{this.admissionDate,
this.admissionNo,
this.appointmentDate,
this.appointmentNo,
this.createdBy,
this.createdOn,
this.gUID,
this.invoiceDate,
this.invoiceNo,
this.isActive,
this.isOutInPatient,
this.isPatient,
this.isUsed,
this.patientID,
this.projectID,
this.ticketBase64,
this.ticketID,
this.ticketURL});
AppointmentParkingQRResponseModel.fromJson(Map<String, dynamic> json) {
admissionDate = json['AdmissionDate'];
admissionNo = json['AdmissionNo'];
appointmentDate = json['AppointmentDate'];
appointmentNo = json['AppointmentNo'];
createdBy = json['CreatedBy'];
createdOn = json['CreatedOn'];
gUID = json['GUID'];
invoiceDate = json['InvoiceDate'];
invoiceNo = json['InvoiceNo'];
isActive = json['IsActive'];
isOutInPatient = json['IsOutInPatient'];
isPatient = json['IsPatient'];
isUsed = json['IsUsed'];
patientID = json['PatientID'];
projectID = json['ProjectID'];
ticketBase64 = json['TicketBase64'].cast<int>();
ticketID = json['TicketID'];
ticketURL = json['TicketURL'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
data['AdmissionDate'] = this.admissionDate;
data['AdmissionNo'] = this.admissionNo;
data['AppointmentDate'] = this.appointmentDate;
data['AppointmentNo'] = this.appointmentNo;
data['CreatedBy'] = this.createdBy;
data['CreatedOn'] = this.createdOn;
data['GUID'] = this.gUID;
data['InvoiceDate'] = this.invoiceDate;
data['InvoiceNo'] = this.invoiceNo;
data['IsActive'] = this.isActive;
data['IsOutInPatient'] = this.isOutInPatient;
data['IsPatient'] = this.isPatient;
data['IsUsed'] = this.isUsed;
data['PatientID'] = this.patientID;
data['ProjectID'] = this.projectID;
data['TicketBase64'] = this.ticketBase64;
data['TicketID'] = this.ticketID;
data['TicketURL'] = this.ticketURL;
return data;
}
}

@ -76,6 +76,7 @@ class PatientAppointmentHistoryResponseModel {
num? patientTaxAmount;
String? doctorNationalityFlagURL;
bool? isClinicReBookingAllowed;
bool? isParkingAvailable;
PatientAppointmentHistoryResponseModel({
this.setupID,
@ -154,6 +155,7 @@ class PatientAppointmentHistoryResponseModel {
this.patientTaxAmount,
this.doctorNationalityFlagURL,
this.isClinicReBookingAllowed,
this.isParkingAvailable,
});
PatientAppointmentHistoryResponseModel.fromJson(Map<String, dynamic> json) {
@ -244,6 +246,7 @@ class PatientAppointmentHistoryResponseModel {
patientTaxAmount = json['PatientTaxAmount'];
doctorNationalityFlagURL = json['DoctorNationalityFlagURL'];
isClinicReBookingAllowed = json['IsClinicReBookingAllowed'];
isParkingAvailable = json['IsParkingAvailable'];
}
Map<String, dynamic> toJson() {
@ -317,6 +320,7 @@ class PatientAppointmentHistoryResponseModel {
data['SMSButtonVisable'] = this.sMSButtonVisable;
data['ServiceID'] = this.serviceID;
data['IsClinicReBookingAllowed'] = this.isClinicReBookingAllowed;
data['IsParkingAvailable'] = this.isParkingAvailable;
return data;
}
}

@ -9,6 +9,7 @@ import 'package:hmg_patient_app_new/core/exceptions/api_failure.dart';
import 'package:hmg_patient_app_new/core/utils/date_util.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_favorite_doctors_list.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/appointment_parking_QR_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/appointment_rated_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/rate_appointment_resp_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/ask_doctor_request_type_response_model.dart';
@ -92,6 +93,8 @@ abstract class MyAppointmentsRepo {
});
Future<Either<Failure, GenericApiModel<List<GetInvoicesListResponseModel>>>> getAppointmentInvoice(num appointmentNum);
Future<Either<Failure, GenericApiModel<AppointmentParkingQRResponseModel>>> getParkingQR(num appointmentNum, int projectID);
}
class MyAppointmentsRepoImp implements MyAppointmentsRepo {
@ -1183,4 +1186,40 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo {
return Left(UnknownFailure(e.toString()));
}
}
@override
Future<Either<Failure, GenericApiModel<AppointmentParkingQRResponseModel>>> getParkingQR(num appointmentNum, int projectID) async {
Map<String, dynamic> mapDevice = {"AppointmentNo": appointmentNum, "ProjectID": projectID};
try {
GenericApiModel<AppointmentParkingQRResponseModel>? apiResponse;
Failure? failure;
await apiClient.post(
GET_APPOINTMENT_PARKING_QR,
body: mapDevice,
onFailure: (error, statusCode, {messageStatus, failureType}) {
failure = failureType;
},
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
AppointmentParkingQRResponseModel appointmentParkingQRResponseModel = AppointmentParkingQRResponseModel.fromJson(response['ParkingTicketsForAppointment'][0]);
apiResponse = GenericApiModel<AppointmentParkingQRResponseModel>(
messageStatus: messageStatus,
statusCode: statusCode,
errorMessage: null,
data: appointmentParkingQRResponseModel,
);
} 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()));
}
}
}

@ -10,6 +10,7 @@ import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/features/book_appointments/models/resp_models/get_favorite_doctors_list.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/appointemnet_filters.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/reminder_type.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/appointment_parking_QR_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/appointment_rated_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/ask_doctor_request_type_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/get_tamara_installments_details_response_model.dart';
@ -100,6 +101,8 @@ class MyAppointmentsViewModel extends ChangeNotifier {
AppointmentRatedResponseModel? appointmentRatedResponseModel;
bool isAppointmentRatedResponseLoading = false;
AppointmentParkingQRResponseModel? appointmentParkingQRResponseModel;
MyAppointmentsViewModel({required this.myAppointmentsRepo, required this.errorHandlerService, required this.appState});
void onTabChange(int index) {
@ -722,6 +725,25 @@ class MyAppointmentsViewModel extends ChangeNotifier {
);
}
Future<void> getParkingQR(num appointmentNum, int projectID, {Function(dynamic)? onSuccess, Function(String)? onError}) async {
final result = await myAppointmentsRepo.getParkingQR(appointmentNum, projectID);
result.fold(
(failure) async {
if (onError != null) {
onError(failure.message);
}
},
(apiResponse) {
appointmentParkingQRResponseModel = apiResponse.data!;
notifyListeners();
if (onSuccess != null) {
onSuccess(apiResponse);
}
},
);
}
// Method to force refresh favorite doctors list
void refreshFavouriteDoctors() {
isFavouriteDoctorsDataFetched = false;

@ -1,5 +1,5 @@
class PaytabsTransactionResponseModel {
int? tranTotal;
dynamic tranTotal;
String? transactionReference;
String? cartCurrency;
String? cartDescription;
@ -13,10 +13,10 @@ class PaytabsTransactionResponseModel {
bool? isAuthorized;
String? trace;
dynamic cartAmount;
int? merchantId;
int? profileId;
dynamic merchantId;
dynamic profileId;
bool? isProcessed;
int? serviceId;
dynamic serviceId;
PaymentInfo? paymentInfo;
bool? isSuccess;
@ -98,8 +98,8 @@ class PaytabsTransactionResponseModel {
class PaymentInfo {
String? cardScheme;
String? cardType;
int? expiryMonth;
int? expiryYear;
dynamic expiryMonth;
dynamic expiryYear;
String? paymentDescription;
String? paymentMethod;

@ -3,6 +3,7 @@ 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/PaymentSdkTokenFormat.dart';
import 'package:flutter_paytabs_bridge/PaymentSdkTokeniseType.dart';
import 'package:flutter_paytabs_bridge/PaymentSdkTransactionType.dart';
import 'package:flutter_paytabs_bridge/flutter_paytabs_bridge.dart';
@ -21,7 +22,7 @@ class PayTabsViewModel extends ChangeNotifier {
required this.appState,
});
setPaymentConfiguration(String paymentDescription, num amount) {
setPaymentConfiguration(String paymentDescription, num amount, {bool isAddCard = false}) {
String cartID = DateTime.now().millisecondsSinceEpoch.toString();
paymentConfiguration = PaymentSdkConfigurationDetails(
profileId: PaymentSdkDefaultConfig.profileID,
@ -62,7 +63,8 @@ class PayTabsViewModel extends ChangeNotifier {
"12626"),
alternativePaymentMethods: [],
linkBillingNameWithCardHolderName: true,
simplifyApplePayValidation: true);
simplifyApplePayValidation: true,
);
paymentConfiguration.iOSThemeConfigurations = IOSThemeConfigurations(
// logoImage: "assets/images/png/hmg_logo.png",
@ -84,17 +86,24 @@ class PayTabsViewModel extends ChangeNotifier {
placeholderColorDark: "2E3039",
buttonColor: "ED1C2B",
buttonColorDark: "ED1C2B",
// buttonFont: "Poppins",
);
paymentConfiguration.tokeniseType = PaymentSdkTokeniseType.MERCHANT_MANDATORY;
inputsCornerRadius: 12
// buttonFont: "Poppins",
);
paymentConfiguration.tokeniseType = PaymentSdkTokeniseType.USER_OPTIONAL_DEFAULT_ON;
paymentConfiguration.tokenFormat = PaymentSdkTokenFormat.Hex32Format;
notifyListeners();
}
startApplePayPayment({Function(PaytabsTransactionResponseModel)? onSuccess, Function(String)? onError}) {
FlutterPaytabsBridge.startApplePayPayment(paymentConfiguration, (event) {
startApplePayPayment({Function(PaytabsTransactionResponseModel)? onSuccess, Function(String)? onError}) async {
bool _callbackFired = false; // prevent duplicate callback invocations
await FlutterPaytabsBridge.startApplePayPayment(paymentConfiguration, (event) {
if (_callbackFired) return; // ignore any subsequent callback calls
final transactionDetails = event["data"] as Map<Object?, Object?>?;
if (event["status"] == "success") {
_callbackFired = true;
// Cast Map<Object?, Object?> to Map<String, dynamic>
final data = Map<String, dynamic>.from(event["data"] as Map);
paytabsTransactionResponseModel = PaytabsTransactionResponseModel.fromJson(data);
@ -110,11 +119,13 @@ class PayTabsViewModel extends ChangeNotifier {
onError!(errorMessage);
}
} else if (event["status"] == "error") {
_callbackFired = true;
final errorMessage = event["message"] ?? "An error occurred";
debugPrint("Error occurred in transaction: $errorMessage");
debugPrint("Full error event: $event");
onError!(errorMessage);
} else if (event["status"] == "event") {
// Intermediate SDK events do NOT mark _callbackFired, just log
final eventMessage = event["message"] ?? "Event occurred";
debugPrint("Event occurred: $eventMessage");
}
@ -122,9 +133,14 @@ class PayTabsViewModel extends ChangeNotifier {
}
startCardPayment({Function(PaytabsTransactionResponseModel)? onSuccess, Function(String)? onError}) {
bool _callbackFired = false; // prevent duplicate callback invocations
FlutterPaytabsBridge.startCardPayment(paymentConfiguration, (event) {
if (_callbackFired) return; // ignore any subsequent callback calls
final transactionDetails = event["data"] as Map<Object?, Object?>?;
if (event["status"] == "success") {
_callbackFired = true;
// Cast Map<Object?, Object?> to Map<String, dynamic>
final data = Map<String, dynamic>.from(event["data"] as Map);
paytabsTransactionResponseModel = PaytabsTransactionResponseModel.fromJson(data);
@ -140,11 +156,13 @@ class PayTabsViewModel extends ChangeNotifier {
onError!(errorMessage);
}
} else if (event["status"] == "error") {
_callbackFired = true;
final errorMessage = event["message"] ?? "An error occurred";
debugPrint("Error occurred in transaction: $errorMessage");
debugPrint("Full error event: $event");
onError!(errorMessage);
} else if (event["status"] == "event") {
// Intermediate SDK events do NOT mark _callbackFired, just log
final eventMessage = event["message"] ?? "Event occurred";
debugPrint("Event occurred: $eventMessage");
}

@ -323,7 +323,7 @@ class PrescriptionsViewModel extends ChangeNotifier {
isGmsAvailable: getIt.get<AppState>().isGMSAvailable,
),
direction: AxisDirection.down),
);
) ?? false;
print("Location Selected: $result");
if (result) {
LocationViewModel locationViewModel = getIt.get<LocationViewModel>();

@ -13,13 +13,13 @@ class TodoSectionViewModel extends ChangeNotifier {
String? notificationsCount = "0";
initializeTodoSectionViewModel() async {
// if (isAncillaryOrdersNeedReloading) {
patientAncillaryOrdersList.clear();
isAncillaryOrdersLoading = true;
if (isAncillaryOrdersNeedReloading) {
patientAncillaryOrdersList.clear();
isAncillaryOrdersLoading = true;
isAncillaryDetailsProceduresLoading = true;
notificationsCount = "0";
getPatientOnlineAncillaryOrderList();
// }
}
getPatientDashboard();
}
@ -67,9 +67,9 @@ class TodoSectionViewModel extends ChangeNotifier {
}
Future<void> getPatientOnlineAncillaryOrderList({Function(dynamic)? onSuccess, Function(String)? onError}) async {
// if (!isAncillaryOrdersNeedReloading) {
// return;
// }
if (!isAncillaryOrdersNeedReloading) {
return;
}
patientAncillaryOrdersList.clear();
isAncillaryOrdersLoading = true;

@ -231,6 +231,17 @@ abstract class LocaleKeys {
static const companyName = 'companyName';
static const receiptOn = 'receiptOn';
static const expiryDate = 'expiryDate';
static const myCards = 'myCards';
static const savedCards = 'savedCards';
static const addNewCard = 'addNewCard';
static const noSavedCards = 'noSavedCards';
static const cardEndingWith = 'cardEndingWith';
static const expiry = 'expiry';
static const defaultCard = 'defaultCard';
static const deleteCard = 'deleteCard';
static const deleteCardConfirmation = 'deleteCardConfirmation';
static const cardDeletedSuccessfully = 'cardDeletedSuccessfully';
static const addCardComingSoon = 'addCardComingSoon';
static const expiryPoints = 'expiryPoints';
static const expiryOn = 'expiryOn';
static const procedureName = 'procedureName';
@ -1877,6 +1888,8 @@ abstract class LocaleKeys {
static const transactions = 'transactions';
static const pharmacy = 'pharmacy';
static const visitsOrders = 'visitsOrders';
static const earned = 'earned';
static const getParkingQR = 'getParkingQR';
static const cannotOpenThisFile = 'cannotOpenThisFile';
static const refundDetails = 'refundDetails';
static const requestNo = 'requestNo';
@ -1885,5 +1898,4 @@ abstract class LocaleKeys {
static const amountOnly = 'amountOnly';
static const invoiceHistory = 'invoiceHistory';
static const refund = 'refund';
}

@ -96,7 +96,7 @@ Future<void> callAppStateInitializations() async {
PlatformDispatcher.instance.onError = (error, stack) {
if (!kDebugMode) {
FirebaseCrashlytics.instance.recordError(error, stack,
fatal: true,
fatal: false,
printDetails: true,
reason:
"${appState.isAuthenticated ? "Authenticated User ID: ${appState.getAuthenticatedUser()!.patientId} - ${appState.getAuthenticatedUser()!.mobileNumber}" : "Unauthenticated User"} - Uncaught asynchronous error");

@ -874,6 +874,35 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
}
})
: SizedBox.shrink(),
// (widget.patientAppointmentHistoryResponseModel.isParkingAvailable ?? false)
// ? MedicalFileCard(
// label: LocaleKeys.getParkingQR.tr(context: context),
// textColor: AppColors.blackColor,
// backgroundColor: AppColors.whiteColor,
// svgIcon: AppAssets.appointment_parking_icon,
// isLargeText: true,
// iconSize: 36.w,
// ).onPress(() {
// LoaderBottomSheet.showLoader(loadingText: LocaleKeys.loadingText.tr(context: context));
// myAppointmentsViewModel.getParkingQR(
// widget.patientAppointmentHistoryResponseModel.appointmentNo,
// widget.patientAppointmentHistoryResponseModel.projectID,
// onSuccess: (val) {
// LoaderBottomSheet.hideLoader();
// },
// onError: (err) {
// LoaderBottomSheet.hideLoader();
// showCommonBottomSheetWithoutHeight(
// context,
// child: Utils.getErrorWidget(loadingText: err),
// callBackFunc: () {},
// isFullScreen: false,
// isCloseButtonVisible: true,
// );
// },
// );
// })
// : SizedBox.shrink(),
],
);
}),

@ -795,6 +795,7 @@ 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) {
if (appState.isPaytabsEnabled) {
selectedPaymentMethod = "VISA";
LoaderBottomSheet.hideLoader();
paytabsViewModel.setPaymentConfiguration(
"Appointment Payment",

@ -639,7 +639,9 @@ class _AppointmentCardState extends State<AppointmentCard> {
),
);
} else {
if (!AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)) {
if (!AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel) &&
widget.patientAppointmentHistoryResponseModel.projectID != null &&
widget.patientAppointmentHistoryResponseModel.clinicID != null) {
widget.bookAppointmentsViewModel.getAppointmentNearestGate(
projectID: widget.patientAppointmentHistoryResponseModel.projectID, clinicID: widget.patientAppointmentHistoryResponseModel.clinicID);
}

@ -98,21 +98,24 @@ class AppointmentDoctorCard extends StatelessWidget {
children: [
Row(
children: [
patientAppointmentHistoryResponseModel.doctorNameObj!.toText16(
isBold: true,
isEnglishOnly: !Utils.isArabicText(patientAppointmentHistoryResponseModel.doctorNameObj ?? ""),
textOverflow: TextOverflow.ellipsis,
Expanded(
child: patientAppointmentHistoryResponseModel.doctorNameObj!.toText16(
isBold: true,
isEnglishOnly: !Utils.isArabicText(patientAppointmentHistoryResponseModel.doctorNameObj ?? ""),
textOverflow: TextOverflow.ellipsis,
maxlines: 2
),
),
SizedBox(width: 12.w),
(patientAppointmentHistoryResponseModel.doctorNationalityFlagURL != null &&
patientAppointmentHistoryResponseModel.doctorNationalityFlagURL!.isNotEmpty)
? Image.network(
patientAppointmentHistoryResponseModel.doctorNationalityFlagURL ?? "https://hmgwebservices.com/Images/flag/SAU.png",
width: 20.h,
height: 15.h,
fit: BoxFit.cover,
)
: SizedBox.shrink(),
// (patientAppointmentHistoryResponseModel.doctorNationalityFlagURL != null &&
// patientAppointmentHistoryResponseModel.doctorNationalityFlagURL!.isNotEmpty)
// ? Image.network(
// patientAppointmentHistoryResponseModel.doctorNationalityFlagURL ?? "https://hmgwebservices.com/Images/flag/SAU.png",
// width: 20.h,
// height: 15.h,
// fit: BoxFit.cover,
// )
// : SizedBox.shrink(),
],
),
SizedBox(height: 8.h),
@ -213,7 +216,7 @@ class AppointmentDoctorCard extends StatelessWidget {
AppointmentType.isArrived(patientAppointmentHistoryResponseModel),
),
),
if (timerWidget != null) timerWidget ?? SizedBox()
if (!AppointmentType.isArrived(patientAppointmentHistoryResponseModel) && timerWidget != null) timerWidget ?? SizedBox()
],
),
),

@ -133,7 +133,7 @@ class _DoctorResponsePageState extends State<DoctorResponsePage> {
).onPress(() {
showCommonBottomSheetWithoutHeight(
context,
title: askDoctorVM.doctorResponsesList[index].requestTypeDescription!,
title: askDoctorVM.doctorResponsesList[index].requestTypeDescription ?? "",
child: DoctorResponseTransactions(doctorResponseModel: askDoctorVM.doctorResponsesList[index],),
callBackFunc: () {},
isFullScreen: false,

@ -52,7 +52,7 @@ class _RegisterNew extends State<RegisterNew> {
// Clear errors when leaving register screen
// Use post frame callback to avoid calling notifyListeners during dispose
WidgetsBinding.instance.addPostFrameCallback((_) {
final authVm = context.read<AuthenticationViewModel>();
final authVm = getIt.get<AuthenticationViewModel>();
authVm.clearNationalIdError();
authVm.clearDobError();
authVm.clearPhoneNumberError();

@ -632,6 +632,7 @@ 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) {
if (appState.isPaytabsEnabled) {
selectedPaymentMethod = "VISA";
LoaderBottomSheet.hideLoader();
paytabsViewModel.setPaymentConfiguration("Advance Payment", num.parse((immediateLiveCareViewModel.liveCareImmediateAppointmentFeesList.total ?? "0.0")));
paytabsViewModel.startApplePayPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async {

@ -197,7 +197,7 @@ class _ImmediateLiveCarePendingRequestPageState extends State<ImmediateLiveCareP
CustomButton(
text: LocaleKeys.callLiveCareSupport.tr(),
onPressed: () async {
launchUrl(Uri.parse("tel://" + "011 525 9553"));
launchUrl(Uri.parse("tel://" + "966 11 525 9553"));
},
backgroundColor: AppColors.lightRedButtonColor,
borderColor: AppColors.lightRedButtonColor,

@ -680,6 +680,7 @@ 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) {
if (appState.isPaytabsEnabled) {
selectedPaymentMethod = "VISA";
LoaderBottomSheet.hideLoader();
paytabsViewModel.setPaymentConfiguration(
"Appointment Payment",

@ -49,6 +49,7 @@ class ClinicCard extends StatelessWidget {
width: 24.w,
height: 24.h,
fit: BoxFit.contain,
errorAsset: "assets/images/clinicIcons/1.svg"
// Note: Add error handling in the Utils.buildSvgWithAssets if possible
// or use a try-catch wrapper
),

@ -139,6 +139,7 @@ class _ErOnlineCheckinPaymentPageState extends State<ErOnlineCheckinPaymentPage>
paymentReference: transactionData.transactionReference!,
appointmentNo: "0",
onSuccess: (val) {
emergencyServicesViewModel.setIsAdvanceERBalanceNeedToBeLoaded(true);
LoaderBottomSheet.hideLoader();
if (emergencyServicesViewModel.isERBookAppointment) {
showCommonBottomSheetWithoutHeight(
@ -228,6 +229,7 @@ class _ErOnlineCheckinPaymentPageState extends State<ErOnlineCheckinPaymentPage>
paymentReference: transactionData.transactionReference!,
appointmentNo: "0",
onSuccess: (val) {
emergencyServicesViewModel.setIsAdvanceERBalanceNeedToBeLoaded(true);
LoaderBottomSheet.hideLoader();
if (emergencyServicesViewModel.isERBookAppointment) {
showCommonBottomSheetWithoutHeight(
@ -492,6 +494,7 @@ 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) {
if (appState.isPaytabsEnabled) {
selectedPaymentMethod = "VISA";
LoaderBottomSheet.hideLoader();
paytabsViewModel.setPaymentConfiguration(
"ER Online Check-In Payment",
@ -509,6 +512,7 @@ class _ErOnlineCheckinPaymentPageState extends State<ErOnlineCheckinPaymentPage>
paymentReference: transactionData.transactionReference!,
appointmentNo: "0",
onSuccess: (val) {
emergencyServicesViewModel.setIsAdvanceERBalanceNeedToBeLoaded(true);
LoaderBottomSheet.hideLoader();
if (emergencyServicesViewModel.isERBookAppointment) {
showCommonBottomSheetWithoutHeight(
@ -589,6 +593,7 @@ class _ErOnlineCheckinPaymentPageState extends State<ErOnlineCheckinPaymentPage>
paymentReference: payfortViewModel.payfortCheckPaymentStatusResponseModel!.fortId!,
appointmentNo: "0",
onSuccess: (val) {
emergencyServicesViewModel.setIsAdvanceERBalanceNeedToBeLoaded(true);
LoaderBottomSheet.hideLoader();
if (emergencyServicesViewModel.isERBookAppointment) {
showCommonBottomSheetWithoutHeight(

@ -136,23 +136,23 @@ class _HabibWalletState extends State<HabibWalletPage> {
},
),
),
SizedBox(width: 8.w),
Flexible(
child: CustomButton(
height: 40.h,
icon: AppAssets.refundIcon,
iconSize: 24.w,
backgroundColor: AppColors.successColor,
textColor: Colors.white,
text: LocaleKeys.withdraw.tr(context: context),
borderWidth: 0.w,
fontWeight: FontWeight.w600,
borderColor: Colors.transparent,
padding: EdgeInsets.fromLTRB(4, 0, 12, 0),
fontSize: 14.f,
onPressed: () => Navigator.of(context).push(CustomPageRoute(page: WithdrawRequestCreatePage())),
),
),
// SizedBox(width: 8.w),
// Flexible(
// child: CustomButton(
// height: 40.h,
// icon: AppAssets.refundIcon,
// iconSize: 24.w,
// backgroundColor: AppColors.successColor,
// textColor: Colors.white,
// text: LocaleKeys.withdraw.tr(context: context),
// borderWidth: 0.w,
// fontWeight: FontWeight.w600,
// borderColor: Colors.transparent,
// padding: EdgeInsets.fromLTRB(4, 0, 12, 0),
// fontSize: 14.f,
// onPressed: () => Navigator.of(context).push(CustomPageRoute(page: WithdrawRequestCreatePage())),
// ),
// ),
],
),
SizedBox(height: 24.h),

@ -127,6 +127,7 @@ class _WalletPaymentConfirmPageState extends State<WalletPaymentConfirmPage> {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(getIt.get<NavigationService>().navigatorKey.currentContext!, child: Utils.getSuccessWidget(loadingText: "Payment Successful!"),
callBackFunc: () {
habibWalletVM.setIsWalletAmountToBeLoaded(true);
habibWalletVM.initHabibWalletProvider();
habibWalletVM.getPatientBalanceAmount();
Navigator.of(getIt.get<NavigationService>().navigatorKey.currentContext!).pop();
@ -228,6 +229,7 @@ class _WalletPaymentConfirmPageState extends State<WalletPaymentConfirmPage> {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(getIt.get<NavigationService>().navigatorKey.currentContext!, child: Utils.getSuccessWidget(loadingText: "Payment Successful!"),
callBackFunc: () {
habibWalletVM.setIsWalletAmountToBeLoaded(true);
habibWalletVM.initHabibWalletProvider();
habibWalletVM.getPatientBalanceAmount();
Navigator.of(getIt.get<NavigationService>().navigatorKey.currentContext!).pop();
@ -389,6 +391,7 @@ 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) {
if(appState.isPaytabsEnabled) {
selectedPaymentMethod = "VISA";
LoaderBottomSheet.hideLoader();
paytabsViewModel.setPaymentConfiguration("Advance Payment", habibWalletVM.walletRechargeAmount);
paytabsViewModel.startApplePayPayment(onSuccess: (PaytabsTransactionResponseModel transactionData) async {
@ -410,8 +413,9 @@ class _WalletPaymentConfirmPageState extends State<WalletPaymentConfirmPage> {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(getIt.get<NavigationService>().navigatorKey.currentContext!, child: Utils.getSuccessWidget(loadingText: "Payment Successful!"),
callBackFunc: () {
habibWalletVM.initHabibWalletProvider();
habibWalletVM.getPatientBalanceAmount();
habibWalletVM.setIsWalletAmountToBeLoaded(true);
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);
@ -451,7 +455,7 @@ class _WalletPaymentConfirmPageState extends State<WalletPaymentConfirmPage> {
customerName: "${appState.getAuthenticatedUser()!.firstName} ${appState.getAuthenticatedUser()!.lastName}",
// customerEmail: projectViewModel.authenticatedUserObject.user.emailAddress,
customerEmail: "CustID_${appState.getAuthenticatedUser()!.patientId.toString()}@HMG.com",
orderDescription: "Appointment Payment",
orderDescription: "Advance Payment",
orderAmount: double.parse(habibWalletVM.walletRechargeAmount.toString()),
merchantReference: transID,
merchantIdentifier: payfortViewModel.payfortProjectDetailsRespModel!.merchantIdentifier,
@ -502,6 +506,7 @@ class _WalletPaymentConfirmPageState extends State<WalletPaymentConfirmPage> {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(getIt.get<NavigationService>().navigatorKey.currentContext!, child: Utils.getSuccessWidget(loadingText: "Payment Successful!"),
callBackFunc: () {
habibWalletVM.setIsWalletAmountToBeLoaded(true);
habibWalletVM.initHabibWalletProvider();
habibWalletVM.getPatientBalanceAmount();
Navigator.of(getIt.get<NavigationService>().navigatorKey.currentContext!).pop();

@ -304,7 +304,7 @@ class LakumWalletDetails extends StatelessWidget {
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;
bool isGained = transaction.subTransactionType == 1 || transaction.operationType?.toLowerCase() == 'cr' || (transaction.points ?? 0) > 0;
Color transactionColor = isGained ? AppColors.habibPharmacyColor : AppColors.primaryRedColor;
String pointsText = "${isGained ? '+' : '-'}${transaction.points?.abs() ?? 0}";
@ -345,7 +345,7 @@ class LakumWalletDetails extends StatelessWidget {
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
(transaction.subTransactionTypeDescription ?? transaction.operationType ?? "Transaction").toText14(
(isGained ? LocaleKeys.earned.tr() : LocaleKeys.consumed.tr()).toText14(
isBold: true,
maxlines: 2,
),

@ -59,6 +59,7 @@ import 'package:hmg_patient_app_new/presentation/todo_section/todo_page.dart';
import 'package:hmg_patient_app_new/presentation/todo_section/widgets/ancillary_orders_list.dart';
import 'package:hmg_patient_app_new/routes/app_routes.dart';
import 'package:hmg_patient_app_new/services/cache_service.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:hmg_patient_app_new/theme/colors.dart';
import 'package:hmg_patient_app_new/widgets/buttons/custom_button.dart';
import 'package:hmg_patient_app_new/widgets/chip/app_custom_chip_widget.dart';
@ -156,9 +157,13 @@ class _LandingPageState extends State<LandingPage> {
appointmentRatingViewModel.getLastRatingAppointment(
onSuccess: (response) {
if (appointmentRatingViewModel.appointmentRatedList.isNotEmpty) {
final lastAppointment = appointmentRatingViewModel.appointmentRatedList.last;
final appointmentNo = lastAppointment.appointmentNo;
final projectID = lastAppointment.projectID;
if (appointmentNo == null || projectID == null) return;
appointmentRatingViewModel.getAppointmentDetails(
appointmentRatingViewModel.appointmentRatedList.last.appointmentNo!,
appointmentRatingViewModel.appointmentRatedList.last.projectID!,
appointmentNo,
projectID,
onSuccess: ((response) {
appointmentRatingViewModel.setClinicOrDoctor(false);
appointmentRatingViewModel.setTitle(LocaleKeys.rateDoctor.tr(context: context));
@ -208,11 +213,31 @@ class _LandingPageState extends State<LandingPage> {
habibWalletVM.getLakumAccountInformation();
// Refresh Ancillary Orders
todoSectionViewModel.setIsAncillaryOrdersNeedReloading(true);
todoSectionViewModel.initializeTodoSectionViewModel();
// Refresh Immediate LiveCare Data
immediateLiveCareViewModel.initImmediateLiveCare();
immediateLiveCareViewModel.getPatientLiveCareHistory();
// appointmentRatingViewModel.getLastRatingAppointment(
// onSuccess: (response) {
// if (appointmentRatingViewModel.appointmentRatedList.isNotEmpty) {
// appointmentRatingViewModel.getAppointmentDetails(
// appointmentRatingViewModel.appointmentRatedList.last.appointmentNo!,
// appointmentRatingViewModel.appointmentRatedList.last.projectID!,
// onSuccess: ((response) {
// appointmentRatingViewModel.setClinicOrDoctor(false);
// appointmentRatingViewModel.setTitle(LocaleKeys.rateDoctor.tr(context: context));
// appointmentRatingViewModel.setSubTitle(LocaleKeys.howWasYourLastVisitWithDoctor.tr(context: context));
// openLastRating();
// appState.setRatedVisible(true);
// }),
// );
// }
// },
// );
}
},
child: SingleChildScrollView(
@ -1218,7 +1243,7 @@ class _LandingPageState extends State<LandingPage> {
},
backgroundColor: AppColors.warningColorYellow.withValues(alpha: 0.20),
textColor: AppColors.alertColor,
borderColor: AppColors.infoBannerBgColor,
borderColor: AppColors.warningColorYellow.withValues(alpha: 0.01),
fontSize: 12.f,
fontWeight: FontWeight.w600,
borderRadius: 12.r,
@ -1377,7 +1402,7 @@ class _LandingPageState extends State<LandingPage> {
void showQuickLogin(BuildContext context) {
showCommonBottomSheetWithoutHeight(
context,
getIt<NavigationService>().navigatorKey.currentContext!,
// title: "",
isCloseButtonVisible: false,
child: StatefulBuilder(

@ -152,7 +152,7 @@ class InsuranceApprovalDetailsPage extends StatelessWidget {
child: insuranceApprovalResponseModel.apporvalDetails != null ? Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
LocaleKeys.approvalDetails.toText16(isBold: true),
LocaleKeys.approvalDetails.tr().toText16(isBold: true),
ListView.separated(
padding: EdgeInsets.only(top: 16.h),
shrinkWrap: true,

@ -211,28 +211,28 @@ class InvoiceListCard extends StatelessWidget {
iconSize: 14.h,
),
),
if (getInvoicesListResponseModel.isAllowRefund == true) ...[
SizedBox(width: 8.w),
Expanded(
child: CustomButton(
text: LocaleKeys.requestRefund.tr(context: context),
onPressed: () {
_handleRefundInvoiceTap(context);
},
icon: AppAssets.refundIcon,
iconColor: AppColors.caloriesCalculatorColor,
backgroundColor: AppColors.blueColor.withValues(alpha: 0.14),
borderColor: AppColors.primaryRedColor.withValues(alpha: 0.01),
textColor: AppColors.caloriesCalculatorColor,
fontSize: 14.f,
fontWeight: FontWeight.w600,
borderRadius: 12.r,
padding: EdgeInsets.symmetric(horizontal: 10.w),
height: 40.h,
iconSize: 14.h,
),
),
]
// if (getInvoicesListResponseModel.isAllowRefund == true) ...[
// SizedBox(width: 8.w),
// Expanded(
// child: CustomButton(
// text: LocaleKeys.requestRefund.tr(context: context),
// onPressed: () {
// _handleRefundInvoiceTap(context);
// },
// icon: AppAssets.refundIcon,
// iconColor: AppColors.caloriesCalculatorColor,
// backgroundColor: AppColors.blueColor.withValues(alpha: 0.14),
// borderColor: AppColors.primaryRedColor.withValues(alpha: 0.01),
// textColor: AppColors.caloriesCalculatorColor,
// fontSize: 14.f,
// fontWeight: FontWeight.w600,
// borderRadius: 12.r,
// padding: EdgeInsets.symmetric(horizontal: 10.w),
// height: 40.h,
// iconSize: 14.h,
// ),
// ),
// ]
],
),
],

@ -1,5 +1,6 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:get_it/get_it.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/extensions/int_extensions.dart';
@ -7,6 +8,7 @@ import 'package:hmg_patient_app_new/extensions/string_extensions.dart';
import 'package:hmg_patient_app_new/extensions/widget_extensions.dart';
import 'package:hmg_patient_app_new/features/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';
@ -70,7 +72,7 @@ class _PreferredLanguageWidgetState extends State<PreferredLanguageWidget> {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(context, title: LocaleKeys.success.tr(context: context), child: Utils.getSuccessWidget(loadingText: LocaleKeys.success.tr()),
callBackFunc: () async {
Navigator.of(context).pop();
Navigator.of(GetIt.instance<NavigationService>().navigatorKey.currentContext!).pop();
profileSettingsViewModel.getProfileSettings();
}, isFullScreen: false, isAutoDismiss: true);
},

@ -1,4 +1,3 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart';

@ -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';
@ -130,7 +132,7 @@ class _UpdateEmailDialogState extends State<UpdateEmailDialog> {
profileSettingsViewModel!.clearEmailError();
showCommonBottomSheetWithoutHeight(context, title: LocaleKeys.success.tr(context: context), child: Utils.getSuccessWidget(loadingText: LocaleKeys.success.tr()),
callBackFunc: () async {
Navigator.of(context).pop();
Navigator.of(getIt<NavigationService>().navigatorKey.currentContext!).pop();
profileSettingsViewModel!.getProfileSettings();
}, isFullScreen: false, isAutoDismiss: true);
},

@ -130,9 +130,11 @@ class _UpdateEmergencyContactDialogState extends State<UpdateEmergencyContactDia
onSuccess: (response) {
LoaderBottomSheet.hideLoader();
profileSettingsViewModel!.clearEmergencyContactError();
showCommonBottomSheetWithoutHeight(context, title: LocaleKeys.success.tr(context: context), child: Utils.getSuccessWidget(loadingText: LocaleKeys.success.tr()),
final navContext = getIt.get<NavigationService>().navigatorKey.currentContext;
if (navContext == null) return;
showCommonBottomSheetWithoutHeight(navContext, title: LocaleKeys.success.tr(context: navContext), child: Utils.getSuccessWidget(loadingText: LocaleKeys.success.tr()),
callBackFunc: () async {
Navigator.of(getIt.get<NavigationService>().navigatorKey.currentContext!).pop();
Navigator.of(navContext).pop();
profileSettingsViewModel!.getProfileSettings();
}, isFullScreen: false, isAutoDismiss: true);
},

@ -80,7 +80,8 @@ class _TriagePageState extends State<TriagePage> {
// Case 2: Should stop flag is true OR
// Case 3: Probability >= 70% OR
// Case 4: 30 or more questions answered
if (viewModel.shouldStopTriage || highestProbability >= 70.0 || viewModel.triageQuestionCount >= 30) {
// if (viewModel.shouldStopTriage || highestProbability >= 70.0 || viewModel.triageQuestionCount >= 30) {
if (highestProbability >= 70.0 || viewModel.triageQuestionCount >= 30 || viewModel.shouldStopTriage) {
// Navigate to results/possible conditions screen
context.navigateWithName(AppRoutes.possibleConditionsPage);
return;

@ -632,6 +632,7 @@ class _AncillaryOrderPaymentPageState extends State<AncillaryOrderPaymentPage> {
}
// Only proceed with Apple Pay if insert was successful
if(appState.isPaytabsEnabled) {
selectedPaymentMethod = "VISA";
LoaderBottomSheet.hideLoader();
paytabsViewModel.setPaymentConfiguration(
"Ancillary Orders Payment",

@ -66,6 +66,7 @@ class _AncillaryOrderDetailsListState extends State<AncillaryOrderDetailsList> {
}
void _autoSelectEligibleProcedures() {
if (!mounted) return;
selectedProcedures.clear();
if (todoSectionViewModel.patientAncillaryOrderProceduresList.isNotEmpty) {
final procedures = todoSectionViewModel.patientAncillaryOrderProceduresList[0].ancillaryOrderProcDetailsList;
@ -77,7 +78,7 @@ class _AncillaryOrderDetailsListState extends State<AncillaryOrderDetailsList> {
}
}
}
setState(() {});
if (mounted) setState(() {});
}
bool _isProcedureDisabled(AncillaryOrderProcDetail procedure) {

@ -1,5 +1,7 @@
import 'dart:async';
import 'package:clarity_flutter/clarity_flutter.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_callkit_incoming/entities/call_event.dart';
import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart';
@ -55,7 +57,13 @@ class _SplashScreenState extends State<SplashPage> {
await notificationService.initialize(onNotificationClick: (payload) {
// Handle notification click here
});
ZoomService().initializeZoomSDK();
// if (!kDebugMode) {
// _initializeClarity();
// }
if (isAppOpenedFromCall) {
navigateToTeleConsult();
} else {
@ -76,6 +84,15 @@ class _SplashScreenState extends State<SplashPage> {
// zoom.initSdk(initConfig);
}
void _initializeClarity() {
final config = ClarityConfig(
projectId: "x0qgorlez4", // You can find it on the Settings page of Clarity dashboard.
logLevel: LogLevel.Verbose, // Optional: Set the log level (Verbose, Debug, Info, Warning, Error, None)
);
Clarity.initialize(context, config);
}
navigateToTeleConsult() async {
String roomID = await Utils.getStringFromPrefs(CacheConst.zoomRoomID);
String callTypeID = await Utils.getStringFromPrefs(CacheConst.callTypeID);

@ -95,7 +95,8 @@ class ImageOptions {
'zip',
],
);
List<File> files = result!.paths.map((path) => File(path!)).toList();
if (result == null) return;
List<File> files = result.paths.map((path) => File(path!)).toList();
image(result.files.first.path.toString(), files.first);
},
),

@ -105,6 +105,7 @@ dependencies:
in_app_review: ^2.0.11
flutter_paytabs_bridge: ^2.7.13
clarity_flutter: 1.9.0
dev_dependencies:
flutter_test:

Loading…
Cancel
Save