Merge pull request 'dev_sultan' (#260) from dev_sultan into master

Reviewed-on: #260
haroon_dev
khansultan1 21 hours ago
commit b51cd52fa4

@ -1245,7 +1245,7 @@
"healthCalculatorsServices": "حاسبات\nالصحة", "healthCalculatorsServices": "حاسبات\nالصحة",
"healthConvertersServices": "محولات\nالصحة", "healthConvertersServices": "محولات\nالصحة",
"smartWatchesServices": "الساعات\nالذكية", "smartWatchesServices": "الساعات\nالذكية",
"exploreServices": "قائمة الخدمات او الخدمات", "exploreServices": "تصفح الخدمات",
"medicalAndCareServices": "خدمات الرعاية الطبية", "medicalAndCareServices": "خدمات الرعاية الطبية",
"hmgServices": "خدمات مجموعة الحبيب الطبية", "hmgServices": "خدمات مجموعة الحبيب الطبية",
"personalServices": "الخدمات الشخصية", "personalServices": "الخدمات الشخصية",
@ -1479,7 +1479,7 @@
"covered": "مغطى", "covered": "مغطى",
"vatPercent": "ضريبة القيمة المضافة (15%)", "vatPercent": "ضريبة القيمة المضافة (15%)",
"proceedToPayment": "المتابعة للدفع", "proceedToPayment": "المتابعة للدفع",
"supportedSmartWatches": "الساعات الذكية المدعومة", "supportedSmartWatches": "كيف تعمل؟",
"pleaseMakeSureSamsungWatchConnected": "يرجى التأكد من أن ساعة Samsung الخاصة بك متصلة بهاتفك، ومتزامنة ومحدثة بشكل نشط.", "pleaseMakeSureSamsungWatchConnected": "يرجى التأكد من أن ساعة Samsung الخاصة بك متصلة بهاتفك، ومتزامنة ومحدثة بشكل نشط.",
"beforeSyncingDataFollowInstructions": "قبل مزامنة البيانات، يرجى التأكد من اتباع التعليمات بشكل صحيح.", "beforeSyncingDataFollowInstructions": "قبل مزامنة البيانات، يرجى التأكد من اتباع التعليمات بشكل صحيح.",
"viewWatchInstructions": "عرض تعليمات الساعة", "viewWatchInstructions": "عرض تعليمات الساعة",
@ -1776,7 +1776,6 @@
"locationServicesDisabled": "حالة الطقس غير متوفرة لتعطيل خدمات الموقع. يرجى تفعيل خدمات الموقع لتتمكن من رؤية معلومات الطقس.", "locationServicesDisabled": "حالة الطقس غير متوفرة لتعطيل خدمات الموقع. يرجى تفعيل خدمات الموقع لتتمكن من رؤية معلومات الطقس.",
"selectDateTimeKey": "اختيار", "selectDateTimeKey": "اختيار",
"medicalKey": "الطبية", "medicalKey": "الطبية",
"convertBloodcholesterolInfo":"حول قيمة كوليسترول الدم بين ملجم/ديسيلتر و مليمول/لتر",
"carbsAlert": "حساب نسب المغذيات (الكربوهيدرات ،البروتين والدهون) بالسعرات الحرارية والقرامات وفقا لنسب محددة مسبقا", "carbsAlert": "حساب نسب المغذيات (الكربوهيدرات ،البروتين والدهون) بالسعرات الحرارية والقرامات وفقا لنسب محددة مسبقا",
"bodyFatAlert": "يحسب بناءً على الحجم", "bodyFatAlert": "يحسب بناءً على الحجم",
"ibwAlert": "حساب الوزن المثالي بناءً على الطول، الوزن، وبنية الجسم", "ibwAlert": "حساب الوزن المثالي بناءً على الطول، الوزن، وبنية الجسم",
@ -1785,5 +1784,12 @@
"routePrescription": "طريقة الاستخدام", "routePrescription": "طريقة الاستخدام",
"noNearestER": "لا يوجد طوارئ قريب منك", "noNearestER": "لا يوجد طوارئ قريب منك",
"loginToUseServiceLiveCare": "تحتاج إلى تسجيل الدخول لاستخدام هذه الخدمة" "loginToUseServiceLiveCare": "تحتاج إلى تسجيل الدخول لاستخدام هذه الخدمة"
"convertBloodcholesterolInfo":"حول قيمة كوليسترول الدم بين ملجم/ديسيلتر و مليمول/لتر",
"mldl": "مليجرام لكل ديسيلتر",
"mmol": "ملي مول لكل لتر",
"triglycerideInfo": "تغيير وحدات القياس بين مليجرام لكل ديسيلتر و ملي مول لكل لتر",
"selectSmartWatch": "تعيين",
"selectBloodDonationCity": "اختر المدينة",
"selectBloodDonationGender": "اختر الجنس",
"selectBloodType": "اختر الفصيلة"
} }

@ -1764,9 +1764,6 @@
"selectCriteria": "Select Criteria", "selectCriteria": "Select Criteria",
"locationServicesDisabled": "Weather is unavailable because location services are disabled. Please enable location services to see the weather information.", "locationServicesDisabled": "Weather is unavailable because location services are disabled. Please enable location services to see the weather information.",
"selectDateTimeKey": "Select", "selectDateTimeKey": "Select",
"medicalKey": "Medical"
,
"convertBloodcholesterolInfo": "Convert blood cholesterol values between mg/dL and mmol/L. ",
"carbsAlert":"Calculate carbohydrate based protein and fat ration in calories and grams according to a pre set ratio.", "carbsAlert":"Calculate carbohydrate based protein and fat ration in calories and grams according to a pre set ratio.",
"bodyFatAlert": "Estimate the total body fat based on the size.", "bodyFatAlert": "Estimate the total body fat based on the size.",
"ibwAlert": "Calculates the ideal body weight based on height, weight and body size.", "ibwAlert": "Calculates the ideal body weight based on height, weight and body size.",
@ -1774,8 +1771,16 @@
"selectBloodGroup": "Select Blood Group", "selectBloodGroup": "Select Blood Group",
"routePrescription": "Route", "routePrescription": "Route",
"noNearestER": "No nearest Er Around you", "noNearestER": "No nearest Er Around you",
"loginToUseServiceLiveCare": "Log In to Use This Service" "loginToUseServiceLiveCare": "Log In to Use This Service",
"medicalKey": "Medical",
"convertBloodcholesterolInfo": "Convert blood cholesterol values between mg/dL and mmol/L. ",
"mldl": "مليجرام لكل ديسيلتر",
"mmol": "ملي مول لكل لتر",
"triglycerideInfo": "Convert triglyceride values between mg/dL and mmol/L.",
"selectSmartWatch": "Select",
"selectBloodDonationCity": "Select City",
"selectBloodDonationGender": "Select Gender",
"selectBloodType": "Select Blood Type"
} }

@ -172,6 +172,7 @@ class ApiClientImp implements ApiClient {
// body['VersionID'] = ApiConsts.appVersionID.toString(); // body['VersionID'] = ApiConsts.appVersionID.toString();
if (!isExternal) { if (!isExternal) {
body['VersionID'] = ApiConsts.appVersionID.toString(); body['VersionID'] = ApiConsts.appVersionID.toString();
if(!isRCService)
body['Channel'] = ApiConsts.appChannelId.toString(); body['Channel'] = ApiConsts.appChannelId.toString();
body['IPAdress'] = ApiConsts.appIpAddress; body['IPAdress'] = ApiConsts.appIpAddress;
body['generalid'] = ApiConsts.appGeneralId; body['generalid'] = ApiConsts.appGeneralId;

@ -189,6 +189,7 @@ class ApiConsts {
static final createEReferral = "Services/Patients.svc/REST/CreateEReferral"; static final createEReferral = "Services/Patients.svc/REST/CreateEReferral";
static final getEReferrals = "Services/Patients.svc/REST/GetEReferrals"; static final getEReferrals = "Services/Patients.svc/REST/GetEReferrals";
//WATER CONSUMPTION //WATER CONSUMPTION
static String h2oGetUserProgress = "Services/H2ORemainder.svc/REST/H2O_GetUserProgress"; static String h2oGetUserProgress = "Services/H2ORemainder.svc/REST/H2O_GetUserProgress";
static String h2oInsertUserActivity = "Services/H2ORemainder.svc/REST/H2O_InsertUserActivity"; static String h2oInsertUserActivity = "Services/H2ORemainder.svc/REST/H2O_InsertUserActivity";

@ -404,7 +404,7 @@ class AppAssets {
static const String lockIcon = '$pngBasePath/lock-icon.png'; static const String lockIcon = '$pngBasePath/lock-icon.png';
static const String dummyUser = '$pngBasePath/dummy_user.png'; static const String dummyUser = '$pngBasePath/dummy_user.png';
static const String comprehensiveCheckupEn = '$pngBasePath/cc_en.png'; static const String comprehensiveCheckupEn = '$pngBasePath/cc_en.png';
static const String comprehensiveCheckupAr = '$pngBasePath/cc_er.png'; static const String comprehensiveCheckupAr = '$pngBasePath/cc_ar.png';
static const String maleIcon = '$pngBasePath/male_icon.png'; static const String maleIcon = '$pngBasePath/male_icon.png';
static const String femaleIcon = '$pngBasePath/female_icon.png'; static const String femaleIcon = '$pngBasePath/female_icon.png';

@ -9,17 +9,27 @@ import 'package:firebase_messaging/firebase_messaging.dart';
import 'package:flutter/cupertino.dart'; import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_callkit_incoming/entities/android_params.dart'; import 'package:flutter_callkit_incoming/entities/android_params.dart';
import 'package:flutter_callkit_incoming/entities/call_event.dart';
import 'package:flutter_callkit_incoming/entities/call_kit_params.dart'; import 'package:flutter_callkit_incoming/entities/call_kit_params.dart';
import 'package:flutter_callkit_incoming/entities/ios_params.dart'; import 'package:flutter_callkit_incoming/entities/ios_params.dart';
import 'package:flutter_callkit_incoming/entities/notification_params.dart'; import 'package:flutter_callkit_incoming/entities/notification_params.dart';
import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart'; import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart';
import 'package:flutter_ios_voip_kit_karmm/call_state_type.dart'; import 'package:flutter_ios_voip_kit_karmm/call_state_type.dart';
import 'package:flutter_ios_voip_kit_karmm/flutter_ios_voip_kit.dart'; import 'package:flutter_ios_voip_kit_karmm/flutter_ios_voip_kit.dart';
import 'package:hmg_patient_app_new/core/api_consts.dart';
import 'package:hmg_patient_app_new/core/cache_consts.dart'; import 'package:hmg_patient_app_new/core/cache_consts.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart'; import 'package:hmg_patient_app_new/core/utils/utils.dart';
import 'package:hmg_patient_app_new/core/api/api_client.dart';
import 'package:hmg_patient_app_new/core/dependencies.dart';
import 'package:permission_handler/permission_handler.dart'; import 'package:permission_handler/permission_handler.dart';
import 'package:uuid/uuid.dart'; import 'package:uuid/uuid.dart';
// Global tracking variables for call state
bool _isCallInProgress = false;
String? _currentCallId;
Timer? _callTimeoutTimer;
String? _currentSessionId; // Store session ID for API call
// |--> Push Notification Background // |--> Push Notification Background
@pragma('vm:entry-point') @pragma('vm:entry-point')
Future<dynamic> backgroundMessageHandler(dynamic message) async { Future<dynamic> backgroundMessageHandler(dynamic message) async {
@ -39,34 +49,36 @@ callPage(String sessionID, String token) async {}
_incomingCall(Map<String, dynamic> data) async { _incomingCall(Map<String, dynamic> data) async {
print('the value of the _incomingCall remote message is $data'); print('the value of the _incomingCall remote message is $data');
// LandingPage.incomingCallData = IncomingCallData.fromJson(data); // Check if there's already a call in progress to prevent duplicates
// var dataItem = await AppSharedPreferences().getObject('call_data'); if (_isCallInProgress && _currentCallId != null) {
// if(dataItem != null ) return; // to stop repeated attempt to invoke the call print('⚠️ Call already in progress (ID: $_currentCallId), ignoring duplicate notification');
// if (LandingPage.isOpenCallPage == false) { return;
// LandingPage.isOpenCallPage = true; }
String roomID = data['session_id'] ?? ''; String roomID = data['session_id'] ?? '';
String callTypeID = data['AppointmentNo'] ?? ''; String callTypeID = data['AppointmentNo'] ?? '';
// GetIt.instance<CacheService>().saveString(key: CacheConst.zoomRoomID, value: roomID); // Generate unique call ID
// GetIt.instance<CacheService>().saveBool(key: CacheConst.isAppOpenedFromCall, value: true); var _currentUuid = Uuid().v4();
_currentCallId = _currentUuid;
_isCallInProgress = true;
_currentSessionId = roomID; // Store session ID for decline API call
print('📞 Starting new call with ID: $_currentCallId, Session: $_currentSessionId');
await Utils.saveStringFromPrefs(CacheConst.zoomRoomID, roomID); await Utils.saveStringFromPrefs(CacheConst.zoomRoomID, roomID);
await Utils.saveStringFromPrefs(CacheConst.callTypeID, callTypeID); await Utils.saveStringFromPrefs(CacheConst.callTypeID, callTypeID);
await Utils.saveBoolFromPrefs(CacheConst.isAppOpenedFromCall, true); await Utils.saveBoolFromPrefs(CacheConst.isAppOpenedFromCall, true);
WidgetsFlutterBinding.ensureInitialized(); WidgetsFlutterBinding.ensureInitialized();
int callKitType = callTypeID == '2' ? 0 : 1; int callKitType = callTypeID == '2' ? 0 : 1;
var _currentUuid = Uuid().v4();
CallKitParams callKitParams = CallKitParams( CallKitParams callKitParams = CallKitParams(
id: _currentUuid, id: _currentUuid,
nameCaller: "Dr Al Habib", nameCaller: "Dr Al Habib",
appName: 'Dr Al Habib', appName: 'Dr Al Habib',
avatar: 'https://play-lh.googleusercontent.com/FBNNpxb7m6eM6wtW7MV1Ffp6OXOGLI38q47zcvP29OCYA1yhYH5mZzl5itZi0TgOyZpG', avatar: 'https://play-lh.googleusercontent.com/FBNNpxb7m6eM6wtW7MV1Ffp6OXOGLI38q47zcvP29OCYA1yhYH5mZzl5itZi0TgOyZpG',
// handle: LandingPage.incomingCallData.name,
type: callKitType, type: callKitType,
textAccept: 'Accept', textAccept: 'Accept',
textDecline: 'Decline', textDecline: 'Decline',
@ -85,8 +97,8 @@ _incomingCall(Map<String, dynamic> data) async {
duration: 30000, duration: 30000,
extra: <String, dynamic>{ extra: <String, dynamic>{
'userId': '1a2b3c4d', 'userId': '1a2b3c4d',
'callTypeID': callTypeID, // Store actual callTypeID here 'callTypeID': callTypeID,
'roomID': roomID, // Store roomID here for backup 'roomID': roomID,
}, },
headers: <String, dynamic>{'apiKey': 'Abc@123!', 'platform': 'flutter'}, headers: <String, dynamic>{'apiKey': 'Abc@123!', 'platform': 'flutter'},
android: const AndroidParams( android: const AndroidParams(
@ -96,10 +108,10 @@ _incomingCall(Map<String, dynamic> data) async {
isShowLogo: false, isShowLogo: false,
logoUrl: "https://play-lh.googleusercontent.com/FBNNpxb7m6eM6wtW7MV1Ffp6OXOGLI38q47zcvP29OCYA1yhYH5mZzl5itZi0TgOyZpG", logoUrl: "https://play-lh.googleusercontent.com/FBNNpxb7m6eM6wtW7MV1Ffp6OXOGLI38q47zcvP29OCYA1yhYH5mZzl5itZi0TgOyZpG",
ringtonePath: 'system_ringtone_default', ringtonePath: 'system_ringtone_default',
backgroundColor: '#ED1C2B', backgroundColor: '#2E3039',
backgroundUrl: 'https://saudipedia.com/en/saudipediaen/uploads/images/2024/08/02/97358.jpg', backgroundUrl: 'https://saudipedia.com/en/saudipediaen/uploads/images/2024/08/02/97358.jpg',
actionColor: '#4CAF50', actionColor: '#4CAF50',
textColor: '#000000', textColor: '#FFFFFF',
incomingCallNotificationChannelName: "Incoming Call", incomingCallNotificationChannelName: "Incoming Call",
missedCallNotificationChannelName: "Missed Call", missedCallNotificationChannelName: "Missed Call",
isShowCallID: false), isShowCallID: false),
@ -120,12 +132,89 @@ _incomingCall(Map<String, dynamic> data) async {
ringtonePath: 'system_ringtone_default', ringtonePath: 'system_ringtone_default',
), ),
); );
FlutterCallkitIncoming.unsilenceEvents(); FlutterCallkitIncoming.unsilenceEvents();
await FlutterCallkitIncoming.showCallkitIncoming(callKitParams); await FlutterCallkitIncoming.showCallkitIncoming(callKitParams);
// Start 60-second timeout timer to auto-dismiss call
_callTimeoutTimer?.cancel(); // Cancel any existing timer
_callTimeoutTimer = Timer(Duration(seconds: 40), () async {
print('⏱️ Call timeout (60 seconds) - auto dismissing call notification');
await _endCallAndCleanup(_currentUuid);
});
await Future.delayed(Duration(milliseconds: 500)); await Future.delayed(Duration(milliseconds: 500));
} }
// Helper method to end call and cleanup
Future<void> _endCallAndCleanup(String callId, {bool isDeclined = false}) async {
try {
print('🧹 Cleaning up call: $callId');
// If call was declined, update session status via API
if (isDeclined && _currentSessionId != null && _currentSessionId!.isNotEmpty) {
await _updateSessionStatus(_currentSessionId!, 3, 'Patient');
}
// End the specific call
await FlutterCallkitIncoming.endCall(callId);
// Clear all calls to ensure clean state
await FlutterCallkitIncoming.endAllCalls();
// Reset tracking flags
_isCallInProgress = false;
_currentCallId = null;
_currentSessionId = null;
// Cancel timer
_callTimeoutTimer?.cancel();
_callTimeoutTimer = null;
// Clear cache
await Utils.saveBoolFromPrefs(CacheConst.isAppOpenedFromCall, false);
await Utils.saveStringFromPrefs(CacheConst.zoomRoomID, '');
await Utils.saveStringFromPrefs(CacheConst.callTypeID, '');
print('✅ Call and push notification cleanup completed successfully');
} catch (e) {
print('❌ Error during call cleanup: $e');
// Force reset flags even if there's an error
_isCallInProgress = false;
_currentCallId = null;
_currentSessionId = null;
_callTimeoutTimer?.cancel();
_callTimeoutTimer = null;
}
}
// Helper method to update session status via API
Future<void> _updateSessionStatus(String sessionId, int sessionStatus, String sessionEndedBy) async {
try {
final apiClient = getIt.get<ApiClient>();
Map<String, dynamic> requestBody = {
"Open_SessionID": sessionId,
"SessionStatus": sessionStatus,
"SessionEndedBy": sessionEndedBy
};
await apiClient.post(
CHANGE_PATIENT_ER_SESSION,
body: requestBody,
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
print('✅ Session status updated successfully: $response');
},
onFailure: (error, statusCode, {messageStatus, failureType}) {
print('❌ Failed to update session status: $error');
},
);
} catch (e) {
print('❌ Exception updating session status: $e');
}
}
Future<void> openCallPage(BuildContext context) async { Future<void> openCallPage(BuildContext context) async {
try { try {
// if (incomingCallData!.background == "0") { // if (incomingCallData!.background == "0") {
@ -291,6 +380,10 @@ class PushNotificationHandler {
try { try {
print('🎈 example: onDidRejectIncomingCall $uuid - $callerId'); print('🎈 example: onDidRejectIncomingCall $uuid - $callerId');
timeOutTimer.cancel(); timeOutTimer.cancel();
// Cleanup on reject with API call
if (_currentCallId != null) {
await _endCallAndCleanup(_currentCallId!, isDeclined: true);
}
} catch (err) {} } catch (err) {}
}; };
@ -301,6 +394,12 @@ class PushNotificationHandler {
print('🎈 example: onDidAcceptIncomingCall $uuid - $callerId'); print('🎈 example: onDidAcceptIncomingCall $uuid - $callerId');
await voIPKit.acceptIncomingCall(callerState: CallStateType.calling); await voIPKit.acceptIncomingCall(callerState: CallStateType.calling);
await voIPKit.callConnected(); await voIPKit.callConnected();
// Cleanup on accept - cancel timer and reset flags
_callTimeoutTimer?.cancel();
_callTimeoutTimer = null;
_isCallInProgress = false;
_currentCallId = null;
await Future.delayed(Duration(seconds: 2)).then((val) async { await Future.delayed(Duration(seconds: 2)).then((val) async {
await voIPKit.endCall(); await voIPKit.endCall();
}); });

@ -306,6 +306,23 @@ class PrescriptionsViewModel extends ChangeNotifier {
} else if (apiResponse.messageStatus == 1) { } else if (apiResponse.messageStatus == 1) {
isPrescriptionsDeliveryOrdersLoading = false; isPrescriptionsDeliveryOrdersLoading = false;
prescriptionsOrderList = apiResponse.data!; prescriptionsOrderList = apiResponse.data!;
// Sort by created date (newest first)
prescriptionsOrderList.sort((a, b) {
try {
if (a.created == null && b.created == null) return 0;
if (a.created == null) return 1;
if (b.created == null) return -1;
DateTime dateA = DateTime.parse(a.created!);
DateTime dateB = DateTime.parse(b.created!);
return dateB.compareTo(dateA); // Descending order (newest first)
} catch (e) {
print('Error parsing date for sorting: $e');
return 0;
}
});
notifyListeners(); notifyListeners();
if (onSuccess != null) { if (onSuccess != null) {
onSuccess(apiResponse); onSuccess(apiResponse);

@ -1776,5 +1776,12 @@ abstract class LocaleKeys {
static const routePrescription = 'routePrescription'; static const routePrescription = 'routePrescription';
static const noNearestER = 'noNearestER'; static const noNearestER = 'noNearestER';
static const loginToUseServiceLiveCare = 'loginToUseServiceLiveCare'; static const loginToUseServiceLiveCare = 'loginToUseServiceLiveCare';
static const mldl = 'mldl';
static const mmol = 'mmol';
static const triglycerideInfo = 'triglycerideInfo';
static const selectSmartWatch = 'selectSmartWatch';
static const selectBloodDonationCity = 'selectBloodDonationCity';
static const selectBloodDonationGender = 'selectBloodDonationGender';
static const selectBloodType = 'selectBloodType';
} }

@ -148,8 +148,8 @@ class _BloodDonationPageState extends State<BloodDonationPage> {
children: [ children: [
LocaleKeys.city.tr().toText16(color: AppColors.textColor, isBold: true), LocaleKeys.city.tr().toText16(color: AppColors.textColor, isBold: true),
(appState.isArabic() (appState.isArabic()
? (bloodDonationVM.selectedCity?.descriptionN ?? LocaleKeys.select.tr()) ? (bloodDonationVM.selectedCity?.descriptionN ?? LocaleKeys.selectBloodDonationCity.tr())
: bloodDonationVM.selectedCity?.description ?? LocaleKeys.select.tr(context: context)) : bloodDonationVM.selectedCity?.description ?? LocaleKeys.selectBloodDonationCity.tr(context: context))
.toText14(color: AppColors.greyTextColor, isBold: true), .toText14(color: AppColors.greyTextColor, isBold: true),
], ],
), ),
@ -176,8 +176,8 @@ class _BloodDonationPageState extends State<BloodDonationPage> {
children: [ children: [
LocaleKeys.gender.tr().toText16(color: AppColors.textColor, isBold: true), LocaleKeys.gender.tr().toText16(color: AppColors.textColor, isBold: true),
(appState.isArabic() (appState.isArabic()
? (bloodDonationVM.selectedGender?.typeAr ?? LocaleKeys.select.tr()) ? (bloodDonationVM.selectedGender?.typeAr ?? LocaleKeys.selectBloodDonationGender.tr())
: bloodDonationVM.selectedGender?.type ?? LocaleKeys.select.tr(context: context)) : bloodDonationVM.selectedGender?.type ?? LocaleKeys.selectBloodDonationGender.tr(context: context))
.toText14(color: AppColors.greyTextColor, isBold: true), .toText14(color: AppColors.greyTextColor, isBold: true),
], ],
), ),
@ -208,8 +208,8 @@ class _BloodDonationPageState extends State<BloodDonationPage> {
LocaleKeys.bloodType.tr().toText16(color: AppColors.textColor, isBold: true), LocaleKeys.bloodType.tr().toText16(color: AppColors.textColor, isBold: true),
// bloodDonationVM.selectedBloodType?.toText14(color: AppColors.greyTextColor, isBold: true), // bloodDonationVM.selectedBloodType?.toText14(color: AppColors.greyTextColor, isBold: true),
(appState.isArabic() (appState.isArabic()
? (bloodDonationVM.selectedBloodType ?? LocaleKeys.select.tr()) ? (bloodDonationVM.selectedBloodType ?? LocaleKeys.selectBloodType.tr())
: bloodDonationVM.selectedBloodType ?? LocaleKeys.select.tr(context: context)) : bloodDonationVM.selectedBloodType ?? LocaleKeys.selectBloodType.tr(context: context))
.toText14(color: AppColors.greyTextColor, isBold: true) .toText14(color: AppColors.greyTextColor, isBold: true)
], ],
), ),

@ -37,7 +37,12 @@ class SelectGenderWidget extends StatelessWidget {
child: Column( child: Column(
crossAxisAlignment: CrossAxisAlignment.start, crossAxisAlignment: CrossAxisAlignment.start,
spacing: 8.h, spacing: 8.h,
children: [GenderTypeEnum.values[index].displayName.toCamelCase.toText16(color: AppColors.textColor, isBold: true)], children: [
(isArabic
? GenderTypeEnum.values[index].typeAr
: GenderTypeEnum.values[index].type)
.toText16(color: AppColors.textColor, isBold: true)
],
), ),
), ),
Transform.flip( Transform.flip(
@ -52,7 +57,10 @@ class SelectGenderWidget extends StatelessWidget {
), ),
], ],
).paddingSymmetrical(16.h, 16.h).onPress(() { ).paddingSymmetrical(16.h, 16.h).onPress(() {
bloodDonationViewModel.onGenderChange(GenderTypeEnum.values[index].name.toCamelCase); final genderValue = isArabic
? GenderTypeEnum.values[index].typeAr
: GenderTypeEnum.values[index].type;
bloodDonationViewModel.onGenderChange(genderValue);
Navigator.of(context).pop(); Navigator.of(context).pop();
})); }));
}, },

@ -240,6 +240,7 @@ class ImmediateLiveCarePaymentDetails extends StatelessWidget {
], ],
).paddingSymmetrical(24.h, 0.h), ).paddingSymmetrical(24.h, 0.h),
(immediateLiveCareVM.liveCareImmediateAppointmentFeesList.total == "0" || immediateLiveCareVM.liveCareImmediateAppointmentFeesList.total == "0.0") (immediateLiveCareVM.liveCareImmediateAppointmentFeesList.total == "0" || immediateLiveCareVM.liveCareImmediateAppointmentFeesList.total == "0.0")
// (true)
? CustomButton( ? CustomButton(
text: LocaleKeys.confirmLiveCare.tr(context: context), text: LocaleKeys.confirmLiveCare.tr(context: context),
onPressed: () async { onPressed: () async {

@ -250,7 +250,10 @@ class _ComprehensiveCheckupPageState extends State<ComprehensiveCheckupPage> {
physics: NeverScrollableScrollPhysics(), physics: NeverScrollableScrollPhysics(),
itemBuilder: (context, index) { itemBuilder: (context, index) {
final service = services[index]; final service = services[index];
final serviceName = getIt.get<AppState>().isArabic()? service.textN??service.text??"" : service.text??service.textN ?? ''; final appStateLocal = getIt.get<AppState>();
final serviceName = appStateLocal.isArabic()
? (service.textN ?? service.text ?? '')
: (service.text ?? service.textN ?? '');
final price = service.priceTotal ?? 0.0; final price = service.priceTotal ?? 0.0;
return RadioListTileWidget<int>( return RadioListTileWidget<int>(
value: service.iD ?? 0, value: service.iD ?? 0,

@ -78,7 +78,7 @@ class _BloodCholesterolWidgetState extends State<BloodCholesterolWidget> {
child: Column( child: Column(
children: [ children: [
_buildInputField( _buildInputField(
label: "MG/DL", label: LocaleKeys.mldl.tr(),
hint: "120", hint: "120",
controller: _mgdlController, controller: _mgdlController,
focusNode: _mgdlFocus, focusNode: _mgdlFocus,
@ -107,7 +107,7 @@ class _BloodCholesterolWidgetState extends State<BloodCholesterolWidget> {
], ],
), ),
_buildInputField( _buildInputField(
label: "MMOL/L", label:LocaleKeys.mmol.tr(),
hint: "3.1", hint: "3.1",
controller: _mmolController, controller: _mmolController,
focusNode: _mmolFocus, focusNode: _mmolFocus,

@ -1,4 +1,6 @@
import 'package:easy_localization/easy_localization.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:hmg_patient_app_new/generated/locale_keys.g.dart';
import 'package:provider/provider.dart'; import 'package:provider/provider.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart'; import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
@ -80,7 +82,7 @@ class _TriglyceridesWidgetState extends State<TriglyceridesWidget> {
child: Column( child: Column(
children: [ children: [
_buildInputField( _buildInputField(
label: "MG/DL", label: LocaleKeys.mldl.tr(),
hint: "150", hint: "150",
controller: _mgdlController, controller: _mgdlController,
focusNode: _mgdlFocus, focusNode: _mgdlFocus,
@ -111,7 +113,7 @@ class _TriglyceridesWidgetState extends State<TriglyceridesWidget> {
), ),
_buildInputField( _buildInputField(
label: "MMOL/L", label: LocaleKeys.mmol,
hint: "1.7", hint: "1.7",
controller: _mmolController, controller: _mmolController,
focusNode: _mmolFocus, focusNode: _mmolFocus,
@ -134,7 +136,7 @@ class _TriglyceridesWidgetState extends State<TriglyceridesWidget> {
SizedBox(width: 12.w), SizedBox(width: 12.w),
Expanded( Expanded(
child: child:
"Convert triglyceride values between mg/dL and mmol/L." LocaleKeys.triglycerideInfo.tr()
.toText12( .toText12(
isBold: true, isBold: true,
color: AppColors.inputLabelTextColor, color: AppColors.inputLabelTextColor,

@ -105,51 +105,51 @@ class _ServicesPageState extends State<ServicesPage> {
); );
} }
}), }),
HmgServicesComponentModel( // HmgServicesComponentModel(
11, // 11,
LocaleKeys.indoorNavigation.tr(), // LocaleKeys.indoorNavigation.tr(),
"", // "",
AppAssets.indoor_nav_icon, // AppAssets.indoor_nav_icon,
bgColor: Color(0xff45A2F8), // bgColor: Color(0xff45A2F8),
true, // true,
route: null, // route: null,
onTap: () { // onTap: () {
showCommonBottomSheetWithoutHeight( // showCommonBottomSheetWithoutHeight(
title: LocaleKeys.selectHospital.tr(), // title: LocaleKeys.selectHospital.tr(),
GetIt.instance<NavigationService>().navigatorKey.currentContext!, // GetIt.instance<NavigationService>().navigatorKey.currentContext!,
child: ChangeNotifierProvider( // child: ChangeNotifierProvider(
create: (context) => HospitalSelectionBottomSheetViewModel(getIt()), // create: (context) => HospitalSelectionBottomSheetViewModel(getIt()),
child: Consumer<HospitalSelectionBottomSheetViewModel>( // child: Consumer<HospitalSelectionBottomSheetViewModel>(
builder: (_, vm, __) => HospitalBottomSheetBody( // builder: (_, vm, __) => HospitalBottomSheetBody(
searchText: vm.searchController, // searchText: vm.searchController,
displayList: vm.displayList, // displayList: vm.displayList,
onFacilityClicked: (value) { // onFacilityClicked: (value) {
vm.setSelectedFacility(value); // vm.setSelectedFacility(value);
vm.getDisplayList(); // vm.getDisplayList();
}, // },
onHospitalClicked: (hospital) { // onHospitalClicked: (hospital) {
Navigator.pop(GetIt.instance<NavigationService>().navigatorKey.currentContext!); // Navigator.pop(GetIt.instance<NavigationService>().navigatorKey.currentContext!);
vm.openPenguin(hospital); // vm.openPenguin(hospital);
}, // },
onHospitalSearch: (value) { // onHospitalSearch: (value) {
vm.searchHospitals(value ?? ""); // vm.searchHospitals(value ?? "");
}, // },
selectedFacility: vm.selectedFacility, // selectedFacility: vm.selectedFacility,
hmcCount: vm.hmcCount, // hmcCount: vm.hmcCount,
hmgCount: vm.hmgCount, // hmgCount: vm.hmgCount,
), // ),
), // ),
), // ),
isFullScreen: false, // isFullScreen: false,
isCloseButtonVisible: true, // isCloseButtonVisible: true,
hasBottomPadding: false, // hasBottomPadding: false,
backgroundColor: AppColors.bottomSheetBgColor, // backgroundColor: AppColors.bottomSheetBgColor,
callBackFunc: () { // callBackFunc: () {
GetIt.instance<NavigationService>().navigatorKey.currentContext!.read<EmergencyServicesViewModel>().clearSearchText(); // GetIt.instance<NavigationService>().navigatorKey.currentContext!.read<EmergencyServicesViewModel>().clearSearchText();
}, // },
); // );
}, // },
), // ),
HmgServicesComponentModel(11, LocaleKeys.eReferralServices.tr(), "", AppAssets.eReferral, bgColor: AppColors.eReferralCardColor, true, route: null, onTap: () async { HmgServicesComponentModel(11, LocaleKeys.eReferralServices.tr(), "", AppAssets.eReferral, bgColor: AppColors.eReferralCardColor, true, route: null, onTap: () async {
if (getIt.get<AppState>().isAuthenticated) { if (getIt.get<AppState>().isAuthenticated) {
getIt.get<NavigationService>().pushPageRoute(AppRoutes.eReferralPage); getIt.get<NavigationService>().pushPageRoute(AppRoutes.eReferralPage);

@ -85,7 +85,7 @@ class SmartwatchHomePage extends StatelessWidget {
Image.asset("assets/images/png/smartwatches/apple-watch-5.jpg", width: 136.w, height: 136.h).paddingSymmetrical(24.w, 8.h), Image.asset("assets/images/png/smartwatches/apple-watch-5.jpg", width: 136.w, height: 136.h).paddingSymmetrical(24.w, 8.h),
"Apple Watch".needTranslation.toText16(isBold: true), "Apple Watch".needTranslation.toText16(isBold: true),
CustomButton( CustomButton(
text: LocaleKeys.select.tr(context: context), text: LocaleKeys.selectSmartWatch.tr(context: context),
onPressed: () { onPressed: () {
context.read<HealthProvider>().setSelectedWatchType(SmartWatchTypes.apple, "assets/images/png/smartwatches/apple-watch-5.jpg"); context.read<HealthProvider>().setSelectedWatchType(SmartWatchTypes.apple, "assets/images/png/smartwatches/apple-watch-5.jpg");
getIt.get<NavigationService>().pushPage(page: SmartwatchInstructionsPage( getIt.get<NavigationService>().pushPage(page: SmartwatchInstructionsPage(

@ -5,6 +5,7 @@ import 'package:flutter_callkit_incoming/entities/call_event.dart';
import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart'; import 'package:flutter_callkit_incoming/flutter_callkit_incoming.dart';
import 'package:flutter_zoom_videosdk/native/zoom_videosdk.dart'; import 'package:flutter_zoom_videosdk/native/zoom_videosdk.dart';
import 'package:get_it/get_it.dart'; import 'package:get_it/get_it.dart';
import 'package:hmg_patient_app_new/core/api/api_client.dart';
import 'package:hmg_patient_app_new/core/api_consts.dart'; import 'package:hmg_patient_app_new/core/api_consts.dart';
import 'package:hmg_patient_app_new/core/app_assets.dart'; import 'package:hmg_patient_app_new/core/app_assets.dart';
import 'package:hmg_patient_app_new/core/app_export.dart'; import 'package:hmg_patient_app_new/core/app_export.dart';
@ -182,9 +183,22 @@ class _SplashScreenState extends State<SplashPage> {
} }
break; break;
case Event.actionCallDecline: case Event.actionCallDecline:
print('actionCallConnected Event Received: ------->'); print('actionCallDecline Event Received: ------->');
// Get session ID from cache
String sessionId = await Utils.getStringFromPrefs(CacheConst.zoomRoomID);
//
// // Update session status to 3 (declined by patient)
// if (sessionId.isNotEmpty) {
await _updateSessionStatus(sessionId, 3, 'Patient');
// }
await FlutterCallkitIncoming.endAllCalls(); await FlutterCallkitIncoming.endAllCalls();
GetIt.instance<CacheService>().remove(key: CacheConst.isAppOpenedFromCall); GetIt.instance<CacheService>().remove(key: CacheConst.isAppOpenedFromCall);
// Clear cache
await Utils.saveStringFromPrefs(CacheConst.zoomRoomID, '');
await Utils.saveStringFromPrefs(CacheConst.callTypeID, '');
break; break;
case Event.actionCallEnded: case Event.actionCallEnded:
break; break;
@ -220,20 +234,35 @@ class _SplashScreenState extends State<SplashPage> {
/// load the Privilege from service /// load the Privilege from service
Future loadPrivilege() async { Future loadPrivilege() async {
// ProjectViewModel projectProvider = Provider.of<ProjectViewModel>(context, listen: false); // ...existing code...
// projectProvider.setPrivilegeModelList(privilege: _privilegeService.privilegeModelList); }
// projectProvider.setVidaPlusProjectList(_privilegeService.vidaPlusProjectListModel);
// projectProvider.setHMCProjectList(_privilegeService.hMCProjectListModel); /// Update session status when call is declined
// projectProvider.setProjectsDetailList(_privilegeService.projectDetailListModel); Future<void> _updateSessionStatus(String sessionId, int sessionStatus, String sessionEndedBy) async {
// double lat = await AppSharedPreferences().getDouble(USER_LAT) ?? 0.0; try {
// double long = await AppSharedPreferences().getDouble(USER_LONG) ?? 0.0; print('📡 Updating session status - SessionID: $sessionId, Status: $sessionStatus, EndedBy: $sessionEndedBy');
// AppSharedPreferences().clear(); // Clearing Shared Preferences On App Launch
// await AppSharedPreferences().setDouble(USER_LAT, lat); final apiClient = getIt.get<ApiClient>();
// await AppSharedPreferences().setDouble(USER_LONG, long); String sessionID = await Utils.getStringFromPrefs(CacheConst.zoomRoomID);
// AppSharedPreferences().setString(APP_LANGUAGE, projectProvider.isArabic ? "ar" : "en"); Map<String, dynamic> requestBody = {
// var themeNotifier = Provider.of<ThemeNotifier>(context, listen: false); "Open_SessionID": sessionID,
// themeNotifier.setTheme(defaultTheme(fontName: projectProvider.isArabic ? 'Cairo' : 'Poppins')); "SessionStatus": sessionStatus,
// PushNotificationHandler().init(context); // Asyncronously "SessionEndedBy": sessionEndedBy
};
await apiClient.post(
CHANGE_PATIENT_ER_SESSION,
body: requestBody,
onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
print('✅ Session status updated successfully: $response');
},
onFailure: (error, statusCode, {messageStatus, failureType}) {
print('❌ Failed to update session status: $error');
},
);
} catch (e) {
print('❌ Exception updating session status: $e');
}
} }
@override @override

Loading…
Cancel
Save