profile picture upload

pull/224/head
Sultan khan 2 days ago
commit 21ae30a8cc

@ -0,0 +1,5 @@
<svg width="30" height="30" viewBox="0 0 30 30" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M11.875 8.75C11.875 8.05965 12.4346 7.5 13.125 7.5L21.875 7.5C22.5654 7.5 23.125 8.05965 23.125 8.75C23.125 9.44036 22.5654 10 21.875 10L13.125 10C12.4346 10 11.875 9.44036 11.875 8.75Z" fill="#2E3039"/>
<path d="M13.125 12.5C12.4346 12.5 11.875 13.0596 11.875 13.75C11.875 14.4404 12.4346 15 13.125 15H17.5C18.1904 15 18.75 14.4404 18.75 13.75C18.75 13.0596 18.1904 12.5 17.5 12.5H13.125Z" fill="#2E3039"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M24.0703 1.70757C22.9908 1.56243 21.6268 1.56246 19.9726 1.5625L14.9137 1.5625C13.2596 1.56246 11.8955 1.56243 10.8159 1.70757C9.6825 1.85996 8.6815 2.19219 7.88001 2.99369C7.07851 3.79518 6.74627 4.79619 6.59389 5.92962C6.44874 7.00921 6.44878 8.37325 6.44882 10.0274L6.44882 19.1364C6.44882 19.4899 6.44882 19.6667 6.33899 19.7765C6.22915 19.8864 6.05237 19.8864 5.69881 19.8864L5.1043 19.8864C4.5742 19.8862 4.03689 19.886 3.59055 19.9603C3.05657 20.0492 2.49845 20.266 2.06822 20.803C1.66663 21.3043 1.54538 21.8484 1.56439 22.3879C1.57941 22.814 1.6877 23.2915 1.78692 23.7289C2.03287 24.816 2.48165 26.0118 3.20368 26.8876C3.95427 27.798 5.02349 28.4375 6.40497 28.4375L23.5434 28.4375C25.2852 28.4375 26.5975 27.7012 27.4152 26.4548C28.1809 25.2875 28.4375 23.7913 28.4375 22.3296V10.0275C28.4375 8.37327 28.4375 7.00921 28.2924 5.92962C28.14 4.79619 27.8078 3.79518 27.0063 2.99369C26.2048 2.19219 25.2038 1.85996 24.0703 1.70757ZM9.64221 19.8864C9.28866 19.8864 9.11188 19.8864 9.00205 19.7766C8.89221 19.6668 8.89221 19.49 8.89221 19.1364L8.89221 10.1137C8.89221 8.35157 8.89481 7.15287 9.01549 6.25522C9.13145 5.39271 9.33591 4.99322 9.6078 4.72133C9.87969 4.44944 10.2792 4.24498 11.1417 4.12902C12.0393 4.00833 13.238 4.00574 15.0002 4.00574L19.8865 4.00574C21.6487 4.00574 22.8474 4.00833 23.745 4.12902C24.6075 4.24498 25.007 4.44944 25.2789 4.72133C25.5508 4.99322 25.7552 5.39271 25.8712 6.25522C25.9919 7.15287 25.9945 8.35157 25.9945 10.1137V22.3296C25.9945 23.5665 25.7669 24.5135 25.3725 25.1147C25.03 25.6368 24.506 25.9944 23.5436 25.9944C23.0042 25.9944 22.5905 25.7739 22.2274 25.3334C21.8357 24.8583 21.5233 24.1393 21.325 23.2628C21.1929 22.6787 21.0635 22.1067 20.9328 21.7044C20.8046 21.3095 20.5948 20.7891 20.1326 20.4205C19.6995 20.0749 19.209 19.9703 18.8049 19.9269C18.428 19.8863 17.9679 19.8864 17.479 19.8864L9.64221 19.8864Z" fill="#2E3039"/>
</svg>

After

Width:  |  Height:  |  Size: 2.4 KiB

@ -694,7 +694,7 @@
"retouch": "روتوش",
"bikini": "بيكيني",
"totalMinutes": "إجمالي الدقائق",
"feedback": "ملاحظات",
"feedback": "رأيك يهمنا",
"likeToHear": "نود سماع ملاحظاتك، ومخاوفك بشأن خدمات الرعاية الصحية وتجربة الخدمات الإلكترونية. يرجى استخدام النموذج أدناه",
"emptySubject": "يرجى إدخال الموضوع",
"emptyMessage": "يرجى إدخال الرسالة",
@ -702,10 +702,10 @@
"complainAppo": "شكوى بخصوص الموعد",
"complainAppoAlt": "شكوى بدون موعد",
"messageType": "نوع الرسالة",
"feedbackType": "نوع الملاحظات",
"feedbackType": "نوع الرسالة",
"compliment": "تقدير",
"suggestion": "اقتراح",
"yourFeedback": "تم إرسال ملاحظاتك",
"yourFeedback": "تم إرسال رأيك",
"selectPart": "يرجى اختيار الجزء الذي تشتكي منه",
"number": "رقم",
"notClassified": "غير مصنف",
@ -1705,5 +1705,8 @@
"takePhotoWithCamera": "التقط صورة بالكاميرا",
"selectFromPhotoLibrary": "اختر من مكتبة الصور",
"selectDocumentFiles": "اختر ملفات المستندات",
"profilePicture": "صورة الملف الشخصي"
"profilePicture": "صورة الملف الشخصي",
"aiDisclaimerRad": "سيتم مشاركة بيانات نتائج الأشعة الخاصة بك بشكل آمن مع محلل الذكاء الاصطناعي لدينا لتحليلها. يساعد هذا في توفير رؤى صحية مخصصة. هل ترغب في المتابعة؟",
"sendingAskDrRequest": "جارٍ إرسال الطلب...",
"askDrRequestSentSuccess": "تم إرسال الطلب بنجاح، وسيتواصل معك الطبيب قريباً."
}

@ -1697,5 +1697,8 @@
"takePhotoWithCamera": "Take a photo with your camera",
"selectFromPhotoLibrary": "Select from your photo library",
"selectDocumentFiles": "Select document files",
"profilePicture": "Profile Picture"
"profilePicture": "Profile Picture",
"aiDisclaimerRad": "Your radiology result data will be securely shared with our AI Analyzer for analysis. This helps provide personalized health insights. Do you want to proceed?",
"sendingAskDrRequest": "Sending Request...",
"askDrRequestSentSuccess": "Request has been sent successfully, The doctor will get back to you soon."
}

@ -25,6 +25,7 @@ class ApiConsts {
static String GET_TAMARA_PAYMENT_STATUS = 'https://mdlaboratories.com/tamaralive/api/OnlineTamara/order_status?orderid=';
static String QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail";
// static String QLINE_URL = "https://qline.hmg.com/api/PatientCall/PatientInQueue_Detail";
static String CHAT_URL = "https://chat.hmg.com/Index.aspx?RequestedId=";
@ -44,7 +45,7 @@ class ApiConsts {
GET_TAMARA_INSTALLMENTS_URL = "https://mdlaboratories.com/tamaralive/Home/GetInstallments";
GET_TAMARA_PAYMENT_STATUS = 'https://mdlaboratories.com/tamaralive/api/OnlineTamara/order_status?orderid=';
rcBaseUrl = 'https://rc.hmg.com/';
QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail";
QLINE_URL = "https://qline.hmg.com/api/PatientCall/PatientInQueue_Detail";
CHAT_URL = "https://chat.hmg.com/Index.aspx?RequestedId=";
break;
case AppEnvironmentTypeEnum.dev:
@ -80,7 +81,7 @@ class ApiConsts {
GET_TAMARA_INSTALLMENTS_URL = "https://epharmacy.hmg.com/tamara/Home/getinstallments";
GET_TAMARA_PAYMENT_STATUS = 'https://epharmacy.hmg.com/tamara/api/OnlineTamara/order_status?orderid=';
rcBaseUrl = 'https://rc.hmg.com/';
QLINE_URL = "https://ms.hmg.com/nscapi/api/PatientCall/PatientInQueue_Detail";
QLINE_URL = "https://qline.hmg.com/api/PatientCall/PatientInQueue_Detail";
CHAT_URL = "https://chat.hmg.com/geneysChat/Index.aspx?RequestedId=";
break;
case AppEnvironmentTypeEnum.qa:
@ -475,7 +476,8 @@ var GET_ACTIVE_APPOINTMENTS_LIST_URL = "Services/Doctors.svc/Rest/Dr_GetAppointm
var GET_DOCTORS_LIST_URL = "Services/Doctors.svc/REST/SearchDoctorsByTime";
//URL to dental doctors list
var GET_DENTAL_DOCTORS_LIST_URL = "Services/Doctors.svc/REST/Dental_DoctorChiefComplaintMapping";
// var GET_DENTAL_DOCTORS_LIST_URL = "Services/Doctors.svc/REST/Dental_DoctorChiefComplaintMapping";
var GET_DENTAL_DOCTORS_LIST_URL = "Services/Doctors.svc/REST/Dental_DoctorChiefComplaintMapping_Nearest";
//URL to get doctor free slots
var GET_DOCTOR_FREE_SLOTS = "Services/Doctors.svc/REST/GetDoctorFreeSlots";

@ -357,6 +357,7 @@ class AppAssets {
static const String biometricLockIcon = '$svgBasePath/biometric_lock_icon.svg';
static const String imageIcon = '$svgBasePath/image_icon.svg';
static const String appointmentRatingIcon = '$svgBasePath/appointment_rating_icon.svg';
static const String invoiceListIcon = '$svgBasePath/invoice_list_icon.svg';
static const String open_camera = '$svgBasePath/open_camera.svg';
static const String gallery = '$svgBasePath/gallery.svg';
static const String files = '$svgBasePath/files.svg';

@ -10,14 +10,19 @@ import 'package:hmg_patient_app_new/features/authentication/models/resp_models/a
import 'package:hmg_patient_app_new/features/authentication/models/resp_models/check_user_staus_nhic_response_model.dart';
import 'package:hmg_patient_app_new/features/authentication/models/resp_models/select_device_by_imei.dart';
import 'package:hmg_patient_app_new/services/navigation_service.dart';
import 'package:hmg_patient_app_new/services/cache_service.dart';
import '../features/authentication/models/request_models/registration_payload_model.dart';
class AppState {
NavigationService navigationService;
CacheService cacheService;
AppState({required this.navigationService}){
static const String _profileImageKey = 'profile_image_data';
AppState({required this.navigationService, required this.cacheService}){
checkIfGMSAvailableOrNot();
_loadProfileImageFromCache();
}
double userLat = 0.0;
@ -88,7 +93,30 @@ class AppState {
String? get getProfileImageData => _profileImageData;
set setProfileImageData(String? value) => _profileImageData = value;
set setProfileImageData(String? value) {
print('📸 AppState: Setting profile image data - ${value != null && value.isNotEmpty ? "Has data (${value.length} chars)" : "Empty/null"}');
_profileImageData = value;
// Persist to cache
if (value != null && value.isNotEmpty) {
cacheService.saveString(key: _profileImageKey, value: value);
print('💾 AppState: Profile image saved to cache');
} else {
cacheService.remove(key: _profileImageKey);
print('🗑️ AppState: Profile image removed from cache');
}
}
/// Load profile image from cache on app initialization
void _loadProfileImageFromCache() {
_profileImageData = cacheService.getString(key: _profileImageKey);
print('📂 AppState: Loaded profile image from cache - ${_profileImageData != null && _profileImageData!.isNotEmpty ? "Has data (${_profileImageData!.length} chars)" : "Empty/null"}');
}
/// Clear profile image from cache (e.g., on logout)
void clearProfileImageCache() {
_profileImageData = null;
cacheService.remove(key: _profileImageKey);
}
SelectDeviceByImeiRespModelElement? _selectDeviceByImeiRespModelElement;
@ -255,4 +283,18 @@ class AppState {
_userSwitchPayloads.clear();
_childSwitchCounter = 0;
}
void clearChildUsersKeepSuperUser() {
// Keep the super user payload
final superUserPayload = _userSwitchPayloads['superUser'];
// Clear all payloads
_userSwitchPayloads.clear();
_childSwitchCounter = 0;
// Restore super user if it existed
if (superUserPayload != null) {
_userSwitchPayloads['superUser'] = superUserPayload;
}
}
}

@ -113,7 +113,12 @@ class AppDependencies {
getIt.registerLazySingleton<LocalAuthentication>(() => LocalAuthentication());
getIt.registerLazySingleton<GAnalytics>(() => GAnalytics());
getIt.registerLazySingleton<AppState>(() => AppState(navigationService: getIt()));
// Register CacheService before AppState so it can be injected
final sharedPreferences = await SharedPreferences.getInstance();
getIt.registerLazySingleton<CacheService>(() => CacheServiceImp(sharedPreferences: sharedPreferences, loggerService: getIt()));
getIt.registerLazySingleton<AppState>(() => AppState(navigationService: getIt(), cacheService: getIt()));
getIt.registerLazySingleton<LocationUtils>(() => LocationUtils(
isShowConfirmDialog: false,
navigationService: getIt(),
@ -126,8 +131,6 @@ class AppDependencies {
navigationService: getIt(),
));
final sharedPreferences = await SharedPreferences.getInstance();
getIt.registerLazySingleton<CacheService>(() => CacheServiceImp(sharedPreferences: sharedPreferences, loggerService: getIt()));
final flutterLocalNotificationsPlugin = FlutterLocalNotificationsPlugin();
getIt.registerLazySingleton<NotificationService>(() => NotificationServiceImp(

@ -314,7 +314,7 @@ class AuthenticationRepoImp implements AuthenticationRepo {
requestBody['PatientIdentificationID'] = "";
requestBody.removeWhere((key, value) => ['NationalID', 'isDentalAllowedBackend', 'ForRegisteration'].contains(key));
appState.setIsChildLoggedIn = false;
appState.clearUserSwitchPayloads();
appState.clearChildUsersKeepSuperUser();
}
} else {
appState.setIsChildLoggedIn = true;

@ -631,6 +631,10 @@ class AuthenticationViewModel extends ChangeNotifier {
} else {
if (activation.list != null && activation.list!.isNotEmpty) {
if (isSwitchUser) {
// Clear profile image cache when switching users
_appState.clearProfileImageCache();
log("🔄 Switching user - cleared profile image cache");
if (_appState.getIsChildLoggedIn) {
_appState.setSuperUserID = null;
// _appState.setIsChildLoggedIn = false;
@ -642,17 +646,20 @@ class AuthenticationViewModel extends ChangeNotifier {
_appState.addUserSwitchPayload(activation.list!.first.toJson(), isSuperUser: true);
}
} else {
_appState.setSuperUserID = _appState.getAuthenticatedUser()?.patientId;
// _appState.setIsChildLoggedIn = false;
var allUsersPayload = _appState.getUserSwitchPayloads;
_appState.setSuperUserID = allUsersPayload['superUser']?['PatientID'];
activation.list!.first.isParentUser = true;
_appState.addUserSwitchPayload(activation.list!.first.toJson(), isSuperUser: true);
}
// Fetch profile image after switching user
_fetchProfileImage(activation.list!.first.patientId);
await _fetchProfileImage(activation.list!.first.patientId);
} else {
activation.list!.first.isParentUser = true;
_appState.addUserSwitchPayload(activation.list!.first.toJson(), isSuperUser: true);
// Fetch profile image after login
await _fetchProfileImage(activation.list!.first.patientId);
}
activation.list!.first.bloodGroup = activation.patientBloodType;
activation.list!.first.zipCode = selectedCountrySignup == CountryEnum.others ? '0' : selectedCountrySignup.countryCode;
@ -660,8 +667,6 @@ class AuthenticationViewModel extends ChangeNotifier {
_appState.setPrivilegeModelList(activation.list!.first.listPrivilege!);
_appState.setUserBloodGroup = activation.patientBlodType ?? "N/A";
// Fetch profile image after login
_fetchProfileImage(activation.list!.first.patientId);
// Fetch patient blood type from new API
// await getPatientBloodTypeNew();
@ -1179,6 +1184,7 @@ class AuthenticationViewModel extends ChangeNotifier {
_appState.setIsAuthenticated = false;
_appState.setAppAuthToken = "";
_appState.setUserBloodGroup = "";
_appState.clearProfileImageCache(); // Clear profile image cache
_appState.setSuperUserID = null;
_appState.setIsChildLoggedIn = false;
_appState.setFamilyFileTokenID = "";
@ -1227,13 +1233,8 @@ class AuthenticationViewModel extends ChangeNotifier {
Future<void> _fetchProfileImage(int? patientID) async {
if (patientID == null) return;
// Skip if already loaded in AppState
if (_appState.getProfileImageData != null && _appState.getProfileImageData!.isNotEmpty) {
log("Profile image already cached, skipping API call");
return;
}
try {
log("Fetching profile image for patient ID: $patientID");
final profileSettingsRepo = GetIt.instance<ProfileSettingsRepo>();
final result = await profileSettingsRepo.getProfileImage(patientID: patientID);
@ -1246,17 +1247,23 @@ class AuthenticationViewModel extends ChangeNotifier {
if (apiResponse.data != null && apiResponse.data['Patient_GetProfileImageDataList'] != null) {
var imageList = apiResponse.data['Patient_GetProfileImageDataList'];
if (imageList is List && imageList.isNotEmpty) {
String? imageData = imageList[0]['PatientsProfileImageData'];
String? imageData = imageList[0]['ImageData'];
if (imageData != null && imageData.isNotEmpty) {
_appState.setProfileImageData = imageData;
log("Profile image loaded successfully");
log("✅ Profile image loaded and cached successfully");
} else {
log("⚠️ Profile image data is empty");
}
} else {
log("⚠️ Profile image list is empty");
}
} else {
log("⚠️ No profile image data in response");
}
},
);
} catch (e) {
log("Error fetching profile image: $e");
log("Error fetching profile image: $e");
}
}
}

@ -86,7 +86,7 @@ abstract class BookAppointmentsRepo {
{required int projectID, required int clinicID, required int patientID, Function(dynamic)? onSuccess, Function(String)? onError});
Future<Either<Failure, GenericApiModel<List<DoctorsListResponseModel>>>> getDentalChiefComplaintDoctorsList(int projectID, int chiefComplaintID,
{Function(dynamic)? onSuccess, Function(String)? onError});
{bool isGetNearestAppointments, Function(dynamic)? onSuccess, Function(String)? onError});
Future<Either<Failure, GenericApiModel<List<LaserBodyPart>>>> getLaserClinics(int laserCategoryID, int projectID, int languageID, {Function(dynamic)? onSuccess, Function(String)? onError});
@ -877,12 +877,13 @@ class BookAppointmentsRepoImp implements BookAppointmentsRepo {
@override
Future<Either<Failure, GenericApiModel<List<DoctorsListResponseModel>>>> getDentalChiefComplaintDoctorsList(int projectID, int chiefComplaintID,
{Function(dynamic)? onSuccess, Function(String)? onError}) async {
{bool isGetNearestAppointments = false, Function(dynamic)? onSuccess, Function(String)? onError}) async {
Map<String, dynamic> mapDevice = {
"ProjectID": projectID,
"ChiefComplaintID": chiefComplaintID,
"isDentalAllowedBackend": true,
"IsPublicRequest": true,
"IsGetNearAppointment": isGetNearestAppointments
};
try {

@ -1471,12 +1471,11 @@ class BookAppointmentsViewModel extends ChangeNotifier {
}
Future<void> getDentalChiefComplaintDoctorsList({int projectID = 0, Function(dynamic)? onSuccess, Function(String)? onError}) async {
setIsNearestAppointmentSelected(false);
doctorsList.clear();
notifyListeners();
projectID = currentlySelectedHospitalFromRegionFlow != null ? int.parse(currentlySelectedHospitalFromRegionFlow!) : projectID;
final result = await bookAppointmentsRepo.getDentalChiefComplaintDoctorsList(projectID, selectedChiefComplaintID);
final result = await bookAppointmentsRepo.getDentalChiefComplaintDoctorsList(projectID, selectedChiefComplaintID, isGetNearestAppointments: isNearestAppointmentSelected);
result.fold(
(failure) async {

@ -6,6 +6,7 @@ class PatientQueueDetails {
String? roomNo;
String? calledOn;
bool? servingNow;
int? isClinicConfigured;
PatientQueueDetails(
{this.patientID,
@ -14,7 +15,7 @@ class PatientQueueDetails {
this.callType,
this.roomNo,
this.calledOn,
this.servingNow});
this.servingNow, this.isClinicConfigured});
PatientQueueDetails.fromJson(Map<String, dynamic> json) {
patientID = json['patientID'];
@ -24,10 +25,11 @@ class PatientQueueDetails {
roomNo = json['roomNo'];
calledOn = json['calledOn'];
servingNow = json['servingNow'];
isClinicConfigured = json['isClinicConfigured'];
}
Map<String, dynamic> toJson() {
final Map<String, dynamic> data = new Map<String, dynamic>();
final Map<String, dynamic> data = Map<String, dynamic>();
data['patientID'] = this.patientID;
data['patientName'] = this.patientName;
data['queueNo'] = this.queueNo;
@ -35,6 +37,7 @@ class PatientQueueDetails {
data['roomNo'] = this.roomNo;
data['calledOn'] = this.calledOn;
data['servingNow'] = this.servingNow;
data['isClinicConfigured'] = this.isClinicConfigured;
return data;
}
}

@ -77,7 +77,7 @@ abstract class MyAppointmentsRepo {
Future<Either<Failure, GenericApiModel<void>>> sendDoctorRate(
int rate, int appointmentNo, int projectID, int doctorID, int clinicID, String note, String appoDate, String docName, String projectName, String clinicName);
Future<Either<Failure, GenericApiModel<List<PatientQueueDetails>>>> getPatientAppointmentQueueDetails({required int appointmentNo, required int patientID});
Future<Either<Failure, GenericApiModel<List<PatientQueueDetails>>>> getPatientAppointmentQueueDetails({required int appointmentNo, required int patientID, required int clinicID});
Future<Either<Failure, GenericApiModel<dynamic>>> convertPatientToCash({required int projectID});
@ -833,8 +833,8 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo {
}
@override
Future<Either<Failure, GenericApiModel<List<PatientQueueDetails>>>> getPatientAppointmentQueueDetails({required int appointmentNo, required int patientID}) async {
Map<String, dynamic> mapDevice = {"appointmentNo": appointmentNo, "patientID": patientID, "apiKey": "EE17D21C7943485D9780223CCE55DCE5"};
Future<Either<Failure, GenericApiModel<List<PatientQueueDetails>>>> getPatientAppointmentQueueDetails({required int appointmentNo, required int patientID, required int clinicID}) async {
Map<String, dynamic> mapDevice = {"appointmentNo": appointmentNo, "patientID": patientID, "clinicID": clinicID, "apiKey": "EE17D21C7943485D9780223CCE55DCE5"};
try {
GenericApiModel<List<PatientQueueDetails>>? apiResponse;
Failure? failure;
@ -842,6 +842,7 @@ class MyAppointmentsRepoImp implements MyAppointmentsRepo {
failure = failureType;
}, onSuccess: (response, statusCode, {messageStatus, errorMessage}) {
try {
// final list = response['data'];
final list = response['data'];
final queueList = list.map((item) => PatientQueueDetails.fromJson(item as Map<String, dynamic>)).toList().cast<PatientQueueDetails>();

@ -929,9 +929,11 @@ class MyAppointmentsViewModel extends ChangeNotifier {
//TODO: Discuss With Haroon, Is the User Has no data it return No Element Bad State;
isPatientHasQueueAppointment = false;
isAppointmentQueueDetailsLoading = true;
patientQueueDetailsList.clear();
notifyListeners();
final result = await myAppointmentsRepo.getPatientAppointmentQueueDetails(
clinicID: patientArrivedAppointmentsHistoryList.first.clinicID,
appointmentNo: patientArrivedAppointmentsHistoryList.first.appointmentNo, patientID: patientArrivedAppointmentsHistoryList.first.patientID);
isAppointmentQueueDetailsLoading = false;
@ -948,8 +950,32 @@ class MyAppointmentsViewModel extends ChangeNotifier {
onError!(apiResponse.errorMessage!);
} else if (apiResponse.messageStatus == 1) {
if (apiResponse.data != null && apiResponse.data!.isNotEmpty) {
isPatientHasQueueAppointment = true;
patientQueueDetailsList = apiResponse.data!;
if (patientQueueDetailsList.first.isClinicConfigured == 1) {
isPatientHasQueueAppointment = true;
for (var element in patientQueueDetailsList) {
if (element.patientID == patientArrivedAppointmentsHistoryList.first.patientID) {
currentPatientQueueDetails = element;
currentQueueStatus = element.callType!;
// currentQueueStatus = 2;
break;
}
}
// patientQueueDetailsList.first.callType = 1;
patientQueueDetailsList.removeWhere((element) => element.patientID == patientArrivedAppointmentsHistoryList.first.patientID);
} else {
isPatientHasQueueAppointment = false;
}
} else {
isPatientHasQueueAppointment = false;
}
if (apiResponse.data != null && apiResponse.data!.isNotEmpty) {
patientQueueDetailsList = apiResponse.data!;
// if (patientQueueDetailsList.first.isClinicConfigured == 1) {
isPatientHasQueueAppointment = true;
for (var element in patientQueueDetailsList) {
if (element.patientID == patientArrivedAppointmentsHistoryList.first.patientID) {
currentPatientQueueDetails = element;
@ -959,8 +985,10 @@ class MyAppointmentsViewModel extends ChangeNotifier {
}
}
// patientQueueDetailsList.first.callType = 1;
patientQueueDetailsList.removeWhere((element) => element.patientID == patientArrivedAppointmentsHistoryList.first.patientID);
// } else {
// isPatientHasQueueAppointment = false;
// }
} else {
isPatientHasQueueAppointment = false;
}

@ -1697,5 +1697,7 @@ abstract class LocaleKeys {
static const selectFromPhotoLibrary = 'selectFromPhotoLibrary';
static const selectDocumentFiles = 'selectDocumentFiles';
static const profilePicture = 'profilePicture';
static const sendingAskDrRequest = 'sendingAskDrRequest';
static const askDrRequestSentSuccess = 'askDrRequestSentSuccess';
}

@ -912,9 +912,9 @@ class _AppointmentDetailsPageState extends State<AppointmentDetailsPage> {
],
).paddingOnly(left: 16.h, top: 24.h, right: 16.h, bottom: 0.h),
AppointmentType.isArrived(widget.patientAppointmentHistoryResponseModel)
?
(widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment! ||
widget.patientAppointmentHistoryResponseModel.isExecludeDoctor! ||
? (!(widget.patientAppointmentHistoryResponseModel.isActiveDoctor ?? true) ||
widget.patientAppointmentHistoryResponseModel.isLiveCareAppointment! ||
widget.patientAppointmentHistoryResponseModel.isExecludeDoctor! ||
!Utils.isClinicAllowedForRebook(widget.patientAppointmentHistoryResponseModel.clinicID))
? SizedBox.shrink()
: CustomButton(

@ -243,88 +243,88 @@ class AppointmentCard extends StatelessWidget {
(patientAppointmentHistoryResponseModel.isExecludeDoctor ?? false) ||
!Utils.isClinicAllowedForRebook(patientAppointmentHistoryResponseModel.clinicID ?? 0))) &&
AppointmentType.isArrived(patientAppointmentHistoryResponseModel)) {
if (((patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) &&
DateTime.now().difference(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate)).inDays <= 15)) {
return Row(
children: [
Expanded(
flex: 6,
child: CustomButton(
text: LocaleKeys.askDoctor.tr(context: context),
onPressed: () async {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingDoctorAvailability.tr(context: context));
await myAppointmentsViewModel.isDoctorAvailable(
projectID: patientAppointmentHistoryResponseModel.projectID,
doctorId: patientAppointmentHistoryResponseModel.doctorID,
clinicId: patientAppointmentHistoryResponseModel.clinicID,
onSuccess: (value) async {
if (value) {
await myAppointmentsViewModel.getAskDoctorRequestTypes(onSuccess: (val) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
title: LocaleKeys.askDoctor.tr(context: context),
child: AskDoctorRequestTypeSelect(
askDoctorRequestTypeList: myAppointmentsViewModel.askDoctorRequestTypeList,
myAppointmentsViewModel: myAppointmentsViewModel,
patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel,
),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
} else {
print("Doctor is not available");
}
});
},
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: (isFoldable || isTablet) ? 12.f : 14.f,
fontWeight: FontWeight.w600,
borderRadius: 12.r,
padding: EdgeInsets.symmetric(horizontal: 10.w),
// height: isTablet || isFoldable ? 46.h : 40.h,
height: 40.h,
icon: AppAssets.ask_doctor_icon,
iconColor: AppColors.primaryRedColor,
iconSize: 16.h,
),
),
SizedBox(width: 8.h),
Expanded(
flex: 1,
child: Container(
height: (isFoldable || isTablet) ? 50.h : 40.h,
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
color: AppColors.textColor,
borderRadius: 10.h,
side: BorderSide(
color: AppColors.textColor,
width: 1.2,
),
),
child: Transform.flip(
flipX: appState.isArabic(),
child: Utils.buildSvgWithAssets(
icon: AppAssets.forward_arrow_icon,
iconColor: AppColors.whiteColor,
width: 24.w,
height: 24.h,
fit: BoxFit.contain,
),
),
).toShimmer2(isShow: isLoading).onPress(() {
_goToDetails(context);
}),
),
],
);
} else {
return CustomButton(
text: isFromMedicalReport ? LocaleKeys.selectAppointment.tr(context: context) : LocaleKeys.viewDetails.tr(context: context),
// if (((patientAppointmentHistoryResponseModel.isLiveCareAppointment ?? false) &&
// DateTime.now().difference(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate)).inDays <= 15)) {
// return Row(
// children: [
// Expanded(
// flex: 6,
// child: CustomButton(
// text: LocaleKeys.askDoctor.tr(context: context),
// onPressed: () async {
// LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingDoctorAvailability.tr(context: context));
// await myAppointmentsViewModel.isDoctorAvailable(
// projectID: patientAppointmentHistoryResponseModel.projectID,
// doctorId: patientAppointmentHistoryResponseModel.doctorID,
// clinicId: patientAppointmentHistoryResponseModel.clinicID,
// onSuccess: (value) async {
// if (value) {
// await myAppointmentsViewModel.getAskDoctorRequestTypes(onSuccess: (val) {
// LoaderBottomSheet.hideLoader();
// showCommonBottomSheetWithoutHeight(
// context,
// title: LocaleKeys.askDoctor.tr(context: context),
// child: AskDoctorRequestTypeSelect(
// askDoctorRequestTypeList: myAppointmentsViewModel.askDoctorRequestTypeList,
// myAppointmentsViewModel: myAppointmentsViewModel,
// patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel,
// ),
// callBackFunc: () {},
// isFullScreen: false,
// isCloseButtonVisible: true,
// );
// });
// } else {
// print("Doctor is not available");
// }
// });
// },
// backgroundColor: AppColors.secondaryLightRedColor,
// borderColor: AppColors.secondaryLightRedColor,
// textColor: AppColors.primaryRedColor,
// fontSize: (isFoldable || isTablet) ? 12.f : 14.f,
// fontWeight: FontWeight.w600,
// borderRadius: 12.r,
// padding: EdgeInsets.symmetric(horizontal: 10.w),
// // height: isTablet || isFoldable ? 46.h : 40.h,
// height: 40.h,
// icon: AppAssets.ask_doctor_icon,
// iconColor: AppColors.primaryRedColor,
// iconSize: 16.h,
// ),
// ),
// SizedBox(width: 8.h),
// Expanded(
// flex: 1,
// child: Container(
// height: (isFoldable || isTablet) ? 50.h : 40.h,
// decoration: RoundedRectangleBorder().toSmoothCornerDecoration(
// color: AppColors.textColor,
// borderRadius: 10.h,
// side: BorderSide(
// color: AppColors.textColor,
// width: 1.2,
// ),
// ),
// child: Transform.flip(
// flipX: appState.isArabic(),
// child: Utils.buildSvgWithAssets(
// icon: AppAssets.forward_arrow_icon,
// iconColor: AppColors.whiteColor,
// width: 24.w,
// height: 24.h,
// fit: BoxFit.contain,
// ),
// ),
// ).toShimmer2(isShow: isLoading).onPress(() {
// _goToDetails(context);
// }),
// ),
// ],
// );
// } else {
return CustomButton(
text: isFromMedicalReport ? LocaleKeys.selectAppointment.tr(context: context) : LocaleKeys.viewDetails.tr(context: context),
onPressed: () {
if (isFromMedicalReport) {
if (isForFeedback) {
@ -359,7 +359,7 @@ class AppointmentCard extends StatelessWidget {
iconColor: AppColors.primaryRedColor,
iconSize: 16.h,
);
}
// }
} else {
if (isFromMedicalReport) {
if (isForEyeMeasurements) {
@ -390,8 +390,9 @@ class AppointmentCard extends StatelessWidget {
);
}
}
return Row(
children: [
return (patientAppointmentHistoryResponseModel.isActiveDoctor ?? true)
? Row(
children: [
Expanded(
flex: 6,
child: (AppointmentType.isArrived(patientAppointmentHistoryResponseModel)
@ -441,58 +442,85 @@ class AppointmentCard extends StatelessWidget {
}),
),
],
);
)
: CustomButton(
text: LocaleKeys.viewDetails.tr(context: context),
onPressed: () {
Navigator.of(context)
.push(
CustomPageRoute(
page: AppointmentDetailsPage(patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel),
),
)
.then((_) {
myAppointmentsViewModel.initAppointmentsViewModel();
myAppointmentsViewModel.getPatientAppointments(true, false);
});
},
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: (isFoldable || isTablet) ? 12.f : 14.f,
fontWeight: FontWeight.w600,
borderRadius: 12.r,
padding: EdgeInsets.symmetric(horizontal: 10.w),
// height: isTablet || isFoldable ? 46.h : 40.h,
height: 40.h,
icon: isFromMedicalReport ? AppAssets.checkmark_icon : null,
iconColor: AppColors.primaryRedColor,
iconSize: 16.h,
);
}
}
Widget _getArrivedButton(BuildContext context) {
final isRecent = DateTime.now().difference(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate)).inDays <= 15;
if (isRecent) {
return CustomButton(
text: LocaleKeys.askDoctor.tr(context: context),
onPressed: () async {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingDoctorAvailability.tr(context: context));
await myAppointmentsViewModel.isDoctorAvailable(
projectID: patientAppointmentHistoryResponseModel.projectID,
doctorId: patientAppointmentHistoryResponseModel.doctorID,
clinicId: patientAppointmentHistoryResponseModel.clinicID,
onSuccess: (value) async {
if (value) {
await myAppointmentsViewModel.getAskDoctorRequestTypes(onSuccess: (val) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
title: LocaleKeys.askDoctor.tr(context: context),
child: AskDoctorRequestTypeSelect(
askDoctorRequestTypeList: myAppointmentsViewModel.askDoctorRequestTypeList,
myAppointmentsViewModel: myAppointmentsViewModel,
patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel,
),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
} else {
print("Doctor is not available");
}
});
},
backgroundColor: AppColors.secondaryLightRedColor,
borderColor: AppColors.secondaryLightRedColor,
textColor: AppColors.primaryRedColor,
fontSize: (isFoldable || isTablet) ? 12.f : 14.f,
fontWeight: FontWeight.w600,
borderRadius: 12.r,
padding: EdgeInsets.symmetric(horizontal: 10.w),
// height: isTablet || isFoldable ? 46.h : 40.h,
height: 40.h,
icon: AppAssets.ask_doctor_icon,
iconColor: AppColors.primaryRedColor,
iconSize: 16.h,
);
}
// final isRecent = DateTime.now().difference(DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate)).inDays <= 15;
//
// if (isRecent) {
// return CustomButton(
// text: LocaleKeys.askDoctor.tr(context: context),
// onPressed: () async {
// LoaderBottomSheet.showLoader(loadingText: LocaleKeys.checkingDoctorAvailability.tr(context: context));
// await myAppointmentsViewModel.isDoctorAvailable(
// projectID: patientAppointmentHistoryResponseModel.projectID,
// doctorId: patientAppointmentHistoryResponseModel.doctorID,
// clinicId: patientAppointmentHistoryResponseModel.clinicID,
// onSuccess: (value) async {
// if (value) {
// await myAppointmentsViewModel.getAskDoctorRequestTypes(onSuccess: (val) {
// LoaderBottomSheet.hideLoader();
// showCommonBottomSheetWithoutHeight(
// context,
// title: LocaleKeys.askDoctor.tr(context: context),
// child: AskDoctorRequestTypeSelect(
// askDoctorRequestTypeList: myAppointmentsViewModel.askDoctorRequestTypeList,
// myAppointmentsViewModel: myAppointmentsViewModel,
// patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel,
// ),
// callBackFunc: () {},
// isFullScreen: false,
// isCloseButtonVisible: true,
// );
// });
// } else {
// print("Doctor is not available");
// }
// });
// },
// backgroundColor: AppColors.secondaryLightRedColor,
// borderColor: AppColors.secondaryLightRedColor,
// textColor: AppColors.primaryRedColor,
// fontSize: (isFoldable || isTablet) ? 12.f : 14.f,
// fontWeight: FontWeight.w600,
// borderRadius: 12.r,
// padding: EdgeInsets.symmetric(horizontal: 10.w),
// // height: isTablet || isFoldable ? 46.h : 40.h,
// height: 40.h,
// icon: AppAssets.ask_doctor_icon,
// iconColor: AppColors.primaryRedColor,
// iconSize: 16.h,
// );
// }
return CustomButton(
borderSide: BorderSide(

@ -126,7 +126,7 @@ class AppointmentDoctorCard extends StatelessWidget {
DateUtil.convertStringToDate(patientAppointmentHistoryResponseModel.appointmentDate),
false,
)}"
.toText10(isEnglishOnly: true),
.toText10(isBold: true),
),
),
AppCustomChipWidget(
@ -142,8 +142,10 @@ class AppointmentDoctorCard extends StatelessWidget {
],
),
),
Utils.buildSvgWithAssets(icon: AppAssets.doctor_profile_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown).onPress(() async {
DoctorsListResponseModel selectedDoctor = DoctorsListResponseModel();
patientAppointmentHistoryResponseModel.isLiveCareAppointment!
? SizedBox.shrink()
: Utils.buildSvgWithAssets(icon: AppAssets.doctor_profile_icon, width: 20.h, height: 20.h, fit: BoxFit.scaleDown).onPress(() async {
DoctorsListResponseModel selectedDoctor = DoctorsListResponseModel();
selectedDoctor.doctorID = patientAppointmentHistoryResponseModel.doctorID;
selectedDoctor.doctorImageURL = patientAppointmentHistoryResponseModel.doctorImageURL;
selectedDoctor.name = patientAppointmentHistoryResponseModel.doctorNameObj;
@ -265,7 +267,7 @@ class AppointmentDoctorCard extends StatelessWidget {
fontSize: 14.f,
isBold: true,
borderRadius: 12.r,
height: 40.h,
height: 46.h,
icon: AppAssets.appointment_calendar_icon,
iconColor: AppColors.primaryRedColor,
iconSize: 16.h,
@ -284,7 +286,7 @@ class AppointmentDoctorCard extends StatelessWidget {
fontSize: 14.f,
isBold: true,
borderRadius: 12.r,
height: 40.h,
height: 46.h,
icon: AppAssets.cancel,
iconColor: AppColors.whiteColor,
iconSize: 16.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/app_assets.dart';
import 'package:hmg_patient_app_new/core/utils/size_utils.dart';
import 'package:hmg_patient_app_new/core/utils/utils.dart';
@ -10,6 +11,7 @@ import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/
import 'package:hmg_patient_app_new/features/my_appointments/models/resp_models/patient_appointment_history_response_model.dart';
import 'package:hmg_patient_app_new/features/my_appointments/my_appointments_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';
@ -95,8 +97,8 @@ class AskDoctorRequestTypeSelect extends StatelessWidget {
child: CustomButton(
text: LocaleKeys.confirm.tr(),
onPressed: () async {
Navigator.pop(context);
LoaderBottomSheet.showLoader(loadingText: "Sending Request...");
Navigator.of(context).pop();
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.sendingAskDrRequest.tr(context: context));
await myAppointmentsViewModel.sendAskDocCallRequest(
patientAppointmentHistoryResponseModel: patientAppointmentHistoryResponseModel,
requestType: selectedParameterCodeValue.toString(),
@ -104,17 +106,10 @@ class AskDoctorRequestTypeSelect extends StatelessWidget {
userMobileNumber: myAppointmentsViewModel.appState.getAuthenticatedUser()!.mobileNumber!,
onSuccess: (val) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getSuccessWidget(loadingText: "Request has been sent successfully, you will be contacted soon."),
callBackFunc: () {
Navigator.of(context).pop();
},
title: "",
isCloseButtonVisible: true,
isDismissible: false,
isFullScreen: false,
);
showCommonBottomSheetWithoutHeight(GetIt.instance<NavigationService>().navigatorKey.currentContext!,
child: Padding(padding: EdgeInsets.symmetric(horizontal: 24.h), child: Utils.getSuccessWidget(loadingText: LocaleKeys.askDrRequestSentSuccess.tr())), callBackFunc: () {
Navigator.of(context).pop();
}, title: "", isCloseButtonVisible: false, isDismissible: false, isFullScreen: false, isAutoDismiss: true);
},
onError: (errMessage) {
LoaderBottomSheet.hideLoader();

@ -92,7 +92,7 @@ class _SavedLogin extends State<SavedLogin> {
Container(
padding: EdgeInsets.all(16.h),
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: false, isCustomShadow: [
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 20.h, hasShadow: false, isCustomShadow: [
BoxShadow(color: Color(0x0D000000), blurRadius: 16.h, offset: Offset(0, 0), spreadRadius: 5.h),
]),
child: Column(
@ -288,6 +288,7 @@ class _SavedLogin extends State<SavedLogin> {
child: CustomButton(
text: LocaleKeys.guest.tr(),
onPressed: () {
authVm.setIsInitialLoginNavigated(true);
Navigator.pushAndRemoveUntil(
context,
CustomPageRoute(

@ -355,10 +355,10 @@ class _BookAppointmentPageState extends State<BookAppointmentPage> {
return Column(
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Utils.buildSvgWithAssets(icon: AppAssets.add_new_family_icon, height: 64.h, width: 64.h),
SizedBox(
width: 80.w,
child: LocaleKeys.add.tr(context: context).toText12(
Utils.buildSvgWithAssets(icon: AppAssets.add_new_family_icon, height: 64.h, width: 64.h, applyThemeColor: false),
SizedBox(
width: 80.w,
child: LocaleKeys.add.tr(context: context).toText12(
color: AppColors.textColor,
isBold: true,
isCenter: true,

@ -220,8 +220,9 @@ class _SelectDoctorPageState extends State<SelectDoctorPage> {
],
).paddingSymmetrical(0.h, 0.h),
if (bookAppointmentsViewModel.isGetDocForHealthCal && bookAppointmentsVM.showSortFilterButtons) SizedBox(height: 16.h),
bookAppointmentsVM.doctorsListGrouped.isNotEmpty
? Row(
// bookAppointmentsVM.doctorsListGrouped.isNotEmpty
// ?
Row(
mainAxisSize: MainAxisSize.max,
children: [
Column(
@ -243,8 +244,8 @@ class _SelectDoctorPageState extends State<SelectDoctorPage> {
},
),
],
)
: SizedBox.shrink(),
),
// : SizedBox.shrink(),
ListView.separated(
padding: EdgeInsets.only(top: 16.h),
shrinkWrap: true,

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

@ -142,6 +142,16 @@ class LandingPageData {
textColor: AppColors.blackColor,
isBold: false,
),
ServiceCardData(
serviceName: "my_invoice",
icon: AppAssets.invoiceListIcon,
title: LocaleKeys.invoiceList,
subtitle: LocaleKeys.myDoctorSubtitle,
backgroundColor: AppColors.whiteColor,
iconColor: AppColors.blackColor,
textColor: AppColors.blackColor,
isBold: false,
),
];
static List<ServiceCardData> get getServiceCardsList => [

@ -105,9 +105,13 @@ class _LandingPageState extends State<LandingPage> {
final ScrollController _horizontalScrollController = ScrollController();
final ScrollController _scrollController = ScrollController();
double _scrollOpacity = 0.0;
@override
void dispose() {
_horizontalScrollController.dispose();
_scrollController.dispose();
super.dispose();
}
@ -123,6 +127,17 @@ class _LandingPageState extends State<LandingPage> {
showQuickLogin(context);
});
}
_scrollController.addListener(() {
final scrollOffset = _scrollController.offset;
final newOpacity = (scrollOffset / 50).clamp(0.0, 1.0);
if (_scrollOpacity != newOpacity) {
setState(() {
_scrollOpacity = newOpacity;
});
}
});
scheduleMicrotask(() async {
if (!appState.isAuthenticated) {
// LoaderBottomSheet.showLoader();
@ -189,7 +204,7 @@ class _LandingPageState extends State<LandingPage> {
// Refresh Appointments Data
myAppointmentsViewModel.setIsAppointmentDataToBeLoaded(true);
myAppointmentsViewModel.initAppointmentsViewModel();
myAppointmentsViewModel.getPatientAppointments(true, false);
myAppointmentsViewModel.getPatientAppointments(true, true);
// Refresh Appointments Data
habibWalletVM.initHabibWalletProvider();
@ -204,6 +219,7 @@ class _LandingPageState extends State<LandingPage> {
}
},
child: SingleChildScrollView(
controller: _scrollController,
physics: const AlwaysScrollableScrollPhysics(),
padding: EdgeInsets.only(
top: (appState.isAuthenticated && !insuranceVM.isInsuranceLoading && insuranceVM.isInsuranceExpired && insuranceVM.isInsuranceExpiryBannerShown)
@ -242,6 +258,7 @@ class _LandingPageState extends State<LandingPage> {
width: 42.w,
height: 42.h,
fit: BoxFit.cover,
isCircular: true,
),
).expanded
: CustomButton(
@ -697,8 +714,8 @@ class _LandingPageState extends State<LandingPage> {
itemBuilder: (context, index) {
return AnimationConfiguration.staggeredList(
position: index,
duration: const Duration(milliseconds: 1000),
child: SlideAnimation(
duration: const Duration(milliseconds: 500),
child: SlideAnimation(
horizontalOffset: 100.0,
child: FadeInAnimation(
child: SmallServiceCard(
@ -829,6 +846,25 @@ class _LandingPageState extends State<LandingPage> {
),
),
),
AnimatedOpacity(
opacity: _scrollOpacity,
duration: Duration(milliseconds: 200),
child: Container(
height: MediaQuery.paddingOf(context).top + 10.h,
decoration: BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
AppColors.scaffoldBgColor,
AppColors.scaffoldBgColor.withValues(alpha: 0.9),
AppColors.scaffoldBgColor.withValues(alpha: 0.0),
],
stops: [0.0, 0.7, 1.0],
),
),
),
),
(appState.isAuthenticated && !insuranceVM.isInsuranceLoading && insuranceVM.isInsuranceExpired && insuranceVM.isInsuranceExpiryBannerShown)
? Container(
height: MediaQuery.paddingOf(context).top + 50.h,
@ -1038,7 +1074,7 @@ class _LandingPageState extends State<LandingPage> {
// Queue Card: Currently serving section
Widget _buildServingNowSection() {
if (myAppointmentsViewModel.patientQueueDetailsList.isEmpty) {
return SizedBox(height: 12.h);
return SizedBox(height: 24.h);
}
final servingQueue = myAppointmentsViewModel.patientQueueDetailsList.first;

@ -16,6 +16,7 @@ import 'package:hmg_patient_app_new/presentation/health_calculators_and_converts
import 'package:hmg_patient_app_new/presentation/insurance/insurance_home_page.dart';
import 'package:hmg_patient_app_new/presentation/lab/lab_orders_page.dart';
import 'package:hmg_patient_app_new/presentation/medical_file/patient_sickleaves_list_page.dart';
import 'package:hmg_patient_app_new/presentation/my_invoices/my_invoices_list.dart';
import 'package:hmg_patient_app_new/presentation/parking/paking_page.dart';
import 'package:hmg_patient_app_new/presentation/prescriptions/prescriptions_list_page.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
@ -92,6 +93,13 @@ class SmallServiceCard extends StatelessWidget {
),
);
break;
case "my_invoice":
Navigator.of(context).push(
CustomPageRoute(
page: MyInvoicesList(),
),
);
break;
case "prescriptions":
Navigator.of(context).push(
CustomPageRoute(

@ -351,7 +351,7 @@ class _LabAiAnalysisDetailedPageState extends State<LabAiAnalysisDetailedPage> {
if (priority == null) return AppColors.primaryRedColor;
switch (priority.toLowerCase()) {
case 'high':
return Color(0xFFED1C2B).withOpacity(.10);
return AppColors.secondaryLightRedColor;
case 'medium' || 'moderate':
return AppColors.lightGreenButtonColor;
case 'low':

@ -247,6 +247,7 @@ class _MedicalFilePageState extends State<MedicalFilePage> {
width: 56.w,
height: 56.h,
fit: BoxFit.cover,
isCircular: true,
),
SizedBox(width: 8.w),
Column(

@ -181,6 +181,7 @@ class _FamilyCardsState extends State<FamilyCards> {
width: 56.w,
height: 56.h,
fit: BoxFit.cover,
isCircular: true,
),
SizedBox(width: 8.w),
Column(
@ -199,7 +200,7 @@ class _FamilyCardsState extends State<FamilyCards> {
children: [
AppCustomChipWidget(
icon: AppAssets.file_icon,
richText: "${LocaleKeys.fileno.tr(context: context)}: ${appState.getAuthenticatedUser()!.patientId}".toText10(isEnglishOnly: true),
richText: "${LocaleKeys.fileno.tr(context: context)}: ${appState.getAuthenticatedUser()!.patientId}".toText10(isBold: true),
labelPadding: EdgeInsetsDirectional.only(start: -4.w, end: 6.w),
),
AppCustomChipWidget(
@ -319,7 +320,6 @@ class _FamilyCardsState extends State<FamilyCards> {
final profile = widget.profileViewList![index];
final isActive = (profile.responseId == appState.getAuthenticatedUser()?.patientId);
final isParentUser = appState.getAuthenticatedUser()?.isParentUser ?? false;
print(jsonEncode(appState.getUserSwitchPayloads));
final isSuperUser = appState.getUserSwitchPayloads;
// TODO: canSwitch functionality is currently disabled - set to true to allow all switches
// Original logic: final canSwitch = isParentUser || (!isParentUser && profile.responseId == appState.getSuperUserID);
@ -340,6 +340,8 @@ class _FamilyCardsState extends State<FamilyCards> {
gender: profile.gender,
age: profile.age,
fit: BoxFit.cover,
customProfileImageData: "", // Prevent showing logged-in user's image for family members
isCircular: true,
),
SizedBox(height: 8.h),
(profile.patientName ?? "Unknown").toText14(isBold: true, isCenter: true, maxlines: 1),

@ -20,6 +20,7 @@ import 'package:hmg_patient_app_new/widgets/appbar/collapsing_list_view.dart';
import 'package:hmg_patient_app_new/widgets/common_bottom_sheet.dart';
import 'package:hmg_patient_app_new/widgets/loader/bottomsheet_loader.dart';
import 'package:hmg_patient_app_new/widgets/routes/custom_page_route.dart';
import 'package:open_filex/open_filex.dart';
import 'package:provider/provider.dart';
import '../../widgets/buttons/custom_button.dart';
@ -215,23 +216,36 @@ class _MyInvoicesListState extends State<MyInvoicesList> {
child: InvoiceListCard(
getInvoicesListResponseModel: myInvoicesVM.allInvoicesList[index],
onTap: () async {
myInvoicesVM.setInvoiceDetailLoading();
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingInvoiceDetails.tr(context: context));
await myInvoicesVM.getInvoiceDetails(
appointmentNo: myInvoicesVM.allInvoicesList[index].appointmentNo!,
if (Utils.isVidaPlusProject(myInvoicesVM.allInvoicesList[index].projectId ?? 0)) {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.sendingEmailPleaseWait.tr(context: context));
await myInvoicesViewModel.sendInvoiceEmail(
appointmentNo: myInvoicesVM.allInvoicesList[index].appointmentNo!,
projectID: myInvoicesVM.allInvoicesList[index].projectId!,
onSuccess: (val) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(context,
child: Utils.getSuccessWidget(loadingText: LocaleKeys.emailSentSuccessfullyMessage.tr(context: context)),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
isAutoDismiss: true);
},
onError: (err) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: err),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
});
} else {
LoaderBottomSheet.showLoader(loadingText: LocaleKeys.loadingText.tr(context: context));
myInvoicesViewModel.downloadInvoicePDF(
setupId: myInvoicesVM.allInvoicesList[index].setupId!,
invoiceNo: myInvoicesVM.allInvoicesList[index].invoiceNo!,
projectID: myInvoicesVM.allInvoicesList[index].projectId!,
onSuccess: (val) {
LoaderBottomSheet.hideLoader();
Navigator.of(context).push(
CustomPageRoute(
page: MyInvoicesDetailsPage(
getInvoiceDetailsResponseModel: myInvoicesVM.invoiceDetailsResponseModel,
getInvoicesListResponseModel: myInvoicesVM.allInvoicesList[index],
),
),
);
},
onError: (err) {
LoaderBottomSheet.hideLoader();
showCommonBottomSheetWithoutHeight(
@ -241,8 +255,56 @@ class _MyInvoicesListState extends State<MyInvoicesList> {
isFullScreen: false,
isCloseButtonVisible: true,
);
});
},
onSuccess: (value) async {
LoaderBottomSheet.hideLoader();
if (myInvoicesViewModel.downloadInvoicePDFBase64!.isNotEmpty) {
String path = await Utils.createFileFromString(myInvoicesViewModel.downloadInvoicePDFBase64!, "pdf");
try {
OpenFilex.open(path);
} catch (ex) {
showCommonBottomSheetWithoutHeight(
context,
child: Utils.getErrorWidget(loadingText: "Cannot open file"),
callBackFunc: () {},
isFullScreen: false,
isCloseButtonVisible: true,
);
}
}
},
);
}
},
// onTap: () async {
// myInvoicesVM.setInvoiceDetailLoading();
// LoaderBottomSheet.showLoader(loadingText: LocaleKeys.fetchingInvoiceDetails.tr(context: context));
// await myInvoicesVM.getInvoiceDetails(
// appointmentNo: myInvoicesVM.allInvoicesList[index].appointmentNo!,
// invoiceNo: myInvoicesVM.allInvoicesList[index].invoiceNo!,
// projectID: myInvoicesVM.allInvoicesList[index].projectId!,
// onSuccess: (val) {
// LoaderBottomSheet.hideLoader();
// Navigator.of(context).push(
// CustomPageRoute(
// page: MyInvoicesDetailsPage(
// getInvoiceDetailsResponseModel: myInvoicesVM.invoiceDetailsResponseModel,
// getInvoicesListResponseModel: myInvoicesVM.allInvoicesList[index],
// ),
// ),
// );
// },
// onError: (err) {
// LoaderBottomSheet.hideLoader();
// showCommonBottomSheetWithoutHeight(
// context,
// child: Utils.getErrorWidget(loadingText: err),
// callBackFunc: () {},
// isFullScreen: false,
// isCloseButtonVisible: true,
// );
// });
// },
),
),
),

@ -140,7 +140,8 @@ class InvoiceListCard extends StatelessWidget {
),
SizedBox(height: 16.h),
CustomButton(
text: LocaleKeys.viewInvoiceDetails.tr(context: context),
// text: LocaleKeys.viewInvoiceDetails.tr(context: context),
text: Utils.isVidaPlusProject(getInvoicesListResponseModel.projectId ?? 0) ? LocaleKeys.sendEmail.tr(context: context) : LocaleKeys.downloadInvoice.tr(context: context),
onPressed: () {
if (onTap != null) {
onTap!();

@ -459,6 +459,7 @@ class FamilyCardWidget extends StatelessWidget {
gender: profile.gender,
age: profile.age,
fit: BoxFit.cover,
isCircular: true,
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,

@ -56,6 +56,8 @@ class FamilyCardWidget extends StatelessWidget {
gender: profile.gender,
age: profile.age,
fit: BoxFit.cover,
customProfileImageData: "",
isCircular: true,// Prevent showing logged-in user's image for family members
),
Column(
crossAxisAlignment: CrossAxisAlignment.start,

@ -47,9 +47,9 @@ class _PreferredLanguageWidgetState extends State<PreferredLanguageWidget> {
decoration: RoundedRectangleBorder().toSmoothCornerDecoration(color: AppColors.whiteColor, borderRadius: 24.h, hasShadow: true),
child: Column(
children: [
languageItem("English", "2"),
1.divider,
languageItem("العربية", "1"),
1.divider,
languageItem("English", "2"),
],
),
),
@ -104,7 +104,7 @@ class _PreferredLanguageWidgetState extends State<PreferredLanguageWidget> {
},
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
title.toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1, fontFamily: _value == "1" ? 'GESSTwo' : 'Poppins').expanded,
title.toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1, fontFamily: _value == "1" ? 'CairoArabic' : 'Poppins').expanded,
],
).paddingOnly(left: 16, right: 16).onPress(() {
setState(() {

@ -103,7 +103,7 @@ class _AppLanguageChangeState extends State<AppLanguageChange> {
},
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
),
title.toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1, fontFamily: _value == "ar" ? 'GESSTwo' : 'Poppins').expanded,
title.toText16(isBold: true, textOverflow: TextOverflow.ellipsis, maxlines: 1, fontFamily: _value == "ar" ? 'CairoArabic' : 'Poppins').expanded,
],
).paddingOnly(left: 16, right: 16).onPress(() {
setState(() {

@ -238,7 +238,7 @@ void showCommonBottomSheetWithoutHeight(
duration: Duration(milliseconds: 500),
reverseDuration: Duration(milliseconds: 300),
),
constraints: BoxConstraints(maxWidth: MediaQuery.sizeOf(context).width //MediaQuery.of(context).size.width, // Full width
constraints: BoxConstraints(maxWidth: MediaQuery.of(context).size.width //MediaQuery.of(context).size.width, // Full width
),
context: context,
isScrollControlled: true,

@ -36,6 +36,12 @@ class UserAvatarWidget extends StatelessWidget {
// Use custom profile image data if provided, otherwise use AppState data
final profileImageData = customProfileImageData ?? appState.getProfileImageData;
// Debug logging
print('🖼️ UserAvatarWidget build - Has profile image: ${profileImageData != null && profileImageData.isNotEmpty}');
if (profileImageData != null && profileImageData.isNotEmpty) {
print('🖼️ Profile image data length: ${profileImageData.length} characters');
}
// Determine the default image based on gender and age
final String defaultImage;
if (userGender == 1) {

Loading…
Cancel
Save